xref: /linux/tools/perf/util/libunwind-arch/libunwind-x86_64.c (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1 // SPDX-License-Identifier: GPL-2.0
2 #include "libunwind-arch.h"
3 #include "../debug.h"
4 #include "../maps.h"
5 #include "../thread.h"
6 #include "../../../arch/x86/include/uapi/asm/perf_regs.h"
7 #include <linux/compiler.h>
8 #include <linux/kernel.h>
9 #include <linux/zalloc.h>
10 #include <elf.h>
11 #include <errno.h>
12 
13 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
14 #include <libunwind-x86_64.h>
15 #endif
16 
17 int __get_perf_regnum_for_unw_regnum_x86_64(int unw_regnum __maybe_unused)
18 {
19 #ifndef HAVE_LIBUNWIND_X86_64_SUPPORT
20 	return -EINVAL;
21 #else
22 	static const int perf_x86_64_regnums[] = {
23 #define REGNUM(reg) [UNW_X86_64_R ## reg] = PERF_REG_X86_ ## reg
24 		REGNUM(AX),
25 		REGNUM(DX),
26 		REGNUM(CX),
27 		REGNUM(BX),
28 		REGNUM(SI),
29 		REGNUM(DI),
30 		REGNUM(BP),
31 		REGNUM(SP),
32 		REGNUM(IP),
33 #undef REGNUM
34 #define REGNUM(reg) [UNW_X86_64_ ## reg] = PERF_REG_X86_ ## reg
35 		REGNUM(R8),
36 		REGNUM(R9),
37 		REGNUM(R10),
38 		REGNUM(R11),
39 		REGNUM(R12),
40 		REGNUM(R13),
41 		REGNUM(R14),
42 		REGNUM(R15),
43 #undef REGNUM
44 	};
45 
46 	if (unw_regnum == UNW_X86_64_RAX)
47 		return PERF_REG_X86_AX;
48 
49 	if (unw_regnum <  0 || unw_regnum >= (int)ARRAY_SIZE(perf_x86_64_regnums) ||
50             perf_x86_64_regnums[unw_regnum] == 0) {
51 		pr_err("unwind: invalid reg id %d\n", unw_regnum);
52 		return -EINVAL;
53 	}
54 	return perf_x86_64_regnums[unw_regnum];
55 #endif // HAVE_LIBUNWIND_X86_64_SUPPORT
56 }
57 
58 void __libunwind_arch__flush_access_x86_64(struct maps *maps __maybe_unused)
59 {
60 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
61 	unw_flush_cache(maps__addr_space(maps), 0, 0);
62 #endif
63 }
64 
65 void __libunwind_arch__finish_access_x86_64(struct maps *maps __maybe_unused)
66 {
67 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
68 	unw_destroy_addr_space(maps__addr_space(maps));
69 #endif
70 }
71 
72 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
73 static int find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
74 			  int need_unwind_info, void *arg)
75 {
76 	return __libunwind__find_proc_info(as, ip, pi, need_unwind_info, arg);
77 }
78 
79 static void put_unwind_info(unw_addr_space_t __maybe_unused as,
80 			    unw_proc_info_t *pi __maybe_unused,
81 			    void *arg __maybe_unused)
82 {
83 	pr_debug("unwind: put_unwind_info called\n");
84 }
85 
86 static int get_dyn_info_list_addr(unw_addr_space_t __maybe_unused as,
87 				  unw_word_t __maybe_unused *dil_addr,
88 				  void __maybe_unused *arg)
89 {
90 	return -UNW_ENOINFO;
91 }
92 
93 static int access_mem(unw_addr_space_t as, unw_word_t addr, unw_word_t *valp,
94 		      int __write, void *arg)
95 {
96 	return __libunwind__access_mem(as, addr, valp, __write, arg);
97 }
98 
99 static int access_reg(unw_addr_space_t as, unw_regnum_t regnum, unw_word_t *valp,
100 		      int __write, void *arg)
101 {
102 	return __libunwind__access_reg(as, regnum, valp, __write, arg);
103 }
104 
105 static int access_fpreg(unw_addr_space_t __maybe_unused as,
106 			unw_regnum_t __maybe_unused num,
107 			unw_fpreg_t __maybe_unused *val,
108 			int __maybe_unused __write,
109 			void __maybe_unused *arg)
110 {
111 	pr_err("unwind: access_fpreg unsupported\n");
112 	return -UNW_EINVAL;
113 }
114 
115 static int resume(unw_addr_space_t __maybe_unused as,
116 		  unw_cursor_t __maybe_unused *cu,
117 		  void __maybe_unused *arg)
118 {
119 	pr_err("unwind: resume unsupported\n");
120 	return -UNW_EINVAL;
121 }
122 
123 static int get_proc_name(unw_addr_space_t __maybe_unused as,
124 			 unw_word_t __maybe_unused addr,
125 			 char __maybe_unused *bufp, size_t __maybe_unused buf_len,
126 			 unw_word_t __maybe_unused *offp, void __maybe_unused *arg)
127 {
128 	pr_err("unwind: get_proc_name unsupported\n");
129 	return -UNW_EINVAL;
130 }
131 #endif
132 
133 void *__libunwind_arch__create_addr_space_x86_64(void)
134 {
135 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
136 	static unw_accessors_t accessors = {
137 		.find_proc_info		= find_proc_info,
138 		.put_unwind_info	= put_unwind_info,
139 		.get_dyn_info_list_addr	= get_dyn_info_list_addr,
140 		.access_mem		= access_mem,
141 		.access_reg		= access_reg,
142 		.access_fpreg		= access_fpreg,
143 		.resume			= resume,
144 		.get_proc_name		= get_proc_name,
145 	};
146 	unw_addr_space_t addr_space;
147 
148 	addr_space = unw_create_addr_space(&accessors, /*byte_order=*/0);
149 	unw_set_caching_policy(addr_space, UNW_CACHE_GLOBAL);
150 	return addr_space;
151 #else
152 	return NULL;
153 #endif
154 }
155 
156 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
157 extern int UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
158 					       unw_word_t ip,
159 					       unw_dyn_info_t *di,
160 					       unw_proc_info_t *pi,
161 					       int need_unwind_info, void *arg);
162 #define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
163 #endif
164 
165 int __libunwind_arch__dwarf_search_unwind_table_x86_64(void *as __maybe_unused,
166 						       uint64_t ip __maybe_unused,
167 						       struct libarch_unwind__dyn_info *_di __maybe_unused,
168 						       void *pi __maybe_unused,
169 						       int need_unwind_info __maybe_unused,
170 						       void *arg __maybe_unused)
171 {
172 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
173 	unw_dyn_info_t di = {
174 		.format     = UNW_INFO_FORMAT_REMOTE_TABLE,
175 		.start_ip   = _di->start_ip,
176 		.end_ip     = _di->end_ip,
177 		.u = {
178 			.rti = {
179 				.segbase    = _di->segbase,
180 				.table_data = _di->table_data,
181 				.table_len  = _di->table_len,
182 			},
183 		},
184 	};
185 	int ret = dwarf_search_unwind_table(as, ip, &di, pi, need_unwind_info, arg);
186 
187 	_di->start_ip = di.start_ip;
188 	_di->end_ip = di.end_ip;
189 	_di->segbase = di.u.rti.segbase;
190 	_di->table_data = di.u.rti.table_data;
191 	_di->table_len = di.u.rti.table_len;
192 	return ret;
193 #else
194 	return -EINVAL;
195 #endif
196 }
197 
198 #if defined(HAVE_LIBUNWIND_X86_64_SUPPORT) && !defined(NO_LIBUNWIND_DEBUG_FRAME_X86_64)
199 extern int UNW_OBJ(dwarf_find_debug_frame) (int found, unw_dyn_info_t *di_debug,
200 					    unw_word_t ip,
201 					    unw_word_t segbase,
202 					    const char *obj_name, unw_word_t start,
203 					    unw_word_t end);
204 #define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
205 #endif
206 
207 int __libunwind_arch__dwarf_find_debug_frame_x86_64(int found __maybe_unused,
208 						 struct libarch_unwind__dyn_info *_di __maybe_unused,
209 						 uint64_t ip __maybe_unused,
210 						 uint64_t segbase __maybe_unused,
211 						 const char *obj_name __maybe_unused,
212 						 uint64_t start __maybe_unused,
213 						 uint64_t end __maybe_unused)
214 {
215 #if defined(HAVE_LIBUNWIND_X86_64_SUPPORT) && !defined(NO_LIBUNWIND_DEBUG_FRAME_X86_64)
216 	unw_dyn_info_t di = {
217 		.format     = UNW_INFO_FORMAT_REMOTE_TABLE,
218 		.start_ip   = _di->start_ip,
219 		.end_ip     = _di->end_ip,
220 		.u = {
221 			.rti = {
222 				.segbase    = _di->segbase,
223 				.table_data = _di->table_data,
224 				.table_len  = _di->table_len,
225 			},
226 		},
227 	};
228 	int ret = dwarf_find_debug_frame(found, &di, ip, segbase, obj_name, start, end);
229 
230 	_di->start_ip = di.start_ip;
231 	_di->end_ip = di.end_ip;
232 	_di->segbase = di.u.ti.segbase;
233 	_di->table_data = di.u.ti.table_data;
234 	_di->table_len = di.u.ti.table_len;
235 	return ret;
236 #else
237 	return -EINVAL;
238 #endif
239 }
240 
241 struct unwind_info *__libunwind_arch_unwind_info__new_x86_64(struct thread *thread __maybe_unused,
242 							     struct perf_sample *sample  __maybe_unused,
243 							     int max_stack __maybe_unused,
244 							     bool best_effort  __maybe_unused,
245 							     uint64_t first_ip  __maybe_unused)
246 {
247 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
248 	struct arch_unwind_info {
249 		struct unwind_info ui;
250 		unw_cursor_t _cursor;
251 		uint64_t _ips[];
252 	};
253 
254 	struct maps *maps = thread__maps(thread);
255 	void *addr_space = maps__addr_space(maps);
256 	struct arch_unwind_info *ui;
257 	int ret;
258 
259 	if (addr_space == NULL)
260 		return NULL;
261 
262 	ui = zalloc(sizeof(*ui) + sizeof(ui->_ips[0]) * max_stack);
263 	if (!ui)
264 		return NULL;
265 
266 	ui->ui.machine = maps__machine(maps);
267 	ui->ui.thread = thread;
268 	ui->ui.sample = sample;
269 	ui->ui.cursor = &ui->_cursor;
270 	ui->ui.ips = &ui->_ips[0];
271 	ui->ui.ips[0] = first_ip;
272 	ui->ui.cur_ip = 1;
273 	ui->ui.max_ips = max_stack;
274 	ui->ui.unw_word_t_size = sizeof(unw_word_t);
275 	ui->ui.e_machine = EM_X86_64;
276 	ui->ui.best_effort = best_effort;
277 
278 	ret = unw_init_remote(&ui->_cursor, addr_space, &ui->ui);
279 	if (ret) {
280 		if (!best_effort)
281 			pr_err("libunwind: %s\n", unw_strerror(ret));
282 		free(ui);
283 		return NULL;
284 	}
285 	return &ui->ui;
286 #else
287 	return NULL;
288 #endif
289 }
290 
291 int __libunwind_arch__unwind_step_x86_64(struct unwind_info *ui __maybe_unused)
292 {
293 #ifdef HAVE_LIBUNWIND_X86_64_SUPPORT
294 	int ret;
295 
296 	if (ui->cur_ip >= ui->max_ips)
297 		return 0;
298 
299 	ret = unw_step(ui->cursor);
300 	if (ret > 0) {
301 		uint64_t ip;
302 
303 		unw_get_reg(ui->cursor, UNW_REG_IP, &ip);
304 
305 		if (unw_is_signal_frame(ui->cursor) <= 0) {
306 			/*
307 			 * Decrement the IP for any non-activation frames. This
308 			 * is required to properly find the srcline for caller
309 			 * frames.  See also the documentation for
310 			 * dwfl_frame_pc(), which this code tries to replicate.
311 			 */
312 			--ip;
313 		}
314 		ui->ips[ui->cur_ip++] = ip;
315 	}
316 	return ret;
317 #else
318 	return -EINVAL;
319 #endif
320 }
321