inlinecall

Print call sites of an inline function
git clone git://git.margiolis.net/inlinecall.git
Log | Files | Refs | README | LICENSE

inlinecall.c (11224B)


      1 #include <sys/param.h>
      2 #include <sys/queue.h>
      3 
      4 #include <dwarf.h>
      5 #include <err.h>
      6 #include <fcntl.h>
      7 #include <gelf.h>
      8 #include <libdwarf.h>
      9 #include <libelf.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <unistd.h>
     14 
     15 struct elf_info {
     16 	Elf			*elf;
     17 	struct section	{
     18 		Elf_Scn		*scn;
     19 		GElf_Shdr	sh;
     20 	}			*sl;
     21 	size_t			shnum;
     22 };
     23 
     24 struct entry {
     25 	int			naddrs;
     26 	struct addr_pair {
     27 		uint64_t	lo;
     28 		uint64_t	hi;
     29 	}			*addr;
     30 	uint64_t		line;
     31 	const char		*file;
     32 	enum {
     33 		F_SUBPROGRAM,
     34 		F_INLINE_COPY,
     35 	}			flag;
     36 	TAILQ_ENTRY(entry)	next;
     37 };
     38 
     39 static void		*emalloc(size_t);
     40 static void		parse_die(Dwarf_Debug, Dwarf_Die, int, int);
     41 static const char	*find_caller_func(struct addr_pair);
     42 static void		dump_results(void);
     43 
     44 static char			**srcfiles;
     45 static char			*func;
     46 static Dwarf_Off		g_dieoff;
     47 static struct elf_info		ei;
     48 static TAILQ_HEAD(, entry)	head = TAILQ_HEAD_INITIALIZER(head);
     49 
     50 static void *
     51 emalloc(size_t nb)
     52 {
     53 	void *p;
     54 
     55 	if ((p = malloc(nb)) == NULL)
     56 		err(1, "malloc");
     57 
     58 	return (p);
     59 }
     60 
     61 static void
     62 parse_die(Dwarf_Debug dbg, Dwarf_Die die, int level, int flag)
     63 {
     64 	static Dwarf_Die die_root;
     65 	Dwarf_Die die_next;
     66 	Dwarf_Ranges *ranges, *rp;
     67 	Dwarf_Attribute attp;
     68 	Dwarf_Addr base0, lowpc, highpc;
     69 	Dwarf_Off dieoff, cuoff, culen, v_off;
     70 	Dwarf_Unsigned line, nbytes, v_udata;
     71 	Dwarf_Signed nranges;
     72 	Dwarf_Half attr, tag;
     73 	Dwarf_Bool v_flag;
     74 	Dwarf_Error error;
     75 	struct entry *e;
     76 	struct addr_pair *addr;
     77 	const char *str;
     78 	char *v_str;
     79 	char *file = NULL;
     80 	int naddrs;
     81 	int res, i, found = 0;
     82 
     83 	if (level == 0)
     84 		die_root = die;
     85 
     86 	if (dwarf_dieoffset(die, &dieoff, &error) != DW_DLV_OK) {
     87 		warnx("%s", dwarf_errmsg(error));
     88 		goto cont;
     89 	}
     90 	if (dwarf_die_CU_offset_range(die, &cuoff, &culen, &error) != DW_DLV_OK) {
     91 		warnx("%s", dwarf_errmsg(error));
     92 		cuoff = 0;
     93 	}
     94 	if (dwarf_tag(die, &tag, &error) != DW_DLV_OK) {
     95 		warnx("%s", dwarf_errmsg(error));
     96 		goto cont;
     97 	}
     98 	if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
     99 		goto cont;
    100 	if (flag == F_SUBPROGRAM && tag == DW_TAG_subprogram) {
    101 		if (dwarf_hasattr(die, DW_AT_inline, &v_flag, &error) !=
    102 		    DW_DLV_OK) {
    103 			warnx("%s", dwarf_errmsg(error));
    104 			goto cont;
    105 		}
    106 		if (!v_flag)
    107 			goto cont;
    108 		res = dwarf_diename(die, &v_str, &error);
    109 		if (res != DW_DLV_OK) {
    110 			warnx("%s", dwarf_errmsg(error));
    111 			goto cont;
    112 		}
    113 		if (strcmp(v_str, func) != 0)
    114 			goto cont;
    115 		/*
    116 		 * The function name we're searching for has an inline
    117 		 * definition.
    118 		 */
    119 		found = 1;
    120 	} else if (flag == F_INLINE_COPY && tag == DW_TAG_inlined_subroutine) {
    121 		res = dwarf_attr(die, DW_AT_abstract_origin, &attp, &error);
    122 		if (res != DW_DLV_OK) {
    123 			if (res == DW_DLV_ERROR)
    124 				warnx("%s", dwarf_errmsg(error));
    125 			goto cont;
    126 		}
    127 		if (dwarf_formref(attp, &v_off, &error) != DW_DLV_OK) {
    128 			warnx("%s", dwarf_errmsg(error));
    129 			goto cont;
    130 		}
    131 		v_off += cuoff;
    132 		/* Doesn't point to the definition's DIE offset. */
    133 		if (v_off != g_dieoff)
    134 			goto cont;
    135 
    136 		if (dwarf_hasattr(die, DW_AT_ranges, &v_flag, &error) !=
    137 		    DW_DLV_OK) {
    138 			warnx("%s", dwarf_errmsg(error));
    139 			goto cont;
    140 		}
    141 		if (v_flag) {
    142 			/* DIE has ranges */
    143 			res = dwarf_attr(die, DW_AT_ranges, &attp, &error);
    144 			if (res != DW_DLV_OK) {
    145 				if (res == DW_DLV_ERROR)
    146 					warnx("%s", dwarf_errmsg(error));
    147 				goto cont;
    148 			}
    149 			if (dwarf_global_formref(attp, &v_off, &error) !=
    150 			    DW_DLV_OK) {
    151 				warnx("%s", dwarf_errmsg(error));
    152 				goto cont;
    153 			}
    154 			if (dwarf_get_ranges(dbg, v_off, &ranges, &nranges,
    155 			    &nbytes, &error) != DW_DLV_OK) {
    156 				warnx("%s", dwarf_errmsg(error));
    157 				goto cont;
    158 			}
    159 
    160 			res = dwarf_lowpc(die_root, &lowpc, &error);
    161 			if (res != DW_DLV_OK) {
    162 				warnx("%s", dwarf_errmsg(error));
    163 				goto cont;
    164 			}
    165 			base0 = lowpc;
    166 
    167 			naddrs = nranges - 1;
    168 			addr = emalloc(naddrs * sizeof(struct addr_pair));
    169 			for (i = 0; i < naddrs; i++) {
    170 				rp = &ranges[i];
    171 				if (rp->dwr_type == DW_RANGES_ADDRESS_SELECTION)
    172 					base0 = rp->dwr_addr2;
    173 				addr[i].lo = rp->dwr_addr1 + base0;
    174 				addr[i].hi = rp->dwr_addr2 + base0;
    175 			}
    176 			dwarf_ranges_dealloc(dbg, ranges, nranges);
    177 		} else {
    178 			/* DIE has high/low PC boundaries */
    179 			res = dwarf_lowpc(die, &lowpc, &error);
    180 			if (res != DW_DLV_OK) {
    181 				warnx("%s", dwarf_errmsg(error));
    182 				goto cont;
    183 			}
    184 			res = dwarf_highpc(die, &highpc, &error);
    185 			if (res != DW_DLV_OK) {
    186 				warnx("%s", dwarf_errmsg(error));
    187 				goto cont;
    188 			}
    189 			naddrs = 1;
    190 			addr = emalloc(sizeof(struct addr_pair));
    191 			addr[0].lo = lowpc;
    192 			addr[0].hi = lowpc + highpc;
    193 		}
    194 	} else
    195 		goto cont;
    196 
    197 	/* Get file:line */
    198 	attr = (flag == F_SUBPROGRAM) ? DW_AT_decl_file : DW_AT_call_file;
    199 	res = dwarf_attr(die, attr, &attp, &error);
    200 	if (res != DW_DLV_OK) {
    201 		if (res == DW_DLV_ERROR)
    202 			warnx("%s", dwarf_errmsg(error));
    203 		goto skip;
    204 	}
    205 	if (dwarf_formudata(attp, &v_udata, &error) != DW_DLV_OK) {
    206 		warnx("%s", dwarf_errmsg(error));
    207 		goto cont;
    208 	}
    209 	file = srcfiles[v_udata - 1];
    210 
    211 	attr = (flag == F_SUBPROGRAM) ? DW_AT_decl_line: DW_AT_call_line;
    212 	res = dwarf_attr(die, attr, &attp, &error);
    213 	if (res != DW_DLV_OK) {
    214 		if (res == DW_DLV_ERROR)
    215 			warnx("%s", dwarf_errmsg(error));
    216 		goto skip;
    217 	}
    218 	if (dwarf_formudata(attp, &line, &error) != DW_DLV_OK) {
    219 		warnx("%s", dwarf_errmsg(error));
    220 		goto cont;
    221 	}
    222 skip:
    223 	e = emalloc(sizeof(struct entry));
    224 	e->flag = flag;
    225 	if (file != NULL) {
    226 		e->file = file;
    227 		e->line = line;
    228 	} else
    229 		e->file = NULL;
    230 	if (e->flag == F_INLINE_COPY) {
    231 		e->naddrs = naddrs;
    232 		e->addr = addr;
    233 	}
    234 	TAILQ_INSERT_TAIL(&head, e, next);
    235 cont:
    236 	/*
    237 	 * Inline copies might appear before the declaration, so we need to
    238 	 * re-parse the CU.
    239 	 *
    240 	 * The rationale for choosing to re-parse the CU instead of using a
    241 	 * hash table of DIEs is that, because we re-parse only when an inline
    242 	 * definition of the function we want is found, statistically, we won't
    243 	 * have to re-parse many times at all considering that only a handful
    244 	 * of CUs will define the same function, whereas if we have used a hash
    245 	 * table, we would first need to parse the whole CU at once and store
    246 	 * all DW_TAG_inlined_subroutine DIEs (so that we can match them
    247 	 * afterwards). In this case, we always have to "parse" twice -- first
    248 	 * the CU, then the DIE table -- and also, the program would use much
    249 	 * more memory since we would have allocated DIEs, which most of them
    250 	 * would never be used.
    251 	 */
    252 	if (found) {
    253 		die = die_root;
    254 		level = 0;
    255 		/*
    256 		 * We'll be checking against the DIE offset of the definition
    257 		 * to determine if the inline copy's DW_AT_abstract_origin
    258 		 * points to it.
    259 		 */
    260 		g_dieoff = dieoff;
    261 		flag = F_INLINE_COPY;
    262 	}
    263 
    264 	res = dwarf_child(die, &die_next, &error);
    265 	if (res == DW_DLV_ERROR)
    266 		warnx("%s", dwarf_errmsg(error));
    267 	else if (res == DW_DLV_OK)
    268 		parse_die(dbg, die_next, level + 1, flag);
    269 
    270 	res = dwarf_siblingof(dbg, die, &die_next, &error);
    271 	if (res == DW_DLV_ERROR)
    272 		warnx("%s", dwarf_errmsg(error));
    273 	else if (res == DW_DLV_OK)
    274 		parse_die(dbg, die_next, level, flag);
    275 
    276 	/*
    277 	 * Deallocating on level 0 will attempt to double-free, since die_root
    278 	 * points to the first DIE. We'll deallocate the root DIE in main().
    279 	 */
    280 	if (level > 0)
    281 		dwarf_dealloc(dbg, die, DW_DLA_DIE);
    282 }
    283 
    284 static const char *
    285 find_caller_func(struct addr_pair addr)
    286 {
    287 	Elf_Data *d;
    288 	GElf_Sym sym;
    289 	struct section *s;
    290 	const char *name;
    291 	uint64_t lo, hi;
    292 	uint32_t stab;
    293 	int len, i, j;
    294 
    295 	for (i = 0; i < ei.shnum; i++) {
    296 		s = &ei.sl[i];
    297 		if (s->sh.sh_type != SHT_SYMTAB && s->sh.sh_type != SHT_DYNSYM)
    298 			continue;
    299 		if (s->sh.sh_link >= ei.shnum)
    300 			continue;
    301 		stab = s->sh.sh_link;
    302 		len = (int)(s->sh.sh_size / s->sh.sh_entsize);
    303 		(void)elf_errno();
    304 		if ((d = elf_getdata(s->scn, NULL)) == NULL) {
    305 			if (elf_errno() != 0)
    306 				warnx("elf_getdata(): %s", elf_errmsg(-1));
    307 			continue;
    308 		}
    309 		if (d->d_size <= 0)
    310 			continue;
    311 		if (s->sh.sh_entsize == 0)
    312 			continue;
    313 		else if (len > INT_MAX)
    314 			continue;
    315 		for (j = 0; j < len; j++) {
    316 			if (gelf_getsym(d, j, &sym) != &sym) {
    317 				warnx("gelf_getsym(): %s", elf_errmsg(-1));
    318 				continue;
    319 			}
    320 			if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
    321 				continue;
    322 			lo = sym.st_value;
    323 			hi = sym.st_value + sym.st_size;
    324 			if (addr.lo < lo || addr.hi > hi)
    325 				continue;
    326 			if ((name = elf_strptr(ei.elf, stab, sym.st_name)) != NULL)
    327 				return (name);
    328 		}
    329 	}
    330 
    331 	return (NULL);
    332 }
    333 
    334 static void
    335 dump_results(void)
    336 {
    337 	struct entry *e;
    338 	const char *str;
    339 	int i;
    340 
    341 	/* Clean up as well */
    342 	while (!TAILQ_EMPTY(&head)) {
    343 		e = TAILQ_FIRST(&head);
    344 		TAILQ_REMOVE(&head, e, next);
    345 		if (e->flag == F_INLINE_COPY) {
    346 			for (i = 0; i < e->naddrs; i++) {
    347 				printf("\t[0x%jx - 0x%jx]", e->addr[i].lo,
    348 				    e->addr[i].hi);
    349 				if (e->file != NULL)
    350 					printf("\t%s:%lu", e->file, e->line);
    351 				str = find_caller_func(e->addr[i]);
    352 				if (str != NULL)
    353 					printf("\t%s()\n", str);
    354 				else
    355 					putchar('\n');
    356 			}
    357 			free(e->addr);
    358 		} else if (e->flag == F_SUBPROGRAM) {
    359 			printf("%s:%lu\n", e->file, e->line);
    360 		}
    361 		free(e);
    362 	}
    363 }
    364 
    365 int
    366 main(int argc, char *argv[])
    367 {
    368 	Elf_Scn *scn;
    369 	GElf_Shdr sh;
    370 	Dwarf_Debug dbg;
    371 	Dwarf_Die die;
    372 	Dwarf_Signed nfiles;
    373 	Dwarf_Error error;
    374 	struct section *s;
    375 	char *file;
    376 	size_t shstrndx, ndx;
    377 	int fd, res = DW_DLV_OK;
    378 
    379 	if (argc < 3) {
    380 		fprintf(stderr, "usage: %s function debug_file\n", *argv);
    381 		return (1);
    382 	}
    383 	func = argv[1];
    384 	file = argv[2];
    385 
    386 	if (elf_version(EV_CURRENT) == EV_NONE)
    387 		errx(1, "elf_version(): %s", elf_errmsg(-1));
    388 	if ((fd = open(file, O_RDONLY)) < 0)
    389 		err(1, "open(%s)", file);
    390 	if ((ei.elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
    391 		errx(1, "elf_begin(): %s", elf_errmsg(-1));
    392 	if (elf_kind(ei.elf) == ELF_K_NONE)
    393 		errx(1, "not an ELF file: %s", file);
    394 	if (dwarf_elf_init(ei.elf, DW_DLC_READ, NULL, NULL, &dbg, &error) !=
    395 	    DW_DLV_OK)
    396 		errx(1, "dwarf_elf_init(): %s", dwarf_errmsg(error));
    397 
    398 	/* Load ELF sections */
    399 	if (!elf_getshnum(ei.elf, &ei.shnum))
    400 		errx(1, "elf_getshnum(): %s", elf_errmsg(-1));
    401 	if ((ei.sl = calloc(ei.shnum, sizeof(struct section))) == NULL)
    402 		err(1, "calloc");
    403 	if (!elf_getshstrndx(ei.elf, &shstrndx))
    404 		errx(1, "elf_getshstrndx(): %s", elf_errmsg(-1));
    405 	if ((scn = elf_getscn(ei.elf, 0)) == NULL)
    406 		err(1, "elf_getscn(): %s", elf_errmsg(-1));
    407 	(void)elf_errno();
    408 
    409 	do {
    410 		if (gelf_getshdr(scn, &sh) == NULL) {
    411 			warnx("gelf_getshdr(): %s", elf_errmsg(-1));
    412 			(void)elf_errno();
    413 			continue;
    414 		}
    415 		if ((ndx = elf_ndxscn(scn)) == SHN_UNDEF && elf_errno() != 0) {
    416 			warnx("elf_ndxscn(): %s", elf_errmsg(-1));
    417 			continue;
    418 		}
    419 		if (ndx >= ei.shnum)
    420 			continue;
    421 		s = &ei.sl[ndx];
    422 		s->scn = scn;
    423 		s->sh = sh;
    424 	} while ((scn = elf_nextscn(ei.elf, scn)) != NULL);
    425 	if (elf_errno() != 0)
    426 		warnx("elf_nextscn(): %s", elf_errmsg(-1));
    427 
    428 	while ((res = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL,
    429 	    &error)) == DW_DLV_OK) {
    430 		die = NULL;
    431 		TAILQ_INIT(&head);
    432 		while (dwarf_siblingof(dbg, die, &die, &error) == DW_DLV_OK) {
    433 			srcfiles = NULL;
    434 			if (dwarf_srcfiles(die, &srcfiles, &nfiles, &error) !=
    435 			    DW_DLV_OK)
    436 				warnx("%s", dwarf_errmsg(error));
    437 			parse_die(dbg, die, 0, F_SUBPROGRAM);
    438 		}
    439 		dwarf_dealloc(dbg, die, DW_DLA_DIE);
    440 		dump_results();
    441 	}
    442 	if (res == DW_DLV_ERROR)
    443 		warnx("%s", dwarf_errmsg(error));
    444 
    445 	free(ei.sl);
    446 	elf_end(ei.elf);
    447 	dwarf_finish(dbg, &error);
    448 	close(fd);
    449 
    450 	return (0);
    451 }