~crm/inlinecall

61441c99bb7a2abbdc914c37562d2806b892f218 — Christos Margiolis 7 months ago
initial commit
4 files changed, 361 insertions(+), 0 deletions(-)

A Makefile
A README
A inlinecall.1
A inlinecall.c
A  => Makefile +8 -0
@@ 1,8 @@
# $FreeBSD$

PROG=		inlinecall
SRCS=		${PROG}.c
MAN=		${PROG}.1
LDFLAGS+=	-ldwarf -lelf

.include <bsd.prog.mk>

A  => README +7 -0
@@ 1,7 @@
inlinecall
==========

inlinecall prints all call sites of an inline function using libdwarf.

It works as-is on FreeBSD and most likely needs some modification to get it to
work on other platforms.

A  => inlinecall.1 +37 -0
@@ 1,37 @@
.Dd February 07, 2023
.Dt INLINECALL 1
.Os
.Sh NAME
.Nm inlinecall
.Nd print call sites of an inline function
.Sh SYNOPSIS
.Nm
.Ar function
.Ar file 
.Sh DESCRIPTION
.Pp
.Nm
finds the call sites of
.Ar function
(if it's an inline function) in
.Ar file
and outputs the results in the following format:
.Bd -literal -offset indent
cu1_func_declaration_file:line
	[low_bound - high_bound]	inline_copy1_file:line
	[low_bound - high_bound]	inline_copy2_file:line
	...
cu2_func_declaration_file:line
	...
.Ed
.Pp
If
.Ar file:line
is missing, that means the inline copy's DIE did not have the
.Ar DW_TAG_call_file
tag set.
.Sh SEE ALSO
.Xr dwarf 3 ,
.Xr elf 3
.Sh AUTHORS
.An Christos Margiolis Aq Mt christos@FreeBSD.org

A  => inlinecall.c +309 -0
@@ 1,309 @@
#include <dwarf.h>
#include <err.h>
#include <fcntl.h>
#include <libdwarf.h>
#include <libelf.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/*
 * TODO
 * make srcfiles global?
 * pack DIE info into a struct?
 */

enum {
	F_SUBPROGRAM,
	F_INLINE_COPY,
};

static void
print_info(Dwarf_Debug dbg, Dwarf_Die die_root, Dwarf_Die die,
    Dwarf_Off dieoff, char **srcfiles, int flag)
{
	Dwarf_Ranges *ranges, *rp;
	Dwarf_Attribute attp;
	Dwarf_Addr v_addr;
	Dwarf_Off v_off;
	Dwarf_Unsigned v_udata, line, nbytes;
	Dwarf_Signed nranges;
	Dwarf_Half attr;
	Dwarf_Bool v_flag;
	Dwarf_Error error;
	char *file = NULL;
	int res, i;

	attr = (flag == F_SUBPROGRAM) ? DW_AT_decl_file : DW_AT_call_file;
	res = dwarf_attr(die, attr, &attp, &error);
	if (res != DW_DLV_OK) {
		if (res == DW_DLV_ERROR)
			warnx("%s", dwarf_errmsg(error));
		goto skip;
	}
	if (dwarf_formudata(attp, &v_udata, &error) != DW_DLV_OK) {
		warnx("%s", dwarf_errmsg(error));
		return;
	}
	file = srcfiles[v_udata - 1];

	attr = (flag == F_SUBPROGRAM) ? DW_AT_decl_line: DW_AT_call_line;
	res = dwarf_attr(die, attr, &attp, &error);
	if (res != DW_DLV_OK) {
		if (res == DW_DLV_ERROR)
			warnx("%s", dwarf_errmsg(error));
		goto skip;
	}
	if (dwarf_formudata(attp, &line, &error) != DW_DLV_OK) {
		warnx("%s", dwarf_errmsg(error));
		return;
	}

skip:
	if (flag == F_INLINE_COPY) {
		if (dwarf_hasattr(die, DW_AT_ranges, &v_flag, &error) !=
		    DW_DLV_OK) {
			warnx("%s", dwarf_errmsg(error));
			return;
		}
		if (v_flag) {
			/* DIE has ranges */
			res = dwarf_attr(die, DW_AT_ranges, &attp, &error);
			if (res != DW_DLV_OK) {
				if (res == DW_DLV_ERROR)
					warnx("%s", dwarf_errmsg(error));
				return;
			}
			if (dwarf_global_formref(attp, &v_off, &error) !=
			    DW_DLV_OK) {
				warnx("%s", dwarf_errmsg(error));
				return;
			}
			if (dwarf_get_ranges(dbg, v_off, &ranges, &nranges,
			    &nbytes, &error) != DW_DLV_OK) {
				warnx("%s", dwarf_errmsg(error));
				return;
			}
			for (i = 0; i < nranges - 1; i++) {
				rp = &ranges[i];
				res = dwarf_attr(die_root, DW_AT_low_pc, &attp,
				    &error);
				if (res != DW_DLV_OK) {
					if (res == DW_DLV_ERROR)
						warnx("%s", dwarf_errmsg(error));
					break;
				}
				if (dwarf_formaddr(attp, &v_addr, &error) !=
				    DW_DLV_OK) {
					warnx("%s", dwarf_errmsg(error));
					break;
				}
				printf("\t[0x%jx - 0x%jx]",
				    v_addr + rp->dwr_addr1,
				    v_addr + rp->dwr_addr2);
				if (file != NULL)
					printf("\t%s:%lu\n", file, line);
			}
			dwarf_ranges_dealloc(dbg, ranges, nranges);
		} else {
			/* DIE has high/low PC boundaries */
			res = dwarf_attr(die, DW_AT_low_pc, &attp, &error);
			if (res != DW_DLV_OK) {
				if (res == DW_DLV_ERROR)
					warnx("%s", dwarf_errmsg(error));
				return;
			}
			if (dwarf_formaddr(attp, &v_addr, &error) != DW_DLV_OK) {
				warnx("%s", dwarf_errmsg(error));
				return;
			}
			res = dwarf_attr(die, DW_AT_high_pc, &attp, &error);
			if (res != DW_DLV_OK) {
				if (res == DW_DLV_ERROR)
					warnx("%s", dwarf_errmsg(error));
				return;
			}
			if (dwarf_formudata(attp, &v_udata, &error) !=
			    DW_DLV_OK) {
				warnx("%s", dwarf_errmsg(error));
				return;
			}
			printf("\t[0x%jx - 0x%jx]", v_addr, v_addr + v_udata);
			if (file != NULL)
				printf("\t%s:%lu\n", file, line);
			else
				putchar('\n');
		}
	} else {
		printf("%s:%lu\n", file, line);
	}
}

static void
parse_die(Dwarf_Debug dbg, Dwarf_Die die, char **srcfiles, void *data,
    int level, int flag)
{
	static Dwarf_Die die_root;
	Dwarf_Die die_next;
	Dwarf_Attribute attp, *attr_list;
	Dwarf_Off dieoff, cuoff, culen, v_off;
	Dwarf_Addr v_addr;
	Dwarf_Unsigned v_udata;
	Dwarf_Signed nattr, v_sdata;
	Dwarf_Half tag, attr, form;
	Dwarf_Bool v_flag;
	Dwarf_Error error;
	const char *str;
	char *v_str;
	int res, i, found = 0;

	/* Save the root DIE so that we can re-parse it. */
	if (level == 0)
		die_root = die;

	if (dwarf_dieoffset(die, &dieoff, &error) != DW_DLV_OK) {
		warnx("%s", dwarf_errmsg(error));
		goto cont;
	}
	if (dwarf_die_CU_offset_range(die, &cuoff, &culen, &error) != DW_DLV_OK) {
		warnx("%s", dwarf_errmsg(error));
		cuoff = 0;
	}
	if (dwarf_tag(die, &tag, &error) != DW_DLV_OK) {
		warnx("%s", dwarf_errmsg(error));
		goto cont;
	}
	if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
		goto cont;
	if (flag == F_SUBPROGRAM && tag == DW_TAG_subprogram) {
		if (dwarf_hasattr(die, DW_AT_inline, &v_flag, &error) !=
		    DW_DLV_OK) {
			warnx("%s", dwarf_errmsg(error));
			goto cont;
		}
		if (!v_flag)
			goto cont;
		res = dwarf_attr(die, DW_AT_name, &attp, &error);
		if (res != DW_DLV_OK) {
			if (res == DW_DLV_ERROR)
				warnx("%s", dwarf_errmsg(error));
			goto cont;
		}
		if (dwarf_formstring(attp, &v_str, &error) != DW_DLV_OK) {
			warnx("%s", dwarf_errmsg(error));
			goto cont;
		}
		if (strcmp(v_str, (char *)data) != 0)
			goto cont;
		found = 1;
	} else if (flag == F_INLINE_COPY) {
		/*
		 * XXX I'm not checking against DW_TAG_inlined_subroutine since
		 * since I'm not sure whether we can have DW_TAG_subprogram
		 * also work as an inline copy. An example of this is <0x1004>.
		 */
		res = dwarf_attr(die, DW_AT_abstract_origin, &attp, &error);
		if (res != DW_DLV_OK) {
			if (res == DW_DLV_ERROR)
				warnx("%s", dwarf_errmsg(error));
			goto cont;
		}
		if (dwarf_formref(attp, &v_off, &error) != DW_DLV_OK) {
			warnx("%s", dwarf_errmsg(error));
			goto cont;
		}
		v_off += cuoff;
		if (v_off != (Dwarf_Off)data)
			goto cont;
	} else
		goto cont;
	print_info(dbg, die_root, die, dieoff, srcfiles, flag);
cont:
	/*
	 * Inline copies might appear before the declaration, so we need to
	 * re-parse the CU.
	 */
	if (found) {
		die = die_root;
		data = (void *)dieoff;
		level = 0;
		flag = F_INLINE_COPY;
	}
	res = dwarf_child(die, &die_next, &error);
	if (res == DW_DLV_ERROR)
		warnx("%s", dwarf_errmsg(error));
	else if (res == DW_DLV_OK)
		parse_die(dbg, die_next, srcfiles, data, level + 1, flag);

	res = dwarf_siblingof(dbg, die, &die_next, &error);
	if (res == DW_DLV_ERROR)
		warnx("%s", dwarf_errmsg(error));
	else if (res == DW_DLV_OK)
		parse_die(dbg, die_next, srcfiles, data, level, flag);

	/*
	 * Deallocating on level 0 will attempt to double-free, since die_root
	 * points to the first DIE. We'll deallocate the root DIE in main().
	 */
	if (level > 0)
		dwarf_dealloc(dbg, die, DW_DLA_DIE);
}

int
main(int argc, char *argv[])
{
	Elf *elf;
	Dwarf_Debug dbg;
	Dwarf_Die die;
	Dwarf_Signed nfiles;
	Dwarf_Error error;
	char *func, *file;
	char **srcfiles;
	int fd;
	int res = DW_DLV_OK;

	if (argc < 3) {
		fprintf(stderr, "usage: %s function debug_file\n", *argv);
		return (1);
	}
	func = argv[1];
	file = argv[2];

	if (elf_version(EV_CURRENT) == EV_NONE)
		errx(1, "elf_version(): %s", elf_errmsg(-1));
	if ((fd = open(file, O_RDONLY)) < 0)
		err(1, "open(%s)", file);
	if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
		errx(1, "elf_begin(): %s", elf_errmsg(-1));
	if (elf_kind(elf) == ELF_K_NONE)
		errx(1, "not an ELF file: %s", file);
	res = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, &dbg, &error);
	if (res != DW_DLV_OK)
		errx(1, "dwarf_elf_init(): %s", dwarf_errmsg(error));

	do {
		while ((res = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL,
		    NULL, &error)) == DW_DLV_OK) {
			die = NULL;
			while (dwarf_siblingof(dbg, die, &die, &error) ==
			    DW_DLV_OK) {
				srcfiles = NULL;
				if (dwarf_srcfiles(die, &srcfiles, &nfiles,
				    &error) != DW_DLV_OK) {
					warnx("%s", dwarf_errmsg(error));
				}
				parse_die(dbg, die, srcfiles, func, 0,
				    F_SUBPROGRAM);
				dwarf_dealloc(dbg, die, DW_DLA_DIE);
			}
		}
		if (res == DW_DLV_ERROR)
			warnx("%s", dwarf_errmsg(error));
	} while (dwarf_next_types_section(dbg, &error) == DW_DLV_OK);

	dwarf_finish(dbg, &error);
	elf_end(elf);
	close(fd);

	return (0);
}