diff options
Diffstat (limited to 'tools/perf/util/unwind.c')
-rw-r--r-- | tools/perf/util/unwind.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/tools/perf/util/unwind.c b/tools/perf/util/unwind.c new file mode 100644 index 000000000000..958723ba3d2e --- /dev/null +++ b/tools/perf/util/unwind.c | |||
@@ -0,0 +1,571 @@ | |||
1 | /* | ||
2 | * Post mortem Dwarf CFI based unwinding on top of regs and stack dumps. | ||
3 | * | ||
4 | * Lots of this code have been borrowed or heavily inspired from parts of | ||
5 | * the libunwind 0.99 code which are (amongst other contributors I may have | ||
6 | * forgotten): | ||
7 | * | ||
8 | * Copyright (C) 2002-2007 Hewlett-Packard Co | ||
9 | * Contributed by David Mosberger-Tang <davidm@hpl.hp.com> | ||
10 | * | ||
11 | * And the bugs have been added by: | ||
12 | * | ||
13 | * Copyright (C) 2010, Frederic Weisbecker <fweisbec@gmail.com> | ||
14 | * Copyright (C) 2012, Jiri Olsa <jolsa@redhat.com> | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <elf.h> | ||
19 | #include <gelf.h> | ||
20 | #include <fcntl.h> | ||
21 | #include <string.h> | ||
22 | #include <unistd.h> | ||
23 | #include <sys/mman.h> | ||
24 | #include <linux/list.h> | ||
25 | #include <libunwind.h> | ||
26 | #include <libunwind-ptrace.h> | ||
27 | #include "thread.h" | ||
28 | #include "session.h" | ||
29 | #include "perf_regs.h" | ||
30 | #include "unwind.h" | ||
31 | #include "util.h" | ||
32 | |||
33 | extern int | ||
34 | UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as, | ||
35 | unw_word_t ip, | ||
36 | unw_dyn_info_t *di, | ||
37 | unw_proc_info_t *pi, | ||
38 | int need_unwind_info, void *arg); | ||
39 | |||
40 | #define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table) | ||
41 | |||
42 | #define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */ | ||
43 | #define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */ | ||
44 | |||
45 | /* Pointer-encoding formats: */ | ||
46 | #define DW_EH_PE_omit 0xff | ||
47 | #define DW_EH_PE_ptr 0x00 /* pointer-sized unsigned value */ | ||
48 | #define DW_EH_PE_udata4 0x03 /* unsigned 32-bit value */ | ||
49 | #define DW_EH_PE_udata8 0x04 /* unsigned 64-bit value */ | ||
50 | #define DW_EH_PE_sdata4 0x0b /* signed 32-bit value */ | ||
51 | #define DW_EH_PE_sdata8 0x0c /* signed 64-bit value */ | ||
52 | |||
53 | /* Pointer-encoding application: */ | ||
54 | #define DW_EH_PE_absptr 0x00 /* absolute value */ | ||
55 | #define DW_EH_PE_pcrel 0x10 /* rel. to addr. of encoded value */ | ||
56 | |||
57 | /* | ||
58 | * The following are not documented by LSB v1.3, yet they are used by | ||
59 | * GCC, presumably they aren't documented by LSB since they aren't | ||
60 | * used on Linux: | ||
61 | */ | ||
62 | #define DW_EH_PE_funcrel 0x40 /* start-of-procedure-relative */ | ||
63 | #define DW_EH_PE_aligned 0x50 /* aligned pointer */ | ||
64 | |||
65 | /* Flags intentionaly not handled, since they're not needed: | ||
66 | * #define DW_EH_PE_indirect 0x80 | ||
67 | * #define DW_EH_PE_uleb128 0x01 | ||
68 | * #define DW_EH_PE_udata2 0x02 | ||
69 | * #define DW_EH_PE_sleb128 0x09 | ||
70 | * #define DW_EH_PE_sdata2 0x0a | ||
71 | * #define DW_EH_PE_textrel 0x20 | ||
72 | * #define DW_EH_PE_datarel 0x30 | ||
73 | */ | ||
74 | |||
75 | struct unwind_info { | ||
76 | struct perf_sample *sample; | ||
77 | struct machine *machine; | ||
78 | struct thread *thread; | ||
79 | u64 sample_uregs; | ||
80 | }; | ||
81 | |||
82 | #define dw_read(ptr, type, end) ({ \ | ||
83 | type *__p = (type *) ptr; \ | ||
84 | type __v; \ | ||
85 | if ((__p + 1) > (type *) end) \ | ||
86 | return -EINVAL; \ | ||
87 | __v = *__p++; \ | ||
88 | ptr = (typeof(ptr)) __p; \ | ||
89 | __v; \ | ||
90 | }) | ||
91 | |||
92 | static int __dw_read_encoded_value(u8 **p, u8 *end, u64 *val, | ||
93 | u8 encoding) | ||
94 | { | ||
95 | u8 *cur = *p; | ||
96 | *val = 0; | ||
97 | |||
98 | switch (encoding) { | ||
99 | case DW_EH_PE_omit: | ||
100 | *val = 0; | ||
101 | goto out; | ||
102 | case DW_EH_PE_ptr: | ||
103 | *val = dw_read(cur, unsigned long, end); | ||
104 | goto out; | ||
105 | default: | ||
106 | break; | ||
107 | } | ||
108 | |||
109 | switch (encoding & DW_EH_PE_APPL_MASK) { | ||
110 | case DW_EH_PE_absptr: | ||
111 | break; | ||
112 | case DW_EH_PE_pcrel: | ||
113 | *val = (unsigned long) cur; | ||
114 | break; | ||
115 | default: | ||
116 | return -EINVAL; | ||
117 | } | ||
118 | |||
119 | if ((encoding & 0x07) == 0x00) | ||
120 | encoding |= DW_EH_PE_udata4; | ||
121 | |||
122 | switch (encoding & DW_EH_PE_FORMAT_MASK) { | ||
123 | case DW_EH_PE_sdata4: | ||
124 | *val += dw_read(cur, s32, end); | ||
125 | break; | ||
126 | case DW_EH_PE_udata4: | ||
127 | *val += dw_read(cur, u32, end); | ||
128 | break; | ||
129 | case DW_EH_PE_sdata8: | ||
130 | *val += dw_read(cur, s64, end); | ||
131 | break; | ||
132 | case DW_EH_PE_udata8: | ||
133 | *val += dw_read(cur, u64, end); | ||
134 | break; | ||
135 | default: | ||
136 | return -EINVAL; | ||
137 | } | ||
138 | |||
139 | out: | ||
140 | *p = cur; | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | #define dw_read_encoded_value(ptr, end, enc) ({ \ | ||
145 | u64 __v; \ | ||
146 | if (__dw_read_encoded_value(&ptr, end, &__v, enc)) { \ | ||
147 | return -EINVAL; \ | ||
148 | } \ | ||
149 | __v; \ | ||
150 | }) | ||
151 | |||
152 | static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, | ||
153 | GElf_Shdr *shp, const char *name) | ||
154 | { | ||
155 | Elf_Scn *sec = NULL; | ||
156 | |||
157 | while ((sec = elf_nextscn(elf, sec)) != NULL) { | ||
158 | char *str; | ||
159 | |||
160 | gelf_getshdr(sec, shp); | ||
161 | str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); | ||
162 | if (!strcmp(name, str)) | ||
163 | break; | ||
164 | } | ||
165 | |||
166 | return sec; | ||
167 | } | ||
168 | |||
169 | static u64 elf_section_offset(int fd, const char *name) | ||
170 | { | ||
171 | Elf *elf; | ||
172 | GElf_Ehdr ehdr; | ||
173 | GElf_Shdr shdr; | ||
174 | u64 offset = 0; | ||
175 | |||
176 | elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); | ||
177 | if (elf == NULL) | ||
178 | return 0; | ||
179 | |||
180 | do { | ||
181 | if (gelf_getehdr(elf, &ehdr) == NULL) | ||
182 | break; | ||
183 | |||
184 | if (!elf_section_by_name(elf, &ehdr, &shdr, name)) | ||
185 | break; | ||
186 | |||
187 | offset = shdr.sh_offset; | ||
188 | } while (0); | ||
189 | |||
190 | elf_end(elf); | ||
191 | return offset; | ||
192 | } | ||
193 | |||
194 | struct table_entry { | ||
195 | u32 start_ip_offset; | ||
196 | u32 fde_offset; | ||
197 | }; | ||
198 | |||
199 | struct eh_frame_hdr { | ||
200 | unsigned char version; | ||
201 | unsigned char eh_frame_ptr_enc; | ||
202 | unsigned char fde_count_enc; | ||
203 | unsigned char table_enc; | ||
204 | |||
205 | /* | ||
206 | * The rest of the header is variable-length and consists of the | ||
207 | * following members: | ||
208 | * | ||
209 | * encoded_t eh_frame_ptr; | ||
210 | * encoded_t fde_count; | ||
211 | */ | ||
212 | |||
213 | /* A single encoded pointer should not be more than 8 bytes. */ | ||
214 | u64 enc[2]; | ||
215 | |||
216 | /* | ||
217 | * struct { | ||
218 | * encoded_t start_ip; | ||
219 | * encoded_t fde_addr; | ||
220 | * } binary_search_table[fde_count]; | ||
221 | */ | ||
222 | char data[0]; | ||
223 | } __packed; | ||
224 | |||
225 | static int unwind_spec_ehframe(struct dso *dso, struct machine *machine, | ||
226 | u64 offset, u64 *table_data, u64 *segbase, | ||
227 | u64 *fde_count) | ||
228 | { | ||
229 | struct eh_frame_hdr hdr; | ||
230 | u8 *enc = (u8 *) &hdr.enc; | ||
231 | u8 *end = (u8 *) &hdr.data; | ||
232 | ssize_t r; | ||
233 | |||
234 | r = dso__data_read_offset(dso, machine, offset, | ||
235 | (u8 *) &hdr, sizeof(hdr)); | ||
236 | if (r != sizeof(hdr)) | ||
237 | return -EINVAL; | ||
238 | |||
239 | /* We dont need eh_frame_ptr, just skip it. */ | ||
240 | dw_read_encoded_value(enc, end, hdr.eh_frame_ptr_enc); | ||
241 | |||
242 | *fde_count = dw_read_encoded_value(enc, end, hdr.fde_count_enc); | ||
243 | *segbase = offset; | ||
244 | *table_data = (enc - (u8 *) &hdr) + offset; | ||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | static int read_unwind_spec(struct dso *dso, struct machine *machine, | ||
249 | u64 *table_data, u64 *segbase, u64 *fde_count) | ||
250 | { | ||
251 | int ret = -EINVAL, fd; | ||
252 | u64 offset; | ||
253 | |||
254 | fd = dso__data_fd(dso, machine); | ||
255 | if (fd < 0) | ||
256 | return -EINVAL; | ||
257 | |||
258 | offset = elf_section_offset(fd, ".eh_frame_hdr"); | ||
259 | close(fd); | ||
260 | |||
261 | if (offset) | ||
262 | ret = unwind_spec_ehframe(dso, machine, offset, | ||
263 | table_data, segbase, | ||
264 | fde_count); | ||
265 | |||
266 | /* TODO .debug_frame check if eh_frame_hdr fails */ | ||
267 | return ret; | ||
268 | } | ||
269 | |||
270 | static struct map *find_map(unw_word_t ip, struct unwind_info *ui) | ||
271 | { | ||
272 | struct addr_location al; | ||
273 | |||
274 | thread__find_addr_map(ui->thread, ui->machine, PERF_RECORD_MISC_USER, | ||
275 | MAP__FUNCTION, ip, &al); | ||
276 | return al.map; | ||
277 | } | ||
278 | |||
279 | static int | ||
280 | find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi, | ||
281 | int need_unwind_info, void *arg) | ||
282 | { | ||
283 | struct unwind_info *ui = arg; | ||
284 | struct map *map; | ||
285 | unw_dyn_info_t di; | ||
286 | u64 table_data, segbase, fde_count; | ||
287 | |||
288 | map = find_map(ip, ui); | ||
289 | if (!map || !map->dso) | ||
290 | return -EINVAL; | ||
291 | |||
292 | pr_debug("unwind: find_proc_info dso %s\n", map->dso->name); | ||
293 | |||
294 | if (read_unwind_spec(map->dso, ui->machine, | ||
295 | &table_data, &segbase, &fde_count)) | ||
296 | return -EINVAL; | ||
297 | |||
298 | memset(&di, 0, sizeof(di)); | ||
299 | di.format = UNW_INFO_FORMAT_REMOTE_TABLE; | ||
300 | di.start_ip = map->start; | ||
301 | di.end_ip = map->end; | ||
302 | di.u.rti.segbase = map->start + segbase; | ||
303 | di.u.rti.table_data = map->start + table_data; | ||
304 | di.u.rti.table_len = fde_count * sizeof(struct table_entry) | ||
305 | / sizeof(unw_word_t); | ||
306 | return dwarf_search_unwind_table(as, ip, &di, pi, | ||
307 | need_unwind_info, arg); | ||
308 | } | ||
309 | |||
310 | static int access_fpreg(unw_addr_space_t __maybe_unused as, | ||
311 | unw_regnum_t __maybe_unused num, | ||
312 | unw_fpreg_t __maybe_unused *val, | ||
313 | int __maybe_unused __write, | ||
314 | void __maybe_unused *arg) | ||
315 | { | ||
316 | pr_err("unwind: access_fpreg unsupported\n"); | ||
317 | return -UNW_EINVAL; | ||
318 | } | ||
319 | |||
320 | static int get_dyn_info_list_addr(unw_addr_space_t __maybe_unused as, | ||
321 | unw_word_t __maybe_unused *dil_addr, | ||
322 | void __maybe_unused *arg) | ||
323 | { | ||
324 | return -UNW_ENOINFO; | ||
325 | } | ||
326 | |||
327 | static int resume(unw_addr_space_t __maybe_unused as, | ||
328 | unw_cursor_t __maybe_unused *cu, | ||
329 | void __maybe_unused *arg) | ||
330 | { | ||
331 | pr_err("unwind: resume unsupported\n"); | ||
332 | return -UNW_EINVAL; | ||
333 | } | ||
334 | |||
335 | static int | ||
336 | get_proc_name(unw_addr_space_t __maybe_unused as, | ||
337 | unw_word_t __maybe_unused addr, | ||
338 | char __maybe_unused *bufp, size_t __maybe_unused buf_len, | ||
339 | unw_word_t __maybe_unused *offp, void __maybe_unused *arg) | ||
340 | { | ||
341 | pr_err("unwind: get_proc_name unsupported\n"); | ||
342 | return -UNW_EINVAL; | ||
343 | } | ||
344 | |||
345 | static int access_dso_mem(struct unwind_info *ui, unw_word_t addr, | ||
346 | unw_word_t *data) | ||
347 | { | ||
348 | struct addr_location al; | ||
349 | ssize_t size; | ||
350 | |||
351 | thread__find_addr_map(ui->thread, ui->machine, PERF_RECORD_MISC_USER, | ||
352 | MAP__FUNCTION, addr, &al); | ||
353 | if (!al.map) { | ||
354 | pr_debug("unwind: no map for %lx\n", (unsigned long)addr); | ||
355 | return -1; | ||
356 | } | ||
357 | |||
358 | if (!al.map->dso) | ||
359 | return -1; | ||
360 | |||
361 | size = dso__data_read_addr(al.map->dso, al.map, ui->machine, | ||
362 | addr, (u8 *) data, sizeof(*data)); | ||
363 | |||
364 | return !(size == sizeof(*data)); | ||
365 | } | ||
366 | |||
367 | static int reg_value(unw_word_t *valp, struct regs_dump *regs, int id, | ||
368 | u64 sample_regs) | ||
369 | { | ||
370 | int i, idx = 0; | ||
371 | |||
372 | if (!(sample_regs & (1 << id))) | ||
373 | return -EINVAL; | ||
374 | |||
375 | for (i = 0; i < id; i++) { | ||
376 | if (sample_regs & (1 << i)) | ||
377 | idx++; | ||
378 | } | ||
379 | |||
380 | *valp = regs->regs[idx]; | ||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | static int access_mem(unw_addr_space_t __maybe_unused as, | ||
385 | unw_word_t addr, unw_word_t *valp, | ||
386 | int __write, void *arg) | ||
387 | { | ||
388 | struct unwind_info *ui = arg; | ||
389 | struct stack_dump *stack = &ui->sample->user_stack; | ||
390 | unw_word_t start, end; | ||
391 | int offset; | ||
392 | int ret; | ||
393 | |||
394 | /* Don't support write, probably not needed. */ | ||
395 | if (__write || !stack || !ui->sample->user_regs.regs) { | ||
396 | *valp = 0; | ||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | ret = reg_value(&start, &ui->sample->user_regs, PERF_REG_SP, | ||
401 | ui->sample_uregs); | ||
402 | if (ret) | ||
403 | return ret; | ||
404 | |||
405 | end = start + stack->size; | ||
406 | |||
407 | /* Check overflow. */ | ||
408 | if (addr + sizeof(unw_word_t) < addr) | ||
409 | return -EINVAL; | ||
410 | |||
411 | if (addr < start || addr + sizeof(unw_word_t) >= end) { | ||
412 | ret = access_dso_mem(ui, addr, valp); | ||
413 | if (ret) { | ||
414 | pr_debug("unwind: access_mem %p not inside range %p-%p\n", | ||
415 | (void *)addr, (void *)start, (void *)end); | ||
416 | *valp = 0; | ||
417 | return ret; | ||
418 | } | ||
419 | return 0; | ||
420 | } | ||
421 | |||
422 | offset = addr - start; | ||
423 | *valp = *(unw_word_t *)&stack->data[offset]; | ||
424 | pr_debug("unwind: access_mem addr %p, val %lx, offset %d\n", | ||
425 | (void *)addr, (unsigned long)*valp, offset); | ||
426 | return 0; | ||
427 | } | ||
428 | |||
429 | static int access_reg(unw_addr_space_t __maybe_unused as, | ||
430 | unw_regnum_t regnum, unw_word_t *valp, | ||
431 | int __write, void *arg) | ||
432 | { | ||
433 | struct unwind_info *ui = arg; | ||
434 | int id, ret; | ||
435 | |||
436 | /* Don't support write, I suspect we don't need it. */ | ||
437 | if (__write) { | ||
438 | pr_err("unwind: access_reg w %d\n", regnum); | ||
439 | return 0; | ||
440 | } | ||
441 | |||
442 | if (!ui->sample->user_regs.regs) { | ||
443 | *valp = 0; | ||
444 | return 0; | ||
445 | } | ||
446 | |||
447 | id = unwind__arch_reg_id(regnum); | ||
448 | if (id < 0) | ||
449 | return -EINVAL; | ||
450 | |||
451 | ret = reg_value(valp, &ui->sample->user_regs, id, ui->sample_uregs); | ||
452 | if (ret) { | ||
453 | pr_err("unwind: can't read reg %d\n", regnum); | ||
454 | return ret; | ||
455 | } | ||
456 | |||
457 | pr_debug("unwind: reg %d, val %lx\n", regnum, (unsigned long)*valp); | ||
458 | return 0; | ||
459 | } | ||
460 | |||
461 | static void put_unwind_info(unw_addr_space_t __maybe_unused as, | ||
462 | unw_proc_info_t *pi __maybe_unused, | ||
463 | void *arg __maybe_unused) | ||
464 | { | ||
465 | pr_debug("unwind: put_unwind_info called\n"); | ||
466 | } | ||
467 | |||
468 | static int entry(u64 ip, struct thread *thread, struct machine *machine, | ||
469 | unwind_entry_cb_t cb, void *arg) | ||
470 | { | ||
471 | struct unwind_entry e; | ||
472 | struct addr_location al; | ||
473 | |||
474 | thread__find_addr_location(thread, machine, | ||
475 | PERF_RECORD_MISC_USER, | ||
476 | MAP__FUNCTION, ip, &al, NULL); | ||
477 | |||
478 | e.ip = ip; | ||
479 | e.map = al.map; | ||
480 | e.sym = al.sym; | ||
481 | |||
482 | pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n", | ||
483 | al.sym ? al.sym->name : "''", | ||
484 | ip, | ||
485 | al.map ? al.map->map_ip(al.map, ip) : (u64) 0); | ||
486 | |||
487 | return cb(&e, arg); | ||
488 | } | ||
489 | |||
490 | static void display_error(int err) | ||
491 | { | ||
492 | switch (err) { | ||
493 | case UNW_EINVAL: | ||
494 | pr_err("unwind: Only supports local.\n"); | ||
495 | break; | ||
496 | case UNW_EUNSPEC: | ||
497 | pr_err("unwind: Unspecified error.\n"); | ||
498 | break; | ||
499 | case UNW_EBADREG: | ||
500 | pr_err("unwind: Register unavailable.\n"); | ||
501 | break; | ||
502 | default: | ||
503 | break; | ||
504 | } | ||
505 | } | ||
506 | |||
507 | static unw_accessors_t accessors = { | ||
508 | .find_proc_info = find_proc_info, | ||
509 | .put_unwind_info = put_unwind_info, | ||
510 | .get_dyn_info_list_addr = get_dyn_info_list_addr, | ||
511 | .access_mem = access_mem, | ||
512 | .access_reg = access_reg, | ||
513 | .access_fpreg = access_fpreg, | ||
514 | .resume = resume, | ||
515 | .get_proc_name = get_proc_name, | ||
516 | }; | ||
517 | |||
518 | static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb, | ||
519 | void *arg) | ||
520 | { | ||
521 | unw_addr_space_t addr_space; | ||
522 | unw_cursor_t c; | ||
523 | int ret; | ||
524 | |||
525 | addr_space = unw_create_addr_space(&accessors, 0); | ||
526 | if (!addr_space) { | ||
527 | pr_err("unwind: Can't create unwind address space.\n"); | ||
528 | return -ENOMEM; | ||
529 | } | ||
530 | |||
531 | ret = unw_init_remote(&c, addr_space, ui); | ||
532 | if (ret) | ||
533 | display_error(ret); | ||
534 | |||
535 | while (!ret && (unw_step(&c) > 0)) { | ||
536 | unw_word_t ip; | ||
537 | |||
538 | unw_get_reg(&c, UNW_REG_IP, &ip); | ||
539 | ret = entry(ip, ui->thread, ui->machine, cb, arg); | ||
540 | } | ||
541 | |||
542 | unw_destroy_addr_space(addr_space); | ||
543 | return ret; | ||
544 | } | ||
545 | |||
546 | int unwind__get_entries(unwind_entry_cb_t cb, void *arg, | ||
547 | struct machine *machine, struct thread *thread, | ||
548 | u64 sample_uregs, struct perf_sample *data) | ||
549 | { | ||
550 | unw_word_t ip; | ||
551 | struct unwind_info ui = { | ||
552 | .sample = data, | ||
553 | .sample_uregs = sample_uregs, | ||
554 | .thread = thread, | ||
555 | .machine = machine, | ||
556 | }; | ||
557 | int ret; | ||
558 | |||
559 | if (!data->user_regs.regs) | ||
560 | return -EINVAL; | ||
561 | |||
562 | ret = reg_value(&ip, &data->user_regs, PERF_REG_IP, sample_uregs); | ||
563 | if (ret) | ||
564 | return ret; | ||
565 | |||
566 | ret = entry(ip, thread, machine, cb, arg); | ||
567 | if (ret) | ||
568 | return -ENOMEM; | ||
569 | |||
570 | return get_entries(&ui, cb, arg); | ||
571 | } | ||