xref: /linux/tools/perf/util/libdw.c (revision 6cc3e0f659b890cfb4a8753eb0e31c871cc7555b)
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 <fcntl.h>
8 #include <unistd.h>
9 #include <elfutils/libdwfl.h>
10 
11 void dso__free_a2l_libdw(struct dso *dso)
12 {
13 	Dwfl *dwfl = dso__a2l_libdw(dso);
14 
15 	if (dwfl) {
16 		dwfl_end(dwfl);
17 		dso__set_a2l_libdw(dso, NULL);
18 	}
19 }
20 
21 struct libdw_a2l_cb_args {
22 	struct dso *dso;
23 	struct symbol *sym;
24 	struct inline_node *node;
25 	char *leaf_srcline;
26 	bool leaf_srcline_used;
27 };
28 
29 static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
30 {
31 	struct libdw_a2l_cb_args *args  = _args;
32 	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
33 	const char *call_fname = die_get_call_file(die);
34 	char *call_srcline = srcline__unknown;
35 	struct inline_list *ilist;
36 
37 	if (!inline_sym)
38 		return -ENOMEM;
39 
40 	/* Assign caller information to the parent. */
41 	if (call_fname)
42 		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
43 
44 	list_for_each_entry(ilist, &args->node->val, list) {
45 		if (args->leaf_srcline == ilist->srcline)
46 			args->leaf_srcline_used = false;
47 		else if (ilist->srcline != srcline__unknown)
48 			free(ilist->srcline);
49 		ilist->srcline =  call_srcline;
50 		call_srcline = NULL;
51 		break;
52 	}
53 	if (call_srcline && call_srcline != srcline__unknown)
54 		free(call_srcline);
55 
56 	/* Add this symbol to the chain as the leaf. */
57 	if (!args->leaf_srcline_used) {
58 		inline_list__append_tail(inline_sym, args->leaf_srcline, args->node);
59 		args->leaf_srcline_used = true;
60 	} else {
61 		inline_list__append_tail(inline_sym, strdup(args->leaf_srcline), args->node);
62 	}
63 	return 0;
64 }
65 
66 int libdw__addr2line(const char *dso_name, u64 addr,
67 		     char **file, unsigned int *line_nr,
68 		     struct dso *dso, bool unwind_inlines,
69 		     struct inline_node *node, struct symbol *sym)
70 {
71 	static const Dwfl_Callbacks offline_callbacks = {
72 		.find_debuginfo = dwfl_standard_find_debuginfo,
73 		.section_address = dwfl_offline_section_address,
74 		.find_elf = dwfl_build_id_find_elf,
75 	};
76 	Dwfl *dwfl = dso__a2l_libdw(dso);
77 	Dwfl_Module *mod;
78 	Dwfl_Line *dwline;
79 	Dwarf_Addr bias;
80 	const char *src;
81 	int lineno = 0;
82 
83 	if (!dwfl) {
84 		/*
85 		 * Initialize Dwfl session.
86 		 * We need to open the DSO file to report it to libdw.
87 		 */
88 		int fd;
89 
90 		fd = open(dso_name, O_RDONLY);
91 		if (fd < 0)
92 			return 0;
93 
94 		dwfl = dwfl_begin(&offline_callbacks);
95 		if (!dwfl) {
96 			close(fd);
97 			return 0;
98 		}
99 
100 		/*
101 		 * If the report is successful, the file descriptor fd is consumed
102 		 * and closed by the Dwfl. If not, it is not closed.
103 		 */
104 		mod = dwfl_report_offline(dwfl, dso_name, dso_name, fd);
105 		if (!mod) {
106 			dwfl_end(dwfl);
107 			close(fd);
108 			return 0;
109 		}
110 
111 		dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
112 		dso__set_a2l_libdw(dso, dwfl);
113 	} else {
114 		/* Dwfl session already initialized, get module for address. */
115 		mod = dwfl_addrmodule(dwfl, addr);
116 	}
117 
118 	if (!mod)
119 		return 0;
120 
121 	/*
122 	 * Get/ignore the dwarf information. Determine the bias, difference
123 	 * between the regular ELF addr2line addresses and those to use with
124 	 * libdw.
125 	 */
126 	if (!dwfl_module_getdwarf(mod, &bias))
127 		return 0;
128 
129 	/* Find source line information for the address. */
130 	dwline = dwfl_module_getsrc(mod, addr + bias);
131 	if (!dwline)
132 		return 0;
133 
134 	/* Get line information. */
135 	src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL,
136 			    /*length=*/NULL);
137 
138 	if (file)
139 		*file = src ? strdup(src) : NULL;
140 	if (line_nr)
141 		*line_nr = lineno;
142 
143 	/* Optionally unwind inline function call chain. */
144 	if (unwind_inlines && node) {
145 		Dwarf_Addr unused_bias;
146 		Dwarf_Die *cudie = dwfl_module_addrdie(mod, addr + bias, &unused_bias);
147 		struct libdw_a2l_cb_args args = {
148 			.dso = dso,
149 			.sym = sym,
150 			.node = node,
151 			.leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno),
152 		};
153 
154 		/* Walk from the parent down to the leaf. */
155 		cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
156 
157 		if (!args.leaf_srcline_used)
158 			free(args.leaf_srcline);
159 	}
160 	return 1;
161 }
162