Dump function with "-f"
[ctfdump/.git] / ctfdump.c
1 /*
2  * Copyright (c) 2016 Martin Pieuchot <mpi@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include <sys/param.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/exec_elf.h>
21 #include <sys/mman.h>
22
23 #include <err.h>
24 #include <fcntl.h>
25 #include <locale.h>
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #ifdef ZLIB
33 #include <zlib.h>
34 #endif /* ZLIB */
35
36 #include "ctf.h"
37
38 #define SUNW_CTF        ".SUNW_ctf"
39
40 #define DUMP_OBJECT     (1 << 0)
41 #define DUMP_FUNCTION   (1 << 1)
42 #define DUMP_HEADER     (1 << 2)
43 #define DUMP_LABEL      (1 << 3)
44
45 int              dump(const char *, uint32_t);
46 int              iself(const char *, size_t);
47 int              isctf(const char *, size_t);
48 __dead void      usage(void);
49
50 int              ctf_dump(const char *, size_t, uint32_t);
51 const char      *ctf_off2name(struct ctf_header *, const char *, off_t,
52                      unsigned int);
53
54 int              elf_dump(const char *, size_t, uint32_t);
55 int              elf_getshstrtab(const char *, size_t, const char **, size_t *);
56 int              elf_getsymtab(const char *, const char *, size_t,
57                      const Elf_Sym **, size_t *);
58 int              elf_getstrtab(const char *, const char *, size_t,
59                      const char **, size_t *);
60
61 #ifdef ZLIB
62 char            *decompress(const char *, size_t, off_t);
63 #endif /* ZLIB */
64
65 int
66 main(int argc, char *argv[])
67 {
68         const char *filename;
69         uint32_t flags = 0;
70         int ch, error = 0;
71
72         setlocale(LC_ALL, "");
73
74         while ((ch = getopt(argc, argv, "dfhlsSt")) != -1) {
75                 switch (ch) {
76                 case 'd':
77                         flags |= DUMP_OBJECT;
78                         break;
79                 case 'f':
80                         flags |= DUMP_FUNCTION;
81                         break;
82                 case 'h':
83                         flags |= DUMP_HEADER;
84                         break;
85                 case 'l':
86                         flags |= DUMP_LABEL;
87                         break;
88                 default:
89                         usage();
90                 }
91         }
92
93         argc -= optind;
94         argv += optind;
95
96         while ((filename = *argv++) != NULL)
97                 error |= dump(filename, flags);
98
99         return error;
100 }
101
102 int
103 dump(const char *path, uint32_t flags)
104 {
105         struct stat              st;
106         int                      fd, error = 1;
107         char                    *p;
108
109         fd = open(path, O_RDONLY);
110         if (fd == -1) {
111                 warn("open");
112                 return 1;
113         }
114         if (fstat(fd, &st) == -1) {
115                 warn("fstat");
116                 return 1;
117         }
118         if (st.st_size < (off_t)sizeof(struct ctf_header)) {
119                 warnx("file too small to be CTF");
120                 return 1;
121         }
122         if ((uintmax_t)st.st_size > SIZE_MAX) {
123                 warnx("file too big to fit memory");
124                 return 1;
125         }
126
127         p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
128         if (p == MAP_FAILED)
129                 err(1, "mmap");
130
131         if (iself(p, st.st_size)) {
132                 error = elf_dump(p, st.st_size, flags);
133         } else if (isctf(p, st.st_size)) {
134                 error = ctf_dump(p, st.st_size, flags);
135         }
136
137         munmap(p, st.st_size);
138         close(fd);
139
140         return error;
141 }
142
143 int
144 iself(const char *p, size_t filesize)
145 {
146         Elf_Ehdr                *eh = (Elf_Ehdr *)p;
147
148         if (eh->e_ehsize < sizeof(Elf_Ehdr) || !IS_ELF(*eh)) {
149                 warnx("file is not ELF");
150                 return 0;
151         }
152         if (eh->e_ident[EI_CLASS] != ELFCLASS) {
153                 warnx("unexpected word size %u", eh->e_ident[EI_CLASS]);
154                 return 0;
155         }
156         if (eh->e_ident[EI_VERSION] != ELF_TARG_VER) {
157                 warnx("unexpected version %u", eh->e_ident[EI_VERSION]);
158                 return 0;
159         }
160         if (eh->e_ident[EI_DATA] >= ELFDATANUM) {
161                 warnx("unexpected data format %u", eh->e_ident[EI_DATA]);
162                 return 0;
163         }
164         if (eh->e_shoff > filesize) {
165                 warnx("bogus section table offset 0x%llx", eh->e_shoff);
166                 return 0;
167         }
168         if (eh->e_shentsize < sizeof(Elf_Shdr)) {
169                 warnx("bogus section header size %u", eh->e_shentsize);
170                 return 0;
171         }
172         if (eh->e_shnum > (filesize - eh->e_shoff) / eh->e_shentsize) {
173                 warnx("bogus section header count %u", eh->e_shnum);
174                 return 0;
175         }
176         if (eh->e_shstrndx >= eh->e_shnum) {
177                 warnx("bogus string table index %u", eh->e_shstrndx);
178                 return 0;
179         }
180
181         return 1;
182 }
183
184 int
185 elf_getshstrtab(const char *p, size_t filesize, const char **shstrtab,
186     size_t *shstrtabsize)
187 {
188         Elf_Ehdr                *eh = (Elf_Ehdr *)p;
189         Elf_Shdr                *sh;
190
191         sh = (Elf_Shdr *)(p + eh->e_shoff + eh->e_shstrndx * eh->e_shentsize);
192         if (sh->sh_type != SHT_STRTAB) {
193                 warnx("unexpected string table type");
194                 return 1;
195         }
196         if (sh->sh_offset > filesize) {
197                 warnx("bogus string table offset");
198                 return 1;
199         }
200         if (sh->sh_size > filesize - sh->sh_offset) {
201                 warnx("bogus string table size");
202                 return 1;
203         }
204         if (shstrtab != NULL)
205                 *shstrtab = p + sh->sh_offset;
206         if (shstrtabsize != NULL)
207                 *shstrtabsize = sh->sh_size;
208
209         return 0;
210 }
211
212 int
213 elf_getsymtab(const char *p, const char *shstrtab, size_t shstrtabsize,
214     const Elf_Sym **symtab, size_t *nsymb)
215 {
216         Elf_Ehdr        *eh = (Elf_Ehdr *)p;
217         Elf_Shdr        *sh;
218         size_t           i;
219
220         for (i = 0; i < eh->e_shnum; i++) {
221                 sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize);
222
223                 if (sh->sh_type != SHT_SYMTAB)
224                         continue;
225
226                 if ((sh->sh_link >= eh->e_shnum) ||
227                     (sh->sh_name >= shstrtabsize))
228                         continue;
229
230                 if (strncmp(shstrtab + sh->sh_name, ELF_SYMTAB,
231                     strlen(ELF_SYMTAB)) == 0) {
232                         if (symtab != NULL)
233                                 *symtab = (Elf_Sym *)(p + sh->sh_offset);
234                         if (nsymb != NULL)
235                                 *nsymb = (sh->sh_size / sh->sh_entsize);
236
237                         return 0;
238                 }
239         }
240
241         return 1;
242 }
243
244 int
245 elf_getstrtab(const char *p, const char *shstrtab, size_t shstrtabsize,
246     const char **strtab, size_t *strtabsize)
247 {
248         Elf_Ehdr        *eh = (Elf_Ehdr *)p;
249         Elf_Shdr        *sh;
250         size_t           i;
251
252         for (i = 0; i < eh->e_shnum; i++) {
253                 sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize);
254
255                 if (sh->sh_type != SHT_STRTAB)
256                         continue;
257
258                 if ((sh->sh_link >= eh->e_shnum) ||
259                     (sh->sh_name >= shstrtabsize))
260                         continue;
261
262                 if (strncmp(shstrtab + sh->sh_name, ELF_STRTAB,
263                     strlen(ELF_STRTAB)) == 0) {
264                         if (strtab != NULL)
265                                 *strtab = p + sh->sh_offset;
266                         if (strtabsize != NULL)
267                                 *strtabsize = sh->sh_size;
268
269                         return 0;
270                 }
271         }
272
273         return 1;
274 }
275
276 const char              *strtab;
277 const Elf_Sym           *symtab;
278 size_t                   strtabsize, nsymb;
279
280 const char *
281 elf_idx2sym(size_t *idx, unsigned char type)
282 {
283         const Elf_Sym   *st;
284         size_t           i;
285
286         for (i = *idx + 1; i < nsymb; i++) {
287                 st = &symtab[i];
288
289                 if (ELF_ST_TYPE(st->st_info) != type)
290                         continue;
291
292                 *idx = i;
293                 return strtab + st->st_name;
294         }
295
296         return NULL;
297 }
298
299 int
300 elf_dump(const char *p, size_t filesize, uint32_t flags)
301 {
302         Elf_Ehdr                *eh = (Elf_Ehdr *)p;
303         Elf_Shdr                *sh;
304         const char              *shstrtab;
305         size_t                   i, shstrtabsize;
306
307         /* Find section header string table location and size. */
308         if (elf_getshstrtab(p, filesize, &shstrtab, &shstrtabsize))
309                 return 1;
310
311         /* Find symbol table location and number of symbols. */
312         if (elf_getsymtab(p, shstrtab, shstrtabsize, &symtab, &nsymb))
313                 warnx("symbol table not found");
314
315         /* Find string table location and size. */
316         if (elf_getstrtab(p, shstrtab, shstrtabsize, &strtab, &strtabsize))
317                 warnx("string table not found");
318
319         /* Find CTF section and dump it. */
320         for (i = 0; i < eh->e_shnum; i++) {
321                 sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize);
322
323                 if ((sh->sh_link >= eh->e_shnum) ||
324                     (sh->sh_name >= shstrtabsize))
325                         continue;
326
327                 if (strncmp(shstrtab + sh->sh_name, SUNW_CTF, strlen(SUNW_CTF)))
328                         continue;
329
330                 if (!isctf(p + sh->sh_offset, sh->sh_size))
331                         break;
332
333                 return ctf_dump(p + sh->sh_offset, sh->sh_size, flags);
334         }
335
336         warnx("%s section not found", SUNW_CTF);
337         return 1;
338 }
339
340 int
341 isctf(const char *p, size_t filesize)
342 {
343         struct ctf_header       *cth = (struct ctf_header *)p;
344         off_t                    dlen = cth->cth_stroff + cth->cth_strlen;
345
346         if (cth->cth_magic != CTF_MAGIC || cth->cth_version != CTF_VERSION)
347                 return 0;
348
349         if (dlen > filesize && !(cth->cth_flags & CTF_F_COMPRESS)) {
350                 warnx("bogus file size");
351                 return 0;
352         }
353
354         if ((cth->cth_lbloff & 3) || (cth->cth_objtoff & 1) ||
355             (cth->cth_funcoff & 1) || (cth->cth_typeoff & 3)) {
356                 warnx("wrongly aligned offset");
357                 return 0;
358         }
359
360         if ((cth->cth_lbloff >= dlen) || (cth->cth_objtoff >= dlen) ||
361             (cth->cth_funcoff >= dlen) || (cth->cth_typeoff >= dlen)) {
362                 warnx("truncated file");
363                 return 0;
364         }
365
366         if ((cth->cth_lbloff > cth->cth_objtoff) ||
367             (cth->cth_objtoff > cth->cth_funcoff) ||
368             (cth->cth_funcoff > cth->cth_typeoff) ||
369             (cth->cth_typeoff > cth->cth_stroff)) {
370                 warnx("corrupted file");
371                 return 0;
372         }
373
374         return 1;
375 }
376
377 int
378 ctf_dump(const char *p, size_t size, uint32_t flags)
379 {
380         struct ctf_header       *cth = (struct ctf_header *)p;
381         char                    *data = (char *)p;
382         off_t                    dlen = cth->cth_stroff + cth->cth_strlen;
383
384         if (cth->cth_flags & CTF_F_COMPRESS) {
385                 data = decompress(p + sizeof(*cth), size - sizeof(*cth), dlen);
386                 if (data == NULL)
387                         return 1;
388         }
389
390         if (flags & DUMP_HEADER) {
391                 printf("cth_magic    = 0x%04x\n", cth->cth_magic);
392                 printf("cth_version  = %d\n", cth->cth_version);
393                 printf("cth_flags    = 0x%02x\n", cth->cth_flags);
394                 printf("cth_parlabel = %s\n",
395                     ctf_off2name(cth, data, dlen, cth->cth_parname));
396                 printf("cth_parname  = %s\n",
397                     ctf_off2name(cth, data, dlen, cth->cth_parname));
398                 printf("cth_lbloff   = %d\n", cth->cth_lbloff);
399                 printf("cth_objtoff  = %d\n", cth->cth_objtoff);
400                 printf("cth_funcoff  = %d\n", cth->cth_funcoff);
401                 printf("cth_typeoff  = %d\n", cth->cth_typeoff);
402                 printf("cth_stroff   = %d\n", cth->cth_stroff);
403                 printf("cth_strlen   = %d\n", cth->cth_strlen);
404         }
405
406         if (flags & DUMP_LABEL) {
407                 unsigned int             lbloff = cth->cth_lbloff;
408                 struct ctf_lblent       *ctl;
409
410                 while (lbloff < cth->cth_objtoff) {
411                         ctl = (struct ctf_lblent *)(data + lbloff);
412
413                         printf("%5u %s\n", ctl->ctl_typeidx,
414                             ctf_off2name(cth, data, dlen, ctl->ctl_label));
415
416                         lbloff += sizeof(*ctl);
417                 }
418         }
419
420         if (flags & DUMP_OBJECT) {
421                 unsigned int             objtoff = cth->cth_objtoff;
422                 size_t                   idx = 0, i = 0;
423                 unsigned short          *dsp;
424                 const char              *s;
425                 int                      l;
426
427                 while (objtoff < cth->cth_funcoff) {
428                         dsp = (unsigned short *)(data + objtoff);
429
430                         l = printf("[%zu] %u", i++, *dsp);
431                         if ((s = elf_idx2sym(&idx, STT_OBJECT)) != NULL)
432                                 printf("%*s %s (%zu)\n", (12 - l), "", s, idx);
433                         else
434                                 printf("\n");
435
436                         objtoff += sizeof(*dsp);
437                 }
438         }
439
440         if (flags & DUMP_FUNCTION) {
441                 unsigned short          *fsp, kind, vlen;
442                 size_t                   idx = 0, i = 0;
443                 const char              *s;
444                 int                      l;
445
446                 fsp = (unsigned short *)(data + cth->cth_funcoff);
447                 while (fsp < (unsigned short *)(data + cth->cth_typeoff)) {
448                         kind = CTF_INFO_KIND(*fsp);
449                         vlen = CTF_INFO_VLEN(*fsp);
450                         fsp++;
451
452                         if (kind == CTF_K_UNKNOWN && vlen == 0)
453                                 continue;
454
455                         l = printf("%u [%zu] FUNC", vlen, i++);
456                         if ((s = elf_idx2sym(&idx, STT_FUNC)) != NULL)
457                                 printf(" (%s)", s);
458                         printf(" returns: %u args: (", *fsp++);
459                         while (vlen-- > 0)
460                                 printf("%u%s", *fsp++, (vlen > 0) ? ", " : "");
461                         printf(")\n");
462                 }
463         }
464
465         if (cth->cth_flags & CTF_F_COMPRESS)
466                 free(data);
467
468         return 0;
469 }
470
471 const char *
472 ctf_off2name(struct ctf_header *cth, const char *data, off_t dlen,
473     unsigned int offset)
474 {
475         const char              *name;
476
477         if (CTF_NAME_STID(offset) != CTF_STRTAB_0)
478                 return "external";
479
480         if (CTF_NAME_OFFSET(offset) >= cth->cth_strlen)
481                 return "exceeds strlab";
482
483         if (cth->cth_stroff + CTF_NAME_OFFSET(offset) >= dlen)
484                 return "invalid";
485
486         name = data + cth->cth_stroff + CTF_NAME_OFFSET(offset);
487         if (*name == '\0')
488                 return "(anon)";
489
490         return name;
491 }
492
493 char *
494 decompress(const char *buf, size_t size, off_t len)
495 {
496 #ifdef ZLIB
497         z_stream                 stream;
498         char                    *data;
499         int                      error;
500
501         data = malloc(len);
502         if (data == NULL) {
503                 warn(NULL);
504                 return NULL;
505         }
506
507         memset(&stream, 0, sizeof(stream));
508         stream.next_in = (void *)buf;
509         stream.avail_in = size;
510         stream.next_out = data;
511         stream.avail_out = len;
512
513         if ((error = inflateInit(&stream)) != Z_OK) {
514                 warnx("zlib inflateInit failed: %s", zError(error));
515                 goto exit;
516         }
517
518         if ((error = inflate(&stream, Z_FINISH)) != Z_STREAM_END) {
519                 warnx("zlib inflate failed: %s", zError(error));
520                 goto exit;
521         }
522
523         if ((error = inflateEnd(&stream)) != Z_OK) {
524                 warnx("zlib inflateEnd failed: %s", zError(error));
525                 goto exit;
526         }
527
528         if (stream.total_out != len) {
529                 warnx("decompression failed: %llu != %llu",
530                     stream.total_out, len);
531                 goto exit;
532         }
533
534         return data;
535
536 exit:
537         free(data);
538 #endif /* ZLIB */
539         return NULL;
540 }
541
542 __dead void
543 usage(void)
544 {
545         extern char             *__progname;
546
547         fprintf(stderr, "usage: %s [-dfhlsSt] [file ...]\n",
548             __progname);
549         exit(1);
550 }
551