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