aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/util/unwind.c
diff options
context:
space:
mode:
authorJiri Olsa <jolsa@redhat.com>2012-08-07 09:20:46 -0400
committerArnaldo Carvalho de Melo <acme@redhat.com>2012-08-11 14:06:56 -0400
commit71ad0f5e4e361c8bca864c7d09d14b64af6bc2fc (patch)
tree57d87f004c3d939d2c7be315b9e1011a9214a6a1 /tools/perf/util/unwind.c
parent0f6a30150ca2e0cf4f893e7173d61434a3c02e0e (diff)
perf tools: Support for DWARF CFI unwinding on post processing
This brings the support for DWARF cfi unwinding on perf post processing. Call frame informations are retrieved and then passed to libunwind that requests memory and register content from the applications. Adding unwind object to handle the user stack backtrace based on the user register values and user stack dump. The unwind object access the libunwind via remote interface and provides to it all the necessary data to unwind the stack. The unwind interface provides following function: unwind__get_entries And callback (specified in above function) to retrieve the backtrace entries: typedef int (*unwind_entry_cb_t)(struct unwind_entry *entry, void *arg); Signed-off-by: Jiri Olsa <jolsa@redhat.com> Original-patch-by: Frederic Weisbecker <fweisbec@gmail.com> Cc: "Frank Ch. Eigler" <fche@redhat.com> Cc: Arun Sharma <asharma@fb.com> Cc: Benjamin Redelings <benjamin.redelings@nescent.org> Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com> Cc: Cyrill Gorcunov <gorcunov@openvz.org> Cc: Frank Ch. Eigler <fche@redhat.com> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> Cc: Paul Mackerras <paulus@samba.org> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Robert Richter <robert.richter@amd.com> Cc: Stephane Eranian <eranian@google.com> Cc: Tom Zanussi <tzanussi@gmail.com> Cc: Ulrich Drepper <drepper@gmail.com> Link: http://lkml.kernel.org/r/1344345647-11536-12-git-send-email-jolsa@redhat.com [ Replaced use of perf_session by usage of perf_evsel ] Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/util/unwind.c')
-rw-r--r--tools/perf/util/unwind.c567
1 files changed, 567 insertions, 0 deletions
diff --git a/tools/perf/util/unwind.c b/tools/perf/util/unwind.c
new file mode 100644
index 000000000000..00a42aa8d5c1
--- /dev/null
+++ b/tools/perf/util/unwind.c
@@ -0,0 +1,567 @@
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
33extern int
34UNW_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
75struct 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
92static 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
152static 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
169static 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
194struct table_entry {
195 u32 start_ip_offset;
196 u32 fde_offset;
197};
198
199struct 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
225static 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
248static 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
270static 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
279static int
280find_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
310static int access_fpreg(unw_addr_space_t __used as, unw_regnum_t __used num,
311 unw_fpreg_t __used *val, int __used __write,
312 void __used *arg)
313{
314 pr_err("unwind: access_fpreg unsupported\n");
315 return -UNW_EINVAL;
316}
317
318static int get_dyn_info_list_addr(unw_addr_space_t __used as,
319 unw_word_t __used *dil_addr,
320 void __used *arg)
321{
322 return -UNW_ENOINFO;
323}
324
325static int resume(unw_addr_space_t __used as, unw_cursor_t __used *cu,
326 void __used *arg)
327{
328 pr_err("unwind: resume unsupported\n");
329 return -UNW_EINVAL;
330}
331
332static int
333get_proc_name(unw_addr_space_t __used as, unw_word_t __used addr,
334 char __used *bufp, size_t __used buf_len,
335 unw_word_t __used *offp, void __used *arg)
336{
337 pr_err("unwind: get_proc_name unsupported\n");
338 return -UNW_EINVAL;
339}
340
341static int access_dso_mem(struct unwind_info *ui, unw_word_t addr,
342 unw_word_t *data)
343{
344 struct addr_location al;
345 ssize_t size;
346
347 thread__find_addr_map(ui->thread, ui->machine, PERF_RECORD_MISC_USER,
348 MAP__FUNCTION, addr, &al);
349 if (!al.map) {
350 pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
351 return -1;
352 }
353
354 if (!al.map->dso)
355 return -1;
356
357 size = dso__data_read_addr(al.map->dso, al.map, ui->machine,
358 addr, (u8 *) data, sizeof(*data));
359
360 return !(size == sizeof(*data));
361}
362
363static int reg_value(unw_word_t *valp, struct regs_dump *regs, int id,
364 u64 sample_regs)
365{
366 int i, idx = 0;
367
368 if (!(sample_regs & (1 << id)))
369 return -EINVAL;
370
371 for (i = 0; i < id; i++) {
372 if (sample_regs & (1 << i))
373 idx++;
374 }
375
376 *valp = regs->regs[idx];
377 return 0;
378}
379
380static int access_mem(unw_addr_space_t __used as,
381 unw_word_t addr, unw_word_t *valp,
382 int __write, void *arg)
383{
384 struct unwind_info *ui = arg;
385 struct stack_dump *stack = &ui->sample->user_stack;
386 unw_word_t start, end;
387 int offset;
388 int ret;
389
390 /* Don't support write, probably not needed. */
391 if (__write || !stack || !ui->sample->user_regs.regs) {
392 *valp = 0;
393 return 0;
394 }
395
396 ret = reg_value(&start, &ui->sample->user_regs, PERF_REG_SP,
397 ui->sample_uregs);
398 if (ret)
399 return ret;
400
401 end = start + stack->size;
402
403 /* Check overflow. */
404 if (addr + sizeof(unw_word_t) < addr)
405 return -EINVAL;
406
407 if (addr < start || addr + sizeof(unw_word_t) >= end) {
408 ret = access_dso_mem(ui, addr, valp);
409 if (ret) {
410 pr_debug("unwind: access_mem %p not inside range %p-%p\n",
411 (void *)addr, (void *)start, (void *)end);
412 *valp = 0;
413 return ret;
414 }
415 return 0;
416 }
417
418 offset = addr - start;
419 *valp = *(unw_word_t *)&stack->data[offset];
420 pr_debug("unwind: access_mem addr %p, val %lx, offset %d\n",
421 (void *)addr, (unsigned long)*valp, offset);
422 return 0;
423}
424
425static int access_reg(unw_addr_space_t __used as,
426 unw_regnum_t regnum, unw_word_t *valp,
427 int __write, void *arg)
428{
429 struct unwind_info *ui = arg;
430 int id, ret;
431
432 /* Don't support write, I suspect we don't need it. */
433 if (__write) {
434 pr_err("unwind: access_reg w %d\n", regnum);
435 return 0;
436 }
437
438 if (!ui->sample->user_regs.regs) {
439 *valp = 0;
440 return 0;
441 }
442
443 id = unwind__arch_reg_id(regnum);
444 if (id < 0)
445 return -EINVAL;
446
447 ret = reg_value(valp, &ui->sample->user_regs, id, ui->sample_uregs);
448 if (ret) {
449 pr_err("unwind: can't read reg %d\n", regnum);
450 return ret;
451 }
452
453 pr_debug("unwind: reg %d, val %lx\n", regnum, (unsigned long)*valp);
454 return 0;
455}
456
457static void put_unwind_info(unw_addr_space_t __used as,
458 unw_proc_info_t *pi __used,
459 void *arg __used)
460{
461 pr_debug("unwind: put_unwind_info called\n");
462}
463
464static int entry(u64 ip, struct thread *thread, struct machine *machine,
465 unwind_entry_cb_t cb, void *arg)
466{
467 struct unwind_entry e;
468 struct addr_location al;
469
470 thread__find_addr_location(thread, machine,
471 PERF_RECORD_MISC_USER,
472 MAP__FUNCTION, ip, &al, NULL);
473
474 e.ip = ip;
475 e.map = al.map;
476 e.sym = al.sym;
477
478 pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n",
479 al.sym ? al.sym->name : "''",
480 ip,
481 al.map ? al.map->map_ip(al.map, ip) : (u64) 0);
482
483 return cb(&e, arg);
484}
485
486static void display_error(int err)
487{
488 switch (err) {
489 case UNW_EINVAL:
490 pr_err("unwind: Only supports local.\n");
491 break;
492 case UNW_EUNSPEC:
493 pr_err("unwind: Unspecified error.\n");
494 break;
495 case UNW_EBADREG:
496 pr_err("unwind: Register unavailable.\n");
497 break;
498 default:
499 break;
500 }
501}
502
503static unw_accessors_t accessors = {
504 .find_proc_info = find_proc_info,
505 .put_unwind_info = put_unwind_info,
506 .get_dyn_info_list_addr = get_dyn_info_list_addr,
507 .access_mem = access_mem,
508 .access_reg = access_reg,
509 .access_fpreg = access_fpreg,
510 .resume = resume,
511 .get_proc_name = get_proc_name,
512};
513
514static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
515 void *arg)
516{
517 unw_addr_space_t addr_space;
518 unw_cursor_t c;
519 int ret;
520
521 addr_space = unw_create_addr_space(&accessors, 0);
522 if (!addr_space) {
523 pr_err("unwind: Can't create unwind address space.\n");
524 return -ENOMEM;
525 }
526
527 ret = unw_init_remote(&c, addr_space, ui);
528 if (ret)
529 display_error(ret);
530
531 while (!ret && (unw_step(&c) > 0)) {
532 unw_word_t ip;
533
534 unw_get_reg(&c, UNW_REG_IP, &ip);
535 ret = entry(ip, ui->thread, ui->machine, cb, arg);
536 }
537
538 unw_destroy_addr_space(addr_space);
539 return ret;
540}
541
542int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
543 struct machine *machine, struct thread *thread,
544 u64 sample_uregs, struct perf_sample *data)
545{
546 unw_word_t ip;
547 struct unwind_info ui = {
548 .sample = data,
549 .sample_uregs = sample_uregs,
550 .thread = thread,
551 .machine = machine,
552 };
553 int ret;
554
555 if (!data->user_regs.regs)
556 return -EINVAL;
557
558 ret = reg_value(&ip, &data->user_regs, PERF_REG_IP, sample_uregs);
559 if (ret)
560 return ret;
561
562 ret = entry(ip, thread, machine, cb, arg);
563 if (ret)
564 return -ENOMEM;
565
566 return get_entries(&ui, cb, arg);
567}