1 /* $OpenBSD: ctfdump.c,v 1.28 2024/02/22 13:21:03 claudio Exp $ */
2
3 /*
4 * Copyright (c) 2016 Martin Pieuchot <mpi@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/mman.h>
22 #include <sys/ctf.h>
23
24 #include <err.h>
25 #include <fcntl.h>
26 #include <gelf.h>
27 #include <libelf.h>
28 #include <locale.h>
29 #include <stdio.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34
35 #ifdef ZLIB
36 #include <zlib.h>
37 #endif /* ZLIB */
38
39 #ifndef nitems
40 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
41 #endif
42
43 #define DUMP_OBJECT (1 << 0)
44 #define DUMP_FUNCTION (1 << 1)
45 #define DUMP_HEADER (1 << 2)
46 #define DUMP_LABEL (1 << 3)
47 #define DUMP_STRTAB (1 << 4)
48 #define DUMP_STATISTIC (1 << 5)
49 #define DUMP_TYPE (1 << 6)
50
51 int dump(const char *, uint8_t);
52 int isctf(const char *, size_t);
53 __dead void usage(void);
54
55 int ctf_dump(const char *, size_t, uint8_t);
56 void ctf_dump_type(struct ctf_header *, const char *, size_t,
57 uint32_t, uint32_t *, uint32_t);
58 const char *ctf_kind2name(uint16_t);
59 const char *ctf_enc2name(uint16_t);
60 const char *ctf_fpenc2name(uint16_t);
61 const char *ctf_off2name(struct ctf_header *, const char *, size_t,
62 uint32_t);
63
64 char *decompress(const char *, size_t, size_t);
65 int elf_dump(uint8_t);
66 const char *elf_idx2sym(size_t *, uint8_t);
67
68 int
main(int argc,char * argv[])69 main(int argc, char *argv[])
70 {
71 const char *filename;
72 uint8_t flags = 0;
73 int ch, error = 0;
74
75 setlocale(LC_ALL, "");
76
77 if (pledge("stdio rpath", NULL) == -1)
78 err(1, "pledge");
79
80 while ((ch = getopt(argc, argv, "dfhlst")) != -1) {
81 switch (ch) {
82 case 'd':
83 flags |= DUMP_OBJECT;
84 break;
85 case 'f':
86 flags |= DUMP_FUNCTION;
87 break;
88 case 'h':
89 flags |= DUMP_HEADER;
90 break;
91 case 'l':
92 flags |= DUMP_LABEL;
93 break;
94 case 's':
95 flags |= DUMP_STRTAB;
96 break;
97 case 't':
98 flags |= DUMP_TYPE;
99 break;
100 default:
101 usage();
102 }
103 }
104
105 argc -= optind;
106 argv += optind;
107
108 if (argc <= 0)
109 usage();
110
111 /* Dump everything by default */
112 if (flags == 0)
113 flags = 0xff;
114
115 if (elf_version(EV_CURRENT) == EV_NONE)
116 errx(1, "elf_version: %s", elf_errmsg(-1));
117
118 while ((filename = *argv++) != NULL)
119 error |= dump(filename, flags);
120
121 return error;
122 }
123
124 Elf *e;
125 Elf_Scn *scnsymtab;
126 size_t strtabndx, strtabsz, nsymb;
127
128 int
dump(const char * path,uint8_t flags)129 dump(const char *path, uint8_t flags)
130 {
131 struct stat st;
132 char *p;
133 int fd, error = 1;
134
135 fd = open(path, O_RDONLY);
136 if (fd == -1) {
137 warn("open");
138 return 1;
139 }
140
141 if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
142 warnx("elf_begin: %s", elf_errmsg(-1));
143 goto done;
144 }
145
146 if (elf_kind(e) == ELF_K_ELF) {
147 error = elf_dump(flags);
148 elf_end(e);
149 goto done;
150 }
151 elf_end(e);
152
153 if (fstat(fd, &st) == -1) {
154 warn("fstat");
155 goto done;
156 }
157 if ((uintmax_t)st.st_size > SIZE_MAX) {
158 warnx("file too big to fit memory");
159 goto done;
160 }
161
162 p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
163 if (p == MAP_FAILED)
164 err(1, "mmap");
165
166 if (isctf(p, st.st_size))
167 error = ctf_dump(p, st.st_size, flags);
168
169 munmap(p, st.st_size);
170
171 done:
172 close(fd);
173 return error;
174 }
175
176 const char *
elf_idx2sym(size_t * idx,uint8_t type)177 elf_idx2sym(size_t *idx, uint8_t type)
178 {
179 GElf_Sym sym;
180 Elf_Data *data;
181 char *name;
182 size_t i;
183
184 if (scnsymtab == NULL || strtabndx == 0)
185 return NULL;
186
187 data = NULL;
188 while ((data = elf_rawdata(scnsymtab, data)) != NULL) {
189 for (i = *idx + 1; i < nsymb; i++) {
190 if (gelf_getsym(data, i, &sym) != &sym)
191 continue;
192 if (GELF_ST_TYPE(sym.st_info) != type)
193 continue;
194 if (sym.st_name >= strtabsz)
195 break;
196 if ((name = elf_strptr(e, strtabndx,
197 sym.st_name)) == NULL)
198 continue;
199
200 *idx = i;
201 return name;
202 }
203 }
204
205 return NULL;
206 }
207
208 int
elf_dump(uint8_t flags)209 elf_dump(uint8_t flags)
210 {
211 GElf_Shdr shdr;
212 Elf_Scn *scn, *scnctf;
213 Elf_Data *data;
214 char *name;
215 size_t shstrndx;
216 int error = 0;
217
218 if (elf_getshdrstrndx(e, &shstrndx) != 0) {
219 warnx("elf_getshdrstrndx: %s", elf_errmsg(-1));
220 return 1;
221 }
222
223 scn = scnctf = NULL;
224 while ((scn = elf_nextscn(e, scn)) != NULL) {
225 if (gelf_getshdr(scn, &shdr) != &shdr) {
226 warnx("elf_getshdr: %s", elf_errmsg(-1));
227 return 1;
228 }
229
230 if ((name = elf_strptr(e, shstrndx, shdr.sh_name)) == NULL) {
231 warnx("elf_strptr: %s", elf_errmsg(-1));
232 return 1;
233 }
234
235 if (strcmp(name, ELF_CTF) == 0)
236 scnctf = scn;
237
238 if (strcmp(name, ELF_SYMTAB) == 0 &&
239 shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
240 scnsymtab = scn;
241 nsymb = shdr.sh_size / shdr.sh_entsize;
242 }
243
244 if (strcmp(name, ELF_STRTAB) == 0 &&
245 shdr.sh_type == SHT_STRTAB) {
246 strtabndx = elf_ndxscn(scn);
247 strtabsz = shdr.sh_size;
248 }
249 }
250
251 if (scnctf == NULL) {
252 warnx("%s section not found", ELF_CTF);
253 return 1;
254 }
255
256 if (scnsymtab == NULL)
257 warnx("symbol table not found");
258
259 data = NULL;
260 while ((data = elf_rawdata(scnctf, data)) != NULL) {
261 if (data->d_buf == NULL) {
262 warnx("%s section size is zero", ELF_CTF);
263 return 1;
264 }
265
266 if (isctf(data->d_buf, data->d_size))
267 error |= ctf_dump(data->d_buf, data->d_size, flags);
268 }
269
270 return error;
271 }
272
273 int
isctf(const char * p,size_t filesize)274 isctf(const char *p, size_t filesize)
275 {
276 struct ctf_header cth;
277 size_t dlen;
278
279 if (filesize < sizeof(struct ctf_header)) {
280 warnx("file too small to be CTF");
281 return 0;
282 }
283
284 memcpy(&cth, p, sizeof(struct ctf_header));
285 if (cth.cth_magic != CTF_MAGIC || cth.cth_version != CTF_VERSION)
286 return 0;
287
288 dlen = cth.cth_stroff + cth.cth_strlen;
289 if (dlen > filesize && !(cth.cth_flags & CTF_F_COMPRESS)) {
290 warnx("bogus file size");
291 return 0;
292 }
293
294 if ((cth.cth_lbloff & 3) || (cth.cth_objtoff & 1) ||
295 (cth.cth_funcoff & 1) || (cth.cth_typeoff & 3)) {
296 warnx("wrongly aligned offset");
297 return 0;
298 }
299
300 if ((cth.cth_lbloff >= dlen) || (cth.cth_objtoff >= dlen) ||
301 (cth.cth_funcoff >= dlen) || (cth.cth_typeoff >= dlen)) {
302 warnx("truncated file");
303 return 0;
304 }
305
306 if ((cth.cth_lbloff > cth.cth_objtoff) ||
307 (cth.cth_objtoff > cth.cth_funcoff) ||
308 (cth.cth_funcoff > cth.cth_typeoff) ||
309 (cth.cth_typeoff > cth.cth_stroff)) {
310 warnx("corrupted file");
311 return 0;
312 }
313
314 return 1;
315 }
316
317 int
ctf_dump(const char * p,size_t size,uint8_t flags)318 ctf_dump(const char *p, size_t size, uint8_t flags)
319 {
320 struct ctf_header cth;
321 size_t dlen;
322 char *data;
323
324 memcpy(&cth, p, sizeof(struct ctf_header));
325 dlen = cth.cth_stroff + cth.cth_strlen;
326 if (cth.cth_flags & CTF_F_COMPRESS) {
327 data = decompress(p + sizeof(cth), size - sizeof(cth), dlen);
328 if (data == NULL)
329 return 1;
330 } else {
331 data = (char *)p + sizeof(cth);
332 }
333
334 if (flags & DUMP_HEADER) {
335 printf(" cth_magic = 0x%04x\n", cth.cth_magic);
336 printf(" cth_version = %u\n", cth.cth_version);
337 printf(" cth_flags = 0x%02x\n", cth.cth_flags);
338 printf(" cth_parlabel = %s\n",
339 ctf_off2name(&cth, data, dlen, cth.cth_parlabel));
340 printf(" cth_parname = %s\n",
341 ctf_off2name(&cth, data, dlen, cth.cth_parname));
342 printf(" cth_lbloff = %u\n", cth.cth_lbloff);
343 printf(" cth_objtoff = %u\n", cth.cth_objtoff);
344 printf(" cth_funcoff = %u\n", cth.cth_funcoff);
345 printf(" cth_typeoff = %u\n", cth.cth_typeoff);
346 printf(" cth_stroff = %u\n", cth.cth_stroff);
347 printf(" cth_strlen = %u\n", cth.cth_strlen);
348 printf("\n");
349 }
350
351 if (flags & DUMP_LABEL) {
352 uint32_t lbloff = cth.cth_lbloff;
353 struct ctf_lblent *ctl;
354
355 while (lbloff < cth.cth_objtoff) {
356 ctl = (struct ctf_lblent *)(data + lbloff);
357
358 printf(" %5u %s\n", ctl->ctl_typeidx,
359 ctf_off2name(&cth, data, dlen, ctl->ctl_label));
360
361 lbloff += sizeof(*ctl);
362 }
363 printf("\n");
364 }
365
366 if (flags & DUMP_OBJECT) {
367 uint32_t objtoff = cth.cth_objtoff;
368 size_t idx = 0, i = 0;
369 uint16_t *dsp;
370 const char *s;
371 int l;
372
373 while (objtoff < cth.cth_funcoff) {
374 dsp = (uint16_t *)(data + objtoff);
375
376 l = printf(" [%zu] %u", i++, *dsp);
377 if ((s = elf_idx2sym(&idx, STT_OBJECT)) != NULL)
378 printf("%*s %s (%zu)\n", (14 - l), "", s, idx);
379 else
380 printf("\n");
381
382 objtoff += sizeof(*dsp);
383 }
384 printf("\n");
385 }
386
387 if (flags & DUMP_FUNCTION) {
388 uint16_t *fsp, kind, vlen;
389 uint16_t *fstart, *fend;
390 size_t idx = 0, i = -1;
391 const char *s;
392 int l;
393
394 fstart = (uint16_t *)(data + cth.cth_funcoff);
395 fend = (uint16_t *)(data + cth.cth_typeoff);
396
397 fsp = fstart;
398 while (fsp < fend) {
399 kind = CTF_INFO_KIND(*fsp);
400 vlen = CTF_INFO_VLEN(*fsp);
401 s = elf_idx2sym(&idx, STT_FUNC);
402 fsp++;
403 i++;
404
405 if (kind == CTF_K_UNKNOWN && vlen == 0)
406 continue;
407
408 l = printf(" [%zu] FUNC ", i);
409 if (s != NULL)
410 printf("(%s) ", s);
411 printf("returns: %u args: (", *fsp++);
412 while (vlen-- > 0 && fsp < fend)
413 printf("%u%s", *fsp++, (vlen > 0) ? ", " : "");
414 printf(")\n");
415 }
416 printf("\n");
417 }
418
419 if (flags & DUMP_TYPE) {
420 uint32_t idx = 1, offset = cth.cth_typeoff;
421 uint32_t stroff = cth.cth_stroff;
422
423 while (offset < stroff) {
424 ctf_dump_type(&cth, data, dlen, stroff, &offset, idx++);
425 }
426 printf("\n");
427 }
428
429 if (flags & DUMP_STRTAB) {
430 uint32_t offset = 0;
431 const char *str;
432
433 while (offset < cth.cth_strlen) {
434 str = ctf_off2name(&cth, data, dlen, offset);
435
436 printf(" [%u] ", offset);
437 if (strcmp(str, "(anon)"))
438 offset += printf("%s\n", str);
439 else {
440 printf("\\0\n");
441 offset++;
442 }
443 }
444 printf("\n");
445 }
446
447 if (cth.cth_flags & CTF_F_COMPRESS)
448 free(data);
449
450 return 0;
451 }
452
453 void
ctf_dump_type(struct ctf_header * cth,const char * data,size_t dlen,uint32_t stroff,uint32_t * offset,uint32_t idx)454 ctf_dump_type(struct ctf_header *cth, const char *data, size_t dlen,
455 uint32_t stroff, uint32_t *offset, uint32_t idx)
456 {
457 const char *p = data + *offset;
458 const struct ctf_type *ctt = (struct ctf_type *)p;
459 const struct ctf_array *cta;
460 uint16_t *argp, i, kind, vlen, root;
461 uint32_t eob, toff;
462 uint64_t size;
463 const char *name, *kname;
464
465 kind = CTF_INFO_KIND(ctt->ctt_info);
466 vlen = CTF_INFO_VLEN(ctt->ctt_info);
467 root = CTF_INFO_ISROOT(ctt->ctt_info);
468 name = ctf_off2name(cth, data, dlen, ctt->ctt_name);
469
470 if (root)
471 printf(" <%u> ", idx);
472 else
473 printf(" [%u] ", idx);
474
475 if ((kname = ctf_kind2name(kind)) != NULL)
476 printf("%s %s", kname, name);
477
478 if (ctt->ctt_size <= CTF_MAX_SIZE) {
479 size = ctt->ctt_size;
480 toff = sizeof(struct ctf_stype);
481 } else {
482 size = CTF_TYPE_LSIZE(ctt);
483 toff = sizeof(struct ctf_type);
484 }
485
486 switch (kind) {
487 case CTF_K_UNKNOWN:
488 case CTF_K_FORWARD:
489 break;
490 case CTF_K_INTEGER:
491 eob = *((uint32_t *)(p + toff));
492 toff += sizeof(uint32_t);
493 printf(" encoding=%s offset=%u bits=%u (%llu bytes)",
494 ctf_enc2name(CTF_INT_ENCODING(eob)), CTF_INT_OFFSET(eob),
495 CTF_INT_BITS(eob), size);
496 break;
497 case CTF_K_FLOAT:
498 eob = *((uint32_t *)(p + toff));
499 toff += sizeof(uint32_t);
500 printf(" encoding=%s offset=%u bits=%u (%llu bytes)",
501 ctf_fpenc2name(CTF_FP_ENCODING(eob)), CTF_FP_OFFSET(eob),
502 CTF_FP_BITS(eob), size);
503 break;
504 case CTF_K_ARRAY:
505 cta = (struct ctf_array *)(p + toff);
506 printf(" content: %u index: %u nelems: %u\n", cta->cta_contents,
507 cta->cta_index, cta->cta_nelems);
508 toff += sizeof(struct ctf_array);
509 break;
510 case CTF_K_FUNCTION:
511 argp = (uint16_t *)(p + toff);
512 printf(" returns: %u args: (%u", ctt->ctt_type, *argp);
513 for (i = 1; i < vlen; i++) {
514 argp++;
515 if ((const char *)argp > data + dlen)
516 errx(1, "offset exceeds CTF section");
517
518 printf(", %u", *argp);
519 }
520 printf(")");
521 toff += (vlen + (vlen & 1)) * sizeof(uint16_t);
522 break;
523 case CTF_K_STRUCT:
524 case CTF_K_UNION:
525 printf(" (%llu bytes)\n", size);
526
527 if (size < CTF_LSTRUCT_THRESH) {
528 for (i = 0; i < vlen; i++) {
529 struct ctf_member *ctm;
530
531 if (p + toff > data + dlen)
532 errx(1, "offset exceeds CTF section");
533
534 if (toff > (stroff - sizeof(*ctm)))
535 break;
536
537 ctm = (struct ctf_member *)(p + toff);
538 toff += sizeof(struct ctf_member);
539
540 printf("\t%s type=%u off=%u\n",
541 ctf_off2name(cth, data, dlen,
542 ctm->ctm_name),
543 ctm->ctm_type, ctm->ctm_offset);
544 }
545 } else {
546 for (i = 0; i < vlen; i++) {
547 struct ctf_lmember *ctlm;
548
549 if (p + toff > data + dlen)
550 errx(1, "offset exceeds CTF section");
551
552 if (toff > (stroff - sizeof(*ctlm)))
553 break;
554
555 ctlm = (struct ctf_lmember *)(p + toff);
556 toff += sizeof(struct ctf_lmember);
557
558 printf("\t%s type=%u off=%llu\n",
559 ctf_off2name(cth, data, dlen,
560 ctlm->ctlm_name),
561 ctlm->ctlm_type, CTF_LMEM_OFFSET(ctlm));
562 }
563 }
564 break;
565 case CTF_K_ENUM:
566 printf(" (%llu bytes)\n", size);
567
568 for (i = 0; i < vlen; i++) {
569 struct ctf_enum *cte;
570
571 if (p + toff > data + dlen)
572 errx(1, "offset exceeds CTF section");
573
574 if (toff > (stroff - sizeof(*cte)))
575 break;
576
577 cte = (struct ctf_enum *)(p + toff);
578 toff += sizeof(struct ctf_enum);
579
580 printf("\t%s = %d\n",
581 ctf_off2name(cth, data, dlen, cte->cte_name),
582 cte->cte_value);
583 }
584 break;
585 case CTF_K_POINTER:
586 case CTF_K_TYPEDEF:
587 case CTF_K_VOLATILE:
588 case CTF_K_CONST:
589 case CTF_K_RESTRICT:
590 printf(" refers to %u", ctt->ctt_type);
591 break;
592 default:
593 errx(1, "incorrect type %u at offset %u", kind, *offset);
594 }
595
596 printf("\n");
597
598 *offset += toff;
599 }
600
601 const char *
ctf_kind2name(uint16_t kind)602 ctf_kind2name(uint16_t kind)
603 {
604 static const char *kind_name[] = { NULL, "INTEGER", "FLOAT", "POINTER",
605 "ARRAY", "FUNCTION", "STRUCT", "UNION", "ENUM", "FORWARD",
606 "TYPEDEF", "VOLATILE", "CONST", "RESTRICT" };
607
608 if (kind >= nitems(kind_name))
609 return NULL;
610
611 return kind_name[kind];
612 }
613
614 const char *
ctf_enc2name(uint16_t enc)615 ctf_enc2name(uint16_t enc)
616 {
617 static const char *enc_name[] = { "SIGNED", "CHAR", "SIGNED CHAR",
618 "BOOL", "SIGNED BOOL" };
619 static char invalid[7];
620
621 if (enc == CTF_INT_VARARGS)
622 return "VARARGS";
623
624 if (enc > 0 && enc <= nitems(enc_name))
625 return enc_name[enc - 1];
626
627 snprintf(invalid, sizeof(invalid), "0x%x", enc);
628 return invalid;
629 }
630
631 const char *
ctf_fpenc2name(uint16_t enc)632 ctf_fpenc2name(uint16_t enc)
633 {
634 static const char *enc_name[] = { "SINGLE", "DOUBLE", NULL, NULL,
635 NULL, "LDOUBLE" };
636 static char invalid[7];
637
638 if (enc > 0 && enc <= nitems(enc_name) && enc_name[enc - 1] != NULL)
639 return enc_name[enc - 1];
640
641 snprintf(invalid, sizeof(invalid), "0x%x", enc);
642 return invalid;
643 }
644
645 const char *
ctf_off2name(struct ctf_header * cth,const char * data,size_t dlen,uint32_t offset)646 ctf_off2name(struct ctf_header *cth, const char *data, size_t dlen,
647 uint32_t offset)
648 {
649 const char *name;
650
651 if (CTF_NAME_STID(offset) != CTF_STRTAB_0)
652 return "external";
653
654 if (CTF_NAME_OFFSET(offset) >= cth->cth_strlen)
655 return "exceeds strlab";
656
657 if (cth->cth_stroff + CTF_NAME_OFFSET(offset) >= dlen)
658 return "invalid";
659
660 name = data + cth->cth_stroff + CTF_NAME_OFFSET(offset);
661 if (*name == '\0')
662 return "(anon)";
663
664 return name;
665 }
666
667 char *
decompress(const char * buf,size_t size,size_t len)668 decompress(const char *buf, size_t size, size_t len)
669 {
670 #ifdef ZLIB
671 z_stream stream;
672 char *data;
673 int error;
674
675 data = malloc(len);
676 if (data == NULL) {
677 warn(NULL);
678 return NULL;
679 }
680
681 memset(&stream, 0, sizeof(stream));
682 stream.next_in = (void *)buf;
683 stream.avail_in = size;
684 stream.next_out = (uint8_t *)data;
685 stream.avail_out = len;
686
687 if ((error = inflateInit(&stream)) != Z_OK) {
688 warnx("zlib inflateInit failed: %s", zError(error));
689 goto exit;
690 }
691
692 if ((error = inflate(&stream, Z_FINISH)) != Z_STREAM_END) {
693 warnx("zlib inflate failed: %s", zError(error));
694 inflateEnd(&stream);
695 goto exit;
696 }
697
698 if ((error = inflateEnd(&stream)) != Z_OK) {
699 warnx("zlib inflateEnd failed: %s", zError(error));
700 goto exit;
701 }
702
703 if (stream.total_out != len) {
704 warnx("decompression failed: %lu != %zu",
705 stream.total_out, len);
706 goto exit;
707 }
708
709 return data;
710
711 exit:
712 free(data);
713 #endif /* ZLIB */
714 return NULL;
715 }
716
717 __dead void
usage(void)718 usage(void)
719 {
720 fprintf(stderr, "usage: %s [-dfhlst] file ...\n",
721 getprogname());
722 exit(1);
723 }
724