1 // SPDX-License-Identifier: GPL-2.0 2 #include "dso.h" 3 #include "libdw.h" 4 #include "srcline.h" 5 #include "symbol.h" 6 #include "dwarf-aux.h" 7 #include "callchain.h" 8 #include <fcntl.h> 9 #include <unistd.h> 10 #include <elfutils/libdwfl.h> 11 12 static const Dwfl_Callbacks offline_callbacks = { 13 .find_debuginfo = dwfl_standard_find_debuginfo, 14 .section_address = dwfl_offline_section_address, 15 .find_elf = dwfl_build_id_find_elf, 16 }; 17 18 void dso__free_libdw(struct dso *dso) 19 { 20 Dwfl *dwfl = dso__libdw(dso); 21 22 if (dwfl) { 23 dwfl_end(dwfl); 24 dso__set_libdw(dso, NULL); 25 } 26 } 27 28 struct Dwfl *dso__libdw_dwfl(struct dso *dso) 29 { 30 Dwfl *dwfl = dso__libdw(dso); 31 const char *dso_name; 32 Dwfl_Module *mod; 33 int fd; 34 35 if (dwfl) 36 return dwfl; 37 38 dso_name = dso__long_name(dso); 39 /* 40 * Initialize Dwfl session. 41 * We need to open the DSO file to report it to libdw. 42 */ 43 fd = open(dso_name, O_RDONLY); 44 if (fd < 0) 45 return NULL; 46 47 dwfl = dwfl_begin(&offline_callbacks); 48 if (!dwfl) { 49 close(fd); 50 return NULL; 51 } 52 53 /* 54 * If the report is successful, the file descriptor fd is consumed 55 * and closed by the Dwfl. If not, it is not closed. 56 */ 57 mod = dwfl_report_offline(dwfl, dso_name, dso_name, fd); 58 if (!mod) { 59 dwfl_end(dwfl); 60 close(fd); 61 return NULL; 62 } 63 64 if (dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL) != 0) { 65 dwfl_end(dwfl); 66 return NULL; 67 } 68 dso__set_libdw(dso, dwfl); 69 70 return dwfl; 71 } 72 73 struct libdw_a2l_cb_args { 74 struct dso *dso; 75 struct symbol *sym; 76 struct inline_node *node; 77 char *leaf_srcline; 78 bool leaf_srcline_used; 79 int err; 80 }; 81 82 static int libdw_a2l_cb(Dwarf_Die *die, void *_args) 83 { 84 struct libdw_a2l_cb_args *args = _args; 85 const char *call_fname = die_get_call_file(die); 86 int call_lineno = die_get_call_lineno(die); 87 char *call_srcline = srcline__unknown; 88 struct symbol *inline_sym; 89 90 if (dwarf_tag(die) == DW_TAG_subprogram && args->sym) { 91 /* 92 * cu_walk_functions_at() opens the walk with the 93 * containing DW_TAG_subprogram DIE (the non-inlined outer 94 * function). That's just the base symbol -- use it 95 * directly. Avoids a fragile name-vs-name compare in 96 * new_inline_sym() that misfires when GCC IPA passes 97 * (.isra/.constprop/.part/.cold) rename the ELF symbol 98 * while DWARF keeps the pre-clone linkage name, which 99 * left the outer frame spuriously tagged "(inlined)". 100 */ 101 inline_sym = args->sym; 102 } else { 103 /* 104 * Prefer DW_AT_linkage_name so C++ inline frames keep 105 * their namespace/class qualification. new_inline_sym() 106 * runs the name through dso__demangle_sym(), so the 107 * mangled linkage name is turned back into 108 * "Namespace::Class::method". Fall back to DW_AT_name 109 * (unqualified) when no linkage name is present, e.g. 110 * for C code or extern "C" functions. 111 */ 112 const char *funcname = die_get_linkage_name(die) ?: die_name(die); 113 114 inline_sym = new_inline_sym(args->dso, args->sym, funcname); 115 if (!inline_sym) 116 goto abort_enomem; 117 } 118 119 /* Assign caller information to the parent. */ 120 if (call_fname) 121 call_srcline = srcline_from_fileline(call_fname, call_lineno >= 0 ? call_lineno : 0); 122 123 if (!list_empty(&args->node->val)) { 124 struct inline_list *parent; 125 126 if (callchain_param.order == ORDER_CALLEE) 127 parent = list_first_entry(&args->node->val, struct inline_list, list); 128 else 129 parent = list_last_entry(&args->node->val, struct inline_list, list); 130 131 if (args->leaf_srcline == parent->srcline) 132 args->leaf_srcline_used = false; 133 else if (parent->srcline != srcline__unknown) 134 free(parent->srcline); 135 parent->srcline = call_srcline; 136 call_srcline = NULL; 137 } 138 if (call_srcline && call_srcline != srcline__unknown) 139 free(call_srcline); 140 141 /* Add this symbol to the chain as the leaf. */ 142 if (!args->leaf_srcline_used) { 143 if (inline_list__append_tail(inline_sym, args->leaf_srcline, args->node) != 0) 144 goto abort_delete_sym; 145 args->leaf_srcline_used = true; 146 } else { 147 char *srcline = strdup(args->leaf_srcline); 148 149 if (!srcline) 150 goto abort_delete_sym; 151 if (inline_list__append_tail(inline_sym, srcline, args->node) != 0) { 152 free(srcline); 153 goto abort_delete_sym; 154 } 155 } 156 return 0; 157 158 abort_delete_sym: 159 if (symbol__inlined(inline_sym)) 160 symbol__delete(inline_sym); 161 abort_enomem: 162 args->err = -ENOMEM; 163 return DWARF_CB_ABORT; 164 } 165 166 int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, 167 struct dso *dso, bool unwind_inlines, 168 struct inline_node *node, struct symbol *sym) 169 { 170 Dwfl *dwfl = dso__libdw_dwfl(dso); 171 Dwfl_Module *mod; 172 Dwfl_Line *dwline; 173 Dwarf_Addr bias; 174 const char *src; 175 int lineno = 0; 176 177 if (!dwfl) 178 return 0; 179 180 mod = dwfl_addrmodule(dwfl, addr); 181 if (!mod) 182 return 0; 183 184 /* 185 * Get/ignore the dwarf information. Determine the bias, difference 186 * between the regular ELF addr2line addresses and those to use with 187 * libdw. 188 */ 189 if (!dwfl_module_getdwarf(mod, &bias)) 190 return 0; 191 192 /* Find source line information for the address. */ 193 dwline = dwfl_module_getsrc(mod, addr + bias); 194 if (!dwline) 195 return 0; 196 197 /* Get line information. */ 198 src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL, 199 /*length=*/NULL); 200 201 if (file) 202 *file = src ? strdup(src) : NULL; 203 if (line_nr) 204 *line_nr = lineno; 205 206 /* Optionally unwind inline function call chain. */ 207 if (unwind_inlines && node) { 208 Dwarf_Addr unused_bias; 209 Dwarf_Die *cudie = dwfl_module_addrdie(mod, addr + bias, &unused_bias); 210 struct libdw_a2l_cb_args args = { 211 .dso = dso, 212 .sym = sym, 213 .node = node, 214 .leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno), 215 }; 216 217 if (!args.leaf_srcline) { 218 if (file && *file) { 219 free(*file); 220 *file = NULL; 221 } 222 return 0; 223 } 224 225 /* Walk from the parent down to the leaf. */ 226 if (cudie) 227 cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args); 228 229 if (!args.leaf_srcline_used) 230 free(args.leaf_srcline); 231 232 if (args.err) { 233 if (file && *file) { 234 free(*file); 235 *file = NULL; 236 } 237 inline_node__clear_frames(node); 238 return 0; 239 } 240 } 241 return 1; 242 } 243