projects/linux·blob master · refs

242 lines · 5.8 KiB

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
12static 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
18void 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
28struct 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
73struct 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
82static 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
158abort_delete_sym:
159 if (symbol__inlined(inline_sym))
160 symbol__delete(inline_sym);
161abort_enomem:
162 args->err = -ENOMEM;
163 return DWARF_CB_ABORT;
164}
165
166int 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}