| // 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; | |
| } |