1*be9aadfaSjsg /* $OpenBSD: ctfconv.c,v 1.12 2017/09/29 16:05:53 jsg Exp $ */ 20687c322Sjasper 3192095f7Smpi /* 4192095f7Smpi * Copyright (c) 2016-2017 Martin Pieuchot 5192095f7Smpi * 6192095f7Smpi * Permission to use, copy, modify, and distribute this software for any 7192095f7Smpi * purpose with or without fee is hereby granted, provided that the above 8192095f7Smpi * copyright notice and this permission notice appear in all copies. 9192095f7Smpi * 10192095f7Smpi * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11192095f7Smpi * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12192095f7Smpi * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13192095f7Smpi * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14192095f7Smpi * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15192095f7Smpi * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16192095f7Smpi * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17192095f7Smpi */ 18192095f7Smpi 19192095f7Smpi #include <sys/types.h> 20192095f7Smpi #include <sys/stat.h> 21192095f7Smpi #include <sys/exec_elf.h> 22192095f7Smpi #include <sys/mman.h> 23192095f7Smpi #include <sys/queue.h> 24192095f7Smpi #include <sys/tree.h> 25192095f7Smpi #include <sys/ctf.h> 26192095f7Smpi 27192095f7Smpi #include <assert.h> 28192095f7Smpi #include <err.h> 29192095f7Smpi #include <fcntl.h> 30192095f7Smpi #include <locale.h> 31192095f7Smpi #include <stdio.h> 32192095f7Smpi #include <stdint.h> 33192095f7Smpi #include <stdlib.h> 34192095f7Smpi #include <string.h> 35192095f7Smpi #include <unistd.h> 36192095f7Smpi 37192095f7Smpi #include "itype.h" 38192095f7Smpi #include "xmalloc.h" 39192095f7Smpi 40192095f7Smpi #ifndef nitems 41192095f7Smpi #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) 42192095f7Smpi #endif 43192095f7Smpi 44192095f7Smpi #define DEBUG_ABBREV ".debug_abbrev" 45192095f7Smpi #define DEBUG_INFO ".debug_info" 46192095f7Smpi #define DEBUG_LINE ".debug_line" 47192095f7Smpi #define DEBUG_STR ".debug_str" 48192095f7Smpi 49192095f7Smpi __dead void usage(void); 50192095f7Smpi int convert(const char *); 51192095f7Smpi int generate(const char *, const char *, int); 52192095f7Smpi int elf_convert(char *, size_t); 53192095f7Smpi void elf_sort(void); 54589f0bfaSmpi struct itype *find_symb(struct itype *, size_t); 55192095f7Smpi void dump_type(struct itype *); 56192095f7Smpi void dump_func(struct itype *, int *); 57192095f7Smpi void dump_obj(struct itype *, int *); 58192095f7Smpi 59192095f7Smpi /* elf.c */ 60192095f7Smpi int iself(const char *, size_t); 61192095f7Smpi int elf_getshstab(const char *, size_t, const char **, size_t *); 62*be9aadfaSjsg ssize_t elf_getsymtab(const char *, size_t, const char *, size_t, 63192095f7Smpi const Elf_Sym **, size_t *); 6459153d10Sjsg ssize_t elf_getsection(char *, size_t, const char *, const char *, 65192095f7Smpi size_t, const char **, size_t *); 66192095f7Smpi 67192095f7Smpi /* parse.c */ 68192095f7Smpi void dwarf_parse(const char *, size_t, const char *, size_t); 69192095f7Smpi 70192095f7Smpi const char *ctf_enc2name(unsigned short); 71192095f7Smpi 72192095f7Smpi /* lists of parsed types and functions */ 73192095f7Smpi struct itype_queue itypeq = TAILQ_HEAD_INITIALIZER(itypeq); 74192095f7Smpi struct itype_queue ifuncq = TAILQ_HEAD_INITIALIZER(ifuncq); 75192095f7Smpi struct itype_queue iobjq = TAILQ_HEAD_INITIALIZER(iobjq); 76192095f7Smpi 77192095f7Smpi __dead void 78192095f7Smpi usage(void) 79192095f7Smpi { 80f471c1b3Sjasper fprintf(stderr, "usage: %s [-d] -l label -o outfile file\n", 81192095f7Smpi getprogname()); 82192095f7Smpi exit(1); 83192095f7Smpi } 84192095f7Smpi 85192095f7Smpi int 86192095f7Smpi main(int argc, char *argv[]) 87192095f7Smpi { 88192095f7Smpi const char *filename, *label = NULL, *outfile = NULL; 89192095f7Smpi int dump = 0; 90192095f7Smpi int ch, error = 0; 91192095f7Smpi struct itype *it; 92192095f7Smpi 93192095f7Smpi setlocale(LC_ALL, ""); 94192095f7Smpi 955dd7bfaeSjasper if (pledge("stdio rpath wpath cpath", NULL) == -1) 965dd7bfaeSjasper err(1, "pledge"); 975dd7bfaeSjasper 98192095f7Smpi while ((ch = getopt(argc, argv, "dl:o:")) != -1) { 99192095f7Smpi switch (ch) { 100192095f7Smpi case 'd': 101d92886f7Sjasper dump = 1; /* ctfdump(1)-like SUNW_ctf sections */ 102192095f7Smpi break; 103192095f7Smpi case 'l': 104192095f7Smpi if (label != NULL) 105192095f7Smpi usage(); 106192095f7Smpi label = optarg; 107192095f7Smpi break; 108192095f7Smpi case 'o': 109192095f7Smpi if (outfile != NULL) 110192095f7Smpi usage(); 111192095f7Smpi outfile = optarg; 112192095f7Smpi break; 113192095f7Smpi default: 114192095f7Smpi usage(); 115192095f7Smpi } 116192095f7Smpi } 117192095f7Smpi 118192095f7Smpi argc -= optind; 119192095f7Smpi argv += optind; 120192095f7Smpi 121192095f7Smpi if (argc != 1) 122192095f7Smpi usage(); 123192095f7Smpi 124d92886f7Sjasper /* Either dump the sections, or write it out. */ 125d92886f7Sjasper if ((dump && (outfile != NULL || label != NULL)) || 126d92886f7Sjasper (!dump && (outfile == NULL || label == NULL))) 127192095f7Smpi usage(); 128192095f7Smpi 129192095f7Smpi filename = *argv; 130192095f7Smpi error = convert(filename); 131192095f7Smpi if (error != 0) 132192095f7Smpi return error; 133192095f7Smpi 1345dd7bfaeSjasper if (outfile != NULL) { 1355dd7bfaeSjasper if (pledge("stdio wpath cpath", NULL) == -1) 1365dd7bfaeSjasper err(1, "pledge"); 1375dd7bfaeSjasper 1385dd7bfaeSjasper error = generate(outfile, label, 1); 1395dd7bfaeSjasper if (error != 0) 1405dd7bfaeSjasper return error; 1415dd7bfaeSjasper } 1425dd7bfaeSjasper 143192095f7Smpi if (dump) { 1445dd7bfaeSjasper if (pledge("stdio", NULL) == -1) 1455dd7bfaeSjasper err(1, "pledge"); 1465dd7bfaeSjasper 147192095f7Smpi int fidx = -1, oidx = -1; 148192095f7Smpi 149192095f7Smpi TAILQ_FOREACH(it, &iobjq, it_symb) 150192095f7Smpi dump_obj(it, &oidx); 151192095f7Smpi printf("\n"); 152192095f7Smpi 153192095f7Smpi TAILQ_FOREACH(it, &ifuncq, it_symb) 154192095f7Smpi dump_func(it, &fidx); 155192095f7Smpi printf("\n"); 156192095f7Smpi 157192095f7Smpi TAILQ_FOREACH(it, &itypeq, it_next) { 158192095f7Smpi if (it->it_flags & (ITF_FUNC|ITF_OBJ)) 159192095f7Smpi continue; 160192095f7Smpi 161192095f7Smpi dump_type(it); 162192095f7Smpi } 163d92886f7Sjasper 164d92886f7Sjasper return 0; 165192095f7Smpi } 166192095f7Smpi 167192095f7Smpi return 0; 168192095f7Smpi } 169192095f7Smpi 170192095f7Smpi int 171192095f7Smpi convert(const char *path) 172192095f7Smpi { 173192095f7Smpi struct stat st; 174192095f7Smpi int fd, error = 1; 175192095f7Smpi char *p; 176192095f7Smpi 177192095f7Smpi fd = open(path, O_RDONLY); 178192095f7Smpi if (fd == -1) { 179192095f7Smpi warn("open %s", path); 180192095f7Smpi return 1; 181192095f7Smpi } 182192095f7Smpi if (fstat(fd, &st) == -1) { 183192095f7Smpi warn("fstat %s", path); 184c8f519c3Sjsg close(fd); 185192095f7Smpi return 1; 186192095f7Smpi } 187192095f7Smpi if ((uintmax_t)st.st_size > SIZE_MAX) { 188192095f7Smpi warnx("file too big to fit memory"); 189c8f519c3Sjsg close(fd); 190192095f7Smpi return 1; 191192095f7Smpi } 192192095f7Smpi 193192095f7Smpi p = mmap(NULL, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); 194192095f7Smpi if (p == MAP_FAILED) 195192095f7Smpi err(1, "mmap"); 196192095f7Smpi 197192095f7Smpi if (iself(p, st.st_size)) 198192095f7Smpi error = elf_convert(p, st.st_size); 199192095f7Smpi 200192095f7Smpi munmap(p, st.st_size); 201192095f7Smpi close(fd); 202192095f7Smpi 203192095f7Smpi return error; 204192095f7Smpi } 205192095f7Smpi 206192095f7Smpi const char *dstrbuf; 207192095f7Smpi size_t dstrlen; 208192095f7Smpi const char *strtab; 209192095f7Smpi const Elf_Sym *symtab; 210192095f7Smpi size_t strtabsz, nsymb; 211192095f7Smpi 212192095f7Smpi int 213192095f7Smpi elf_convert(char *p, size_t filesize) 214192095f7Smpi { 215192095f7Smpi const char *shstab; 216192095f7Smpi const char *infobuf, *abbuf; 217192095f7Smpi size_t infolen, ablen; 218192095f7Smpi size_t shstabsz; 219192095f7Smpi 220192095f7Smpi /* Find section header string table location and size. */ 221192095f7Smpi if (elf_getshstab(p, filesize, &shstab, &shstabsz)) 222192095f7Smpi return 1; 223192095f7Smpi 224192095f7Smpi /* Find symbol table location and number of symbols. */ 225*be9aadfaSjsg if (elf_getsymtab(p, filesize, shstab, shstabsz, &symtab, &nsymb) == -1) 226192095f7Smpi warnx("symbol table not found"); 227192095f7Smpi 228192095f7Smpi /* Find string table location and size. */ 22959153d10Sjsg if (elf_getsection(p, filesize, ELF_STRTAB, shstab, shstabsz, &strtab, 230192095f7Smpi &strtabsz) == -1) 231192095f7Smpi warnx("string table not found"); 232192095f7Smpi 233192095f7Smpi /* Find abbreviation location and size. */ 23459153d10Sjsg if (elf_getsection(p, filesize, DEBUG_ABBREV, shstab, shstabsz, &abbuf, 235192095f7Smpi &ablen) == -1) { 236192095f7Smpi warnx("%s section not found", DEBUG_ABBREV); 237192095f7Smpi return 1; 238192095f7Smpi } 239192095f7Smpi 24059153d10Sjsg if (elf_getsection(p, filesize, DEBUG_INFO, shstab, shstabsz, &infobuf, 241192095f7Smpi &infolen) == -1) { 242192095f7Smpi warnx("%s section not found", DEBUG_INFO); 243192095f7Smpi return 1; 244192095f7Smpi } 245192095f7Smpi 246192095f7Smpi /* Find string table location and size. */ 24759153d10Sjsg if (elf_getsection(p, filesize, DEBUG_STR, shstab, shstabsz, &dstrbuf, 248192095f7Smpi &dstrlen) == -1) 249192095f7Smpi warnx("%s section not found", DEBUG_STR); 250192095f7Smpi 251192095f7Smpi dwarf_parse(infobuf, infolen, abbuf, ablen); 252192095f7Smpi 253192095f7Smpi /* Sort functions */ 254192095f7Smpi elf_sort(); 255192095f7Smpi 256192095f7Smpi return 0; 257192095f7Smpi } 258192095f7Smpi 259589f0bfaSmpi struct itype * 260589f0bfaSmpi find_symb(struct itype *tmp, size_t stroff) 261589f0bfaSmpi { 262589f0bfaSmpi struct itype *it; 263589f0bfaSmpi char *sname, *p; 264589f0bfaSmpi 265589f0bfaSmpi if (strtab == NULL || stroff >= strtabsz) 266589f0bfaSmpi return NULL; 267589f0bfaSmpi 268589f0bfaSmpi /* 269589f0bfaSmpi * Skip local suffix 270589f0bfaSmpi * 271589f0bfaSmpi * FIXME: only skip local copies. 272589f0bfaSmpi */ 273589f0bfaSmpi sname = xstrdup(strtab + stroff); 274589f0bfaSmpi if ((p = strtok(sname, ".")) == NULL) { 275589f0bfaSmpi free(sname); 276589f0bfaSmpi return NULL; 277589f0bfaSmpi } 278589f0bfaSmpi 279589f0bfaSmpi strlcpy(tmp->it_name, p, ITNAME_MAX); 280589f0bfaSmpi free(sname); 281589f0bfaSmpi it = RB_FIND(isymb_tree, &isymbt, tmp); 282589f0bfaSmpi 283589f0bfaSmpi /* Restore original name */ 284589f0bfaSmpi if (it == NULL) 285589f0bfaSmpi strlcpy(tmp->it_name, (strtab + stroff), ITNAME_MAX); 286589f0bfaSmpi 287589f0bfaSmpi return it; 288589f0bfaSmpi } 289589f0bfaSmpi 290192095f7Smpi void 291192095f7Smpi elf_sort(void) 292192095f7Smpi { 293192095f7Smpi struct itype *it, tmp; 294192095f7Smpi size_t i; 295192095f7Smpi 296192095f7Smpi memset(&tmp, 0, sizeof(tmp)); 297192095f7Smpi for (i = 0; i < nsymb; i++) { 298192095f7Smpi const Elf_Sym *st = &symtab[i]; 299192095f7Smpi 300192095f7Smpi if (st->st_shndx == SHN_UNDEF || st->st_shndx == SHN_COMMON) 301192095f7Smpi continue; 302192095f7Smpi 303192095f7Smpi switch (ELF_ST_TYPE(st->st_info)) { 304192095f7Smpi case STT_FUNC: 305192095f7Smpi tmp.it_flags = ITF_FUNC; 306192095f7Smpi break; 307192095f7Smpi case STT_OBJECT: 308192095f7Smpi tmp.it_flags = ITF_OBJ; 309192095f7Smpi break; 310192095f7Smpi default: 311192095f7Smpi continue; 312192095f7Smpi } 313192095f7Smpi 314589f0bfaSmpi it = find_symb(&tmp, st->st_name); 315192095f7Smpi if (it == NULL) { 316192095f7Smpi /* Insert 'unknown' entry to match symbol order. */ 317192095f7Smpi it = it_dup(&tmp); 318192095f7Smpi it->it_refp = it; 319192095f7Smpi #ifdef DEBUG 320192095f7Smpi warnx("symbol not found: %s", it_name(it)); 321192095f7Smpi #endif 322192095f7Smpi } 323192095f7Smpi 324192095f7Smpi if (it->it_flags & ITF_INSERTED) { 325192095f7Smpi #ifdef DEBUG 326192095f7Smpi warnx("%s: already inserted", it_name(it)); 327192095f7Smpi #endif 328192095f7Smpi it = it_dup(it); 329192095f7Smpi } 330192095f7Smpi 331192095f7Smpi /* Save symbol index for dump. */ 332192095f7Smpi it->it_ref = i; 333192095f7Smpi 334192095f7Smpi it->it_flags |= ITF_INSERTED; 335192095f7Smpi if (it->it_flags & ITF_FUNC) 336192095f7Smpi TAILQ_INSERT_TAIL(&ifuncq, it, it_symb); 337192095f7Smpi else 338192095f7Smpi TAILQ_INSERT_TAIL(&iobjq, it, it_symb); 339192095f7Smpi } 340192095f7Smpi } 341192095f7Smpi 342192095f7Smpi const char * 343192095f7Smpi type_name(struct itype *it) 344192095f7Smpi { 345192095f7Smpi const char *name; 346192095f7Smpi 347192095f7Smpi name = it_name(it); 348192095f7Smpi if (name == NULL) 349192095f7Smpi return "(anon)"; 350192095f7Smpi 351192095f7Smpi return name; 352192095f7Smpi } 353192095f7Smpi 354192095f7Smpi /* Display parsed types a la ctfdump(1) */ 355192095f7Smpi void 356192095f7Smpi dump_type(struct itype *it) 357192095f7Smpi { 358192095f7Smpi struct imember *im; 359192095f7Smpi 360192095f7Smpi #ifdef DEBUG 361192095f7Smpi switch (it->it_type) { 362192095f7Smpi case CTF_K_POINTER: 363192095f7Smpi case CTF_K_TYPEDEF: 364192095f7Smpi case CTF_K_VOLATILE: 365192095f7Smpi case CTF_K_CONST: 366192095f7Smpi case CTF_K_RESTRICT: 367192095f7Smpi case CTF_K_ARRAY: 368192095f7Smpi case CTF_K_FUNCTION: 369192095f7Smpi if (it->it_refp == NULL) { 370192095f7Smpi printf("unresolved: %s type=%d\n", it_name(it), 371192095f7Smpi it->it_type); 372192095f7Smpi return; 373192095f7Smpi } 374192095f7Smpi default: 375192095f7Smpi break; 376192095f7Smpi } 377192095f7Smpi #endif 378192095f7Smpi 379192095f7Smpi switch (it->it_type) { 380192095f7Smpi case CTF_K_FLOAT: 381192095f7Smpi case CTF_K_INTEGER: 382192095f7Smpi printf(" [%u] %s %s encoding=%s offset=0 bits=%u\n", 383192095f7Smpi it->it_idx, 384192095f7Smpi (it->it_type == CTF_K_INTEGER) ? "INTEGER" : "FLOAT", 385192095f7Smpi it_name(it), ctf_enc2name(it->it_enc), it->it_size); 386192095f7Smpi break; 387192095f7Smpi case CTF_K_POINTER: 388192095f7Smpi printf(" <%u> POINTER %s refers to %u\n", it->it_idx, 389192095f7Smpi type_name(it), it->it_refp->it_idx); 390192095f7Smpi break; 391192095f7Smpi case CTF_K_TYPEDEF: 392192095f7Smpi printf(" <%u> TYPEDEF %s refers to %u\n", 393192095f7Smpi it->it_idx, it_name(it), it->it_refp->it_idx); 394192095f7Smpi break; 395192095f7Smpi case CTF_K_VOLATILE: 396192095f7Smpi printf(" <%u> VOLATILE %s refers to %u\n", it->it_idx, 397192095f7Smpi type_name(it), it->it_refp->it_idx); 398192095f7Smpi break; 399192095f7Smpi case CTF_K_CONST: 400192095f7Smpi printf(" <%u> CONST %s refers to %u\n", it->it_idx, 401192095f7Smpi type_name(it), it->it_refp->it_idx); 402192095f7Smpi break; 403192095f7Smpi case CTF_K_RESTRICT: 404192095f7Smpi printf(" <%u> RESTRICT %s refers to %u\n", it->it_idx, 405192095f7Smpi it_name(it), it->it_refp->it_idx); 406192095f7Smpi break; 407192095f7Smpi case CTF_K_ARRAY: 408192095f7Smpi printf(" [%u] ARRAY %s content: %u index: %u nelems: %u\n", 409192095f7Smpi it->it_idx, type_name(it), it->it_refp->it_idx, long_tidx, 410192095f7Smpi it->it_nelems); 411192095f7Smpi printf("\n"); 412192095f7Smpi break; 413192095f7Smpi case CTF_K_STRUCT: 414192095f7Smpi case CTF_K_UNION: 415192095f7Smpi printf(" [%u] %s %s (%u bytes)\n", it->it_idx, 416192095f7Smpi (it->it_type == CTF_K_STRUCT) ? "STRUCT" : "UNION", 417192095f7Smpi type_name(it), it->it_size); 418192095f7Smpi TAILQ_FOREACH(im, &it->it_members, im_next) { 419192095f7Smpi printf("\t%s type=%u off=%zd\n", 42072c906afSmpi (im_name(im) == NULL) ? "unknown" : im_name(im), 421d84376f1Smpi im->im_refp ? im->im_refp->it_idx : 0, im->im_off); 422192095f7Smpi } 423192095f7Smpi printf("\n"); 424192095f7Smpi break; 425192095f7Smpi case CTF_K_ENUM: 426192095f7Smpi printf(" [%u] ENUM %s\n\n", it->it_idx, type_name(it)); 427192095f7Smpi break; 428192095f7Smpi case CTF_K_FUNCTION: 429192095f7Smpi printf(" [%u] FUNCTION (%s) returns: %u args: (", 430192095f7Smpi it->it_idx, (it_name(it) != NULL) ? it_name(it) : "anon", 431192095f7Smpi it->it_refp->it_idx); 432192095f7Smpi TAILQ_FOREACH(im, &it->it_members, im_next) { 433192095f7Smpi printf("%u%s", im->im_refp->it_idx, 434192095f7Smpi TAILQ_NEXT(im, im_next) ? ", " : ""); 435192095f7Smpi } 436192095f7Smpi printf(")\n"); 437192095f7Smpi break; 438192095f7Smpi default: 439192095f7Smpi assert(0 == 1); 440192095f7Smpi } 441192095f7Smpi } 442192095f7Smpi 443192095f7Smpi void 444192095f7Smpi dump_func(struct itype *it, int *idx) 445192095f7Smpi { 446192095f7Smpi struct imember *im; 447192095f7Smpi 448192095f7Smpi (*idx)++; 449192095f7Smpi 450192095f7Smpi if (it->it_type == CTF_K_UNKNOWN && it->it_nelems == 0) 451192095f7Smpi return; 452192095f7Smpi 453192095f7Smpi printf(" [%u] FUNC (%s) returns: %u args: (", (*idx), 454192095f7Smpi (it_name(it) != NULL) ? it_name(it) : "unknown", 455192095f7Smpi it->it_refp->it_idx); 456192095f7Smpi TAILQ_FOREACH(im, &it->it_members, im_next) { 457192095f7Smpi printf("%u%s", im->im_refp->it_idx, 458192095f7Smpi TAILQ_NEXT(im, im_next) ? ", " : ""); 459192095f7Smpi } 460192095f7Smpi printf(")\n"); 461192095f7Smpi } 462192095f7Smpi 463192095f7Smpi void 464192095f7Smpi dump_obj(struct itype *it, int *idx) 465192095f7Smpi { 466192095f7Smpi int l; 467192095f7Smpi 468192095f7Smpi (*idx)++; 469192095f7Smpi 470192095f7Smpi l = printf(" [%u] %u", (*idx), it->it_refp->it_idx); 471192095f7Smpi printf("%*s %s (%llu)\n", 14 - l, "", it_name(it), it->it_ref); 472192095f7Smpi } 473192095f7Smpi 474192095f7Smpi const char * 475192095f7Smpi ctf_enc2name(unsigned short enc) 476192095f7Smpi { 477192095f7Smpi static const char *enc_name[] = { "SIGNED", "CHAR", "SIGNED CHAR", 478192095f7Smpi "BOOL", "SIGNED BOOL" }; 479192095f7Smpi static char invalid[7]; 480192095f7Smpi 481192095f7Smpi if (enc == CTF_INT_VARARGS) 482192095f7Smpi return "VARARGS"; 483192095f7Smpi 484192095f7Smpi if (enc > 0 && enc < nitems(enc_name)) 485192095f7Smpi return enc_name[enc - 1]; 486192095f7Smpi 487192095f7Smpi snprintf(invalid, sizeof(invalid), "0x%x", enc); 488192095f7Smpi return invalid; 489192095f7Smpi } 490