242 lines · 5.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#include "dso.h"#include "libdw.h"#include "srcline.h"#include "symbol.h"#include "dwarf-aux.h"#include "callchain.h"#include <fcntl.h>#include <unistd.h>#include <elfutils/libdwfl.h> static const Dwfl_Callbacks offline_callbacks = {	.find_debuginfo = dwfl_standard_find_debuginfo,	.section_address = dwfl_offline_section_address,	.find_elf = dwfl_build_id_find_elf,}; void dso__free_libdw(struct dso *dso){	Dwfl *dwfl = dso__libdw(dso); 	if (dwfl) {		dwfl_end(dwfl);		dso__set_libdw(dso, NULL);	}} struct Dwfl *dso__libdw_dwfl(struct dso *dso){	Dwfl *dwfl = dso__libdw(dso);	const char *dso_name;	Dwfl_Module *mod;	int fd; 	if (dwfl)		return dwfl; 	dso_name = dso__long_name(dso);	/*	 * Initialize Dwfl session.	 * We need to open the DSO file to report it to libdw.	 */	fd = open(dso_name, O_RDONLY);	if (fd < 0)		return NULL; 	dwfl = dwfl_begin(&offline_callbacks);	if (!dwfl) {		close(fd);		return NULL;	} 	/*	 * If the report is successful, the file descriptor fd is consumed	 * and closed by the Dwfl. If not, it is not closed.	 */	mod = dwfl_report_offline(dwfl, dso_name, dso_name, fd);	if (!mod) {		dwfl_end(dwfl);		close(fd);		return NULL;	} 	if (dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL) != 0) {		dwfl_end(dwfl);		return NULL;	}	dso__set_libdw(dso, dwfl); 	return dwfl;} struct libdw_a2l_cb_args {	struct dso *dso;	struct symbol *sym;	struct inline_node *node;	char *leaf_srcline;	bool leaf_srcline_used;	int err;}; static int libdw_a2l_cb(Dwarf_Die *die, void *_args){	struct libdw_a2l_cb_args *args  = _args;	const char *call_fname = die_get_call_file(die);	int call_lineno = die_get_call_lineno(die);	char *call_srcline = srcline__unknown;	struct symbol *inline_sym; 	if (dwarf_tag(die) == DW_TAG_subprogram && args->sym) {		/*		 * cu_walk_functions_at() opens the walk with the		 * containing DW_TAG_subprogram DIE (the non-inlined outer		 * function). That's just the base symbol -- use it		 * directly. Avoids a fragile name-vs-name compare in		 * new_inline_sym() that misfires when GCC IPA passes		 * (.isra/.constprop/.part/.cold) rename the ELF symbol		 * while DWARF keeps the pre-clone linkage name, which		 * left the outer frame spuriously tagged "(inlined)".		 */		inline_sym = args->sym;	} else {		/*		 * Prefer DW_AT_linkage_name so C++ inline frames keep		 * their namespace/class qualification. new_inline_sym()		 * runs the name through dso__demangle_sym(), so the		 * mangled linkage name is turned back into		 * "Namespace::Class::method". Fall back to DW_AT_name		 * (unqualified) when no linkage name is present, e.g.		 * for C code or extern "C" functions.		 */		const char *funcname = die_get_linkage_name(die) ?: die_name(die); 		inline_sym = new_inline_sym(args->dso, args->sym, funcname);		if (!inline_sym)			goto abort_enomem;	} 	/* Assign caller information to the parent. */	if (call_fname)		call_srcline = srcline_from_fileline(call_fname, call_lineno >= 0 ? call_lineno : 0); 	if (!list_empty(&args->node->val)) {		struct inline_list *parent; 		if (callchain_param.order == ORDER_CALLEE)			parent = list_first_entry(&args->node->val, struct inline_list, list);		else			parent = list_last_entry(&args->node->val, struct inline_list, list); 		if (args->leaf_srcline == parent->srcline)			args->leaf_srcline_used = false;		else if (parent->srcline != srcline__unknown)			free(parent->srcline);		parent->srcline = call_srcline;		call_srcline = NULL;	}	if (call_srcline && call_srcline != srcline__unknown)		free(call_srcline); 	/* Add this symbol to the chain as the leaf. */	if (!args->leaf_srcline_used) {		if (inline_list__append_tail(inline_sym, args->leaf_srcline, args->node) != 0)			goto abort_delete_sym;		args->leaf_srcline_used = true;	} else {		char *srcline = strdup(args->leaf_srcline); 		if (!srcline)			goto abort_delete_sym;		if (inline_list__append_tail(inline_sym, srcline, args->node) != 0) {			free(srcline);			goto abort_delete_sym;		}	}	return 0; abort_delete_sym:	if (symbol__inlined(inline_sym))		symbol__delete(inline_sym);abort_enomem:	args->err = -ENOMEM;	return DWARF_CB_ABORT;} int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,		     struct dso *dso, bool unwind_inlines,		     struct inline_node *node, struct symbol *sym){	Dwfl *dwfl = dso__libdw_dwfl(dso);	Dwfl_Module *mod;	Dwfl_Line *dwline;	Dwarf_Addr bias;	const char *src;	int lineno = 0; 	if (!dwfl)		return 0; 	mod = dwfl_addrmodule(dwfl, addr);	if (!mod)		return 0; 	/*	 * Get/ignore the dwarf information. Determine the bias, difference	 * between the regular ELF addr2line addresses and those to use with	 * libdw.	 */	if (!dwfl_module_getdwarf(mod, &bias))		return 0; 	/* Find source line information for the address. */	dwline = dwfl_module_getsrc(mod, addr + bias);	if (!dwline)		return 0; 	/* Get line information. */	src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL,			    /*length=*/NULL); 	if (file)		*file = src ? strdup(src) : NULL;	if (line_nr)		*line_nr = lineno; 	/* Optionally unwind inline function call chain. */	if (unwind_inlines && node) {		Dwarf_Addr unused_bias;		Dwarf_Die *cudie = dwfl_module_addrdie(mod, addr + bias, &unused_bias);		struct libdw_a2l_cb_args args = {			.dso = dso,			.sym = sym,			.node = node,			.leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno),		}; 		if (!args.leaf_srcline) {			if (file && *file) {				free(*file);				*file = NULL;			}			return 0;		} 		/* Walk from the parent down to the leaf. */		if (cudie)			cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args); 		if (!args.leaf_srcline_used)			free(args.leaf_srcline); 		if (args.err) {			if (file && *file) {				free(*file);				*file = NULL;			}			inline_node__clear_frames(node);			return 0;		}	}	return 1;}