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