aboutsummaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--tools/perf/Makefile2
-rw-r--r--tools/perf/arch/x86/Makefile3
-rw-r--r--tools/perf/arch/x86/util/unwind.c111
-rw-r--r--tools/perf/builtin-report.c18
-rw-r--r--tools/perf/builtin-script.c4
-rw-r--r--tools/perf/builtin-top.c6
-rw-r--r--tools/perf/util/include/linux/compiler.h1
-rw-r--r--tools/perf/util/map.h5
-rw-r--r--tools/perf/util/session.c66
-rw-r--r--tools/perf/util/session.h6
-rw-r--r--tools/perf/util/trace-event.h2
-rw-r--r--tools/perf/util/unwind.c567
-rw-r--r--tools/perf/util/unwind.h34
13 files changed, 795 insertions, 30 deletions
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 0aee6a916493..e457afa04b59 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -334,6 +334,7 @@ LIB_H += util/target.h
334LIB_H += util/rblist.h 334LIB_H += util/rblist.h
335LIB_H += util/intlist.h 335LIB_H += util/intlist.h
336LIB_H += util/perf_regs.h 336LIB_H += util/perf_regs.h
337LIB_H += util/unwind.h
337 338
338LIB_OBJS += $(OUTPUT)util/abspath.o 339LIB_OBJS += $(OUTPUT)util/abspath.o
339LIB_OBJS += $(OUTPUT)util/alias.o 340LIB_OBJS += $(OUTPUT)util/alias.o
@@ -547,6 +548,7 @@ else
547 EXTLIBS += $(LIBUNWIND_LIBS) 548 EXTLIBS += $(LIBUNWIND_LIBS)
548 BASIC_CFLAGS := $(LIBUNWIND_CFLAGS) $(BASIC_CFLAGS) 549 BASIC_CFLAGS := $(LIBUNWIND_CFLAGS) $(BASIC_CFLAGS)
549 BASIC_LDFLAGS := $(LIBUNWIND_LDFLAGS) $(BASIC_LDFLAGS) 550 BASIC_LDFLAGS := $(LIBUNWIND_LDFLAGS) $(BASIC_LDFLAGS)
551 LIB_OBJS += $(OUTPUT)util/unwind.o
550endif 552endif
551 553
552ifdef NO_NEWT 554ifdef NO_NEWT
diff --git a/tools/perf/arch/x86/Makefile b/tools/perf/arch/x86/Makefile
index 744e629797be..815841c04eb2 100644
--- a/tools/perf/arch/x86/Makefile
+++ b/tools/perf/arch/x86/Makefile
@@ -2,4 +2,7 @@ ifndef NO_DWARF
2PERF_HAVE_DWARF_REGS := 1 2PERF_HAVE_DWARF_REGS := 1
3LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o 3LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
4endif 4endif
5ifndef NO_LIBUNWIND
6LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind.o
7endif
5LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o 8LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o
diff --git a/tools/perf/arch/x86/util/unwind.c b/tools/perf/arch/x86/util/unwind.c
new file mode 100644
index 000000000000..78d956eff96f
--- /dev/null
+++ b/tools/perf/arch/x86/util/unwind.c
@@ -0,0 +1,111 @@
1
2#include <errno.h>
3#include <libunwind.h>
4#include "perf_regs.h"
5#include "../../util/unwind.h"
6
7#ifdef ARCH_X86_64
8int unwind__arch_reg_id(int regnum)
9{
10 int id;
11
12 switch (regnum) {
13 case UNW_X86_64_RAX:
14 id = PERF_REG_X86_AX;
15 break;
16 case UNW_X86_64_RDX:
17 id = PERF_REG_X86_DX;
18 break;
19 case UNW_X86_64_RCX:
20 id = PERF_REG_X86_CX;
21 break;
22 case UNW_X86_64_RBX:
23 id = PERF_REG_X86_BX;
24 break;
25 case UNW_X86_64_RSI:
26 id = PERF_REG_X86_SI;
27 break;
28 case UNW_X86_64_RDI:
29 id = PERF_REG_X86_DI;
30 break;
31 case UNW_X86_64_RBP:
32 id = PERF_REG_X86_BP;
33 break;
34 case UNW_X86_64_RSP:
35 id = PERF_REG_X86_SP;
36 break;
37 case UNW_X86_64_R8:
38 id = PERF_REG_X86_R8;
39 break;
40 case UNW_X86_64_R9:
41 id = PERF_REG_X86_R9;
42 break;
43 case UNW_X86_64_R10:
44 id = PERF_REG_X86_R10;
45 break;
46 case UNW_X86_64_R11:
47 id = PERF_REG_X86_R11;
48 break;
49 case UNW_X86_64_R12:
50 id = PERF_REG_X86_R12;
51 break;
52 case UNW_X86_64_R13:
53 id = PERF_REG_X86_R13;
54 break;
55 case UNW_X86_64_R14:
56 id = PERF_REG_X86_R14;
57 break;
58 case UNW_X86_64_R15:
59 id = PERF_REG_X86_R15;
60 break;
61 case UNW_X86_64_RIP:
62 id = PERF_REG_X86_IP;
63 break;
64 default:
65 pr_err("unwind: invalid reg id %d\n", regnum);
66 return -EINVAL;
67 }
68
69 return id;
70}
71#else
72int unwind__arch_reg_id(int regnum)
73{
74 int id;
75
76 switch (regnum) {
77 case UNW_X86_EAX:
78 id = PERF_REG_X86_AX;
79 break;
80 case UNW_X86_EDX:
81 id = PERF_REG_X86_DX;
82 break;
83 case UNW_X86_ECX:
84 id = PERF_REG_X86_CX;
85 break;
86 case UNW_X86_EBX:
87 id = PERF_REG_X86_BX;
88 break;
89 case UNW_X86_ESI:
90 id = PERF_REG_X86_SI;
91 break;
92 case UNW_X86_EDI:
93 id = PERF_REG_X86_DI;
94 break;
95 case UNW_X86_EBP:
96 id = PERF_REG_X86_BP;
97 break;
98 case UNW_X86_ESP:
99 id = PERF_REG_X86_SP;
100 break;
101 case UNW_X86_EIP:
102 id = PERF_REG_X86_IP;
103 break;
104 default:
105 pr_err("unwind: invalid reg id %d\n", regnum);
106 return -EINVAL;
107 }
108
109 return id;
110}
111#endif /* ARCH_X86_64 */
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index 7c88a243b5db..d61825371adc 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -69,8 +69,8 @@ static int perf_report__add_branch_hist_entry(struct perf_tool *tool,
69 69
70 if ((sort__has_parent || symbol_conf.use_callchain) 70 if ((sort__has_parent || symbol_conf.use_callchain)
71 && sample->callchain) { 71 && sample->callchain) {
72 err = machine__resolve_callchain(machine, al->thread, 72 err = machine__resolve_callchain(machine, evsel, al->thread,
73 sample->callchain, &parent); 73 sample, &parent);
74 if (err) 74 if (err)
75 return err; 75 return err;
76 } 76 }
@@ -140,8 +140,8 @@ static int perf_evsel__add_hist_entry(struct perf_evsel *evsel,
140 struct hist_entry *he; 140 struct hist_entry *he;
141 141
142 if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) { 142 if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) {
143 err = machine__resolve_callchain(machine, al->thread, 143 err = machine__resolve_callchain(machine, evsel, al->thread,
144 sample->callchain, &parent); 144 sample, &parent);
145 if (err) 145 if (err)
146 return err; 146 return err;
147 } 147 }
@@ -397,17 +397,17 @@ static int __cmd_report(struct perf_report *rep)
397 desc); 397 desc);
398 } 398 }
399 399
400 if (dump_trace) {
401 perf_session__fprintf_nr_events(session, stdout);
402 goto out_delete;
403 }
404
405 if (verbose > 3) 400 if (verbose > 3)
406 perf_session__fprintf(session, stdout); 401 perf_session__fprintf(session, stdout);
407 402
408 if (verbose > 2) 403 if (verbose > 2)
409 perf_session__fprintf_dsos(session, stdout); 404 perf_session__fprintf_dsos(session, stdout);
410 405
406 if (dump_trace) {
407 perf_session__fprintf_nr_events(session, stdout);
408 goto out_delete;
409 }
410
411 nr_samples = 0; 411 nr_samples = 0;
412 list_for_each_entry(pos, &session->evlist->entries, node) { 412 list_for_each_entry(pos, &session->evlist->entries, node) {
413 struct hists *hists = &pos->hists; 413 struct hists *hists = &pos->hists;
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 30a9cb8c9927..2d6e3b226aad 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -380,7 +380,7 @@ static void print_sample_bts(union perf_event *event,
380 printf(" "); 380 printf(" ");
381 else 381 else
382 printf("\n"); 382 printf("\n");
383 perf_event__print_ip(event, sample, machine, 383 perf_evsel__print_ip(evsel, event, sample, machine,
384 PRINT_FIELD(SYM), PRINT_FIELD(DSO), 384 PRINT_FIELD(SYM), PRINT_FIELD(DSO),
385 PRINT_FIELD(SYMOFFSET)); 385 PRINT_FIELD(SYMOFFSET));
386 } 386 }
@@ -422,7 +422,7 @@ static void process_event(union perf_event *event, struct perf_sample *sample,
422 printf(" "); 422 printf(" ");
423 else 423 else
424 printf("\n"); 424 printf("\n");
425 perf_event__print_ip(event, sample, machine, 425 perf_evsel__print_ip(evsel, event, sample, machine,
426 PRINT_FIELD(SYM), PRINT_FIELD(DSO), 426 PRINT_FIELD(SYM), PRINT_FIELD(DSO),
427 PRINT_FIELD(SYMOFFSET)); 427 PRINT_FIELD(SYMOFFSET));
428 } 428 }
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 68cd61ef6ac5..e45a1ba61722 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -783,8 +783,10 @@ static void perf_event__process_sample(struct perf_tool *tool,
783 783
784 if ((sort__has_parent || symbol_conf.use_callchain) && 784 if ((sort__has_parent || symbol_conf.use_callchain) &&
785 sample->callchain) { 785 sample->callchain) {
786 err = machine__resolve_callchain(machine, al.thread, 786 err = machine__resolve_callchain(machine, evsel,
787 sample->callchain, &parent); 787 al.thread, sample,
788 &parent);
789
788 if (err) 790 if (err)
789 return; 791 return;
790 } 792 }
diff --git a/tools/perf/util/include/linux/compiler.h b/tools/perf/util/include/linux/compiler.h
index 547628e97f3d..2dc867128e46 100644
--- a/tools/perf/util/include/linux/compiler.h
+++ b/tools/perf/util/include/linux/compiler.h
@@ -10,5 +10,6 @@
10#endif 10#endif
11 11
12#define __used __attribute__((__unused__)) 12#define __used __attribute__((__unused__))
13#define __packed __attribute__((__packed__))
13 14
14#endif 15#endif
diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h
index 1e183d1ae581..c98ab1900608 100644
--- a/tools/perf/util/map.h
+++ b/tools/perf/util/map.h
@@ -158,9 +158,12 @@ int machine__init(struct machine *self, const char *root_dir, pid_t pid);
158void machine__exit(struct machine *self); 158void machine__exit(struct machine *self);
159void machine__delete(struct machine *self); 159void machine__delete(struct machine *self);
160 160
161struct perf_evsel;
162struct perf_sample;
161int machine__resolve_callchain(struct machine *machine, 163int machine__resolve_callchain(struct machine *machine,
164 struct perf_evsel *evsel,
162 struct thread *thread, 165 struct thread *thread,
163 struct ip_callchain *chain, 166 struct perf_sample *sample,
164 struct symbol **parent); 167 struct symbol **parent);
165int maps__set_kallsyms_ref_reloc_sym(struct map **maps, const char *symbol_name, 168int maps__set_kallsyms_ref_reloc_sym(struct map **maps, const char *symbol_name,
166 u64 addr); 169 u64 addr);
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index c9ed7e3cf231..f7bb7ae328da 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -16,6 +16,7 @@
16#include "cpumap.h" 16#include "cpumap.h"
17#include "event-parse.h" 17#include "event-parse.h"
18#include "perf_regs.h" 18#include "perf_regs.h"
19#include "unwind.h"
19 20
20static int perf_session__open(struct perf_session *self, bool force) 21static int perf_session__open(struct perf_session *self, bool force)
21{ 22{
@@ -289,10 +290,11 @@ struct branch_info *machine__resolve_bstack(struct machine *self,
289 return bi; 290 return bi;
290} 291}
291 292
292int machine__resolve_callchain(struct machine *self, 293static int machine__resolve_callchain_sample(struct machine *machine,
293 struct thread *thread, 294 struct thread *thread,
294 struct ip_callchain *chain, 295 struct ip_callchain *chain,
295 struct symbol **parent) 296 struct symbol **parent)
297
296{ 298{
297 u8 cpumode = PERF_RECORD_MISC_USER; 299 u8 cpumode = PERF_RECORD_MISC_USER;
298 unsigned int i; 300 unsigned int i;
@@ -317,11 +319,14 @@ int machine__resolve_callchain(struct machine *self,
317 if (ip >= PERF_CONTEXT_MAX) { 319 if (ip >= PERF_CONTEXT_MAX) {
318 switch (ip) { 320 switch (ip) {
319 case PERF_CONTEXT_HV: 321 case PERF_CONTEXT_HV:
320 cpumode = PERF_RECORD_MISC_HYPERVISOR; break; 322 cpumode = PERF_RECORD_MISC_HYPERVISOR;
323 break;
321 case PERF_CONTEXT_KERNEL: 324 case PERF_CONTEXT_KERNEL:
322 cpumode = PERF_RECORD_MISC_KERNEL; break; 325 cpumode = PERF_RECORD_MISC_KERNEL;
326 break;
323 case PERF_CONTEXT_USER: 327 case PERF_CONTEXT_USER:
324 cpumode = PERF_RECORD_MISC_USER; break; 328 cpumode = PERF_RECORD_MISC_USER;
329 break;
325 default: 330 default:
326 pr_debug("invalid callchain context: " 331 pr_debug("invalid callchain context: "
327 "%"PRId64"\n", (s64) ip); 332 "%"PRId64"\n", (s64) ip);
@@ -336,7 +341,7 @@ int machine__resolve_callchain(struct machine *self,
336 } 341 }
337 342
338 al.filtered = false; 343 al.filtered = false;
339 thread__find_addr_location(thread, self, cpumode, 344 thread__find_addr_location(thread, machine, cpumode,
340 MAP__FUNCTION, ip, &al, NULL); 345 MAP__FUNCTION, ip, &al, NULL);
341 if (al.sym != NULL) { 346 if (al.sym != NULL) {
342 if (sort__has_parent && !*parent && 347 if (sort__has_parent && !*parent &&
@@ -355,6 +360,40 @@ int machine__resolve_callchain(struct machine *self,
355 return 0; 360 return 0;
356} 361}
357 362
363static int unwind_entry(struct unwind_entry *entry, void *arg)
364{
365 struct callchain_cursor *cursor = arg;
366 return callchain_cursor_append(cursor, entry->ip,
367 entry->map, entry->sym);
368}
369
370int machine__resolve_callchain(struct machine *machine,
371 struct perf_evsel *evsel,
372 struct thread *thread,
373 struct perf_sample *sample,
374 struct symbol **parent)
375
376{
377 int ret;
378
379 callchain_cursor_reset(&callchain_cursor);
380
381 ret = machine__resolve_callchain_sample(machine, thread,
382 sample->callchain, parent);
383 if (ret)
384 return ret;
385
386 /* Can we do dwarf post unwind? */
387 if (!((evsel->attr.sample_type & PERF_SAMPLE_REGS_USER) &&
388 (evsel->attr.sample_type & PERF_SAMPLE_STACK_USER)))
389 return 0;
390
391 return unwind__get_entries(unwind_entry, &callchain_cursor, machine,
392 thread, evsel->attr.sample_regs_user,
393 sample);
394
395}
396
358static int process_event_synth_tracing_data_stub(union perf_event *event __used, 397static int process_event_synth_tracing_data_stub(union perf_event *event __used,
359 struct perf_session *session __used) 398 struct perf_session *session __used)
360{ 399{
@@ -1533,9 +1572,9 @@ struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session,
1533 return NULL; 1572 return NULL;
1534} 1573}
1535 1574
1536void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, 1575void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event,
1537 struct machine *machine, int print_sym, 1576 struct perf_sample *sample, struct machine *machine,
1538 int print_dso, int print_symoffset) 1577 int print_sym, int print_dso, int print_symoffset)
1539{ 1578{
1540 struct addr_location al; 1579 struct addr_location al;
1541 struct callchain_cursor_node *node; 1580 struct callchain_cursor_node *node;
@@ -1549,8 +1588,9 @@ void perf_event__print_ip(union perf_event *event, struct perf_sample *sample,
1549 1588
1550 if (symbol_conf.use_callchain && sample->callchain) { 1589 if (symbol_conf.use_callchain && sample->callchain) {
1551 1590
1552 if (machine__resolve_callchain(machine, al.thread, 1591
1553 sample->callchain, NULL) != 0) { 1592 if (machine__resolve_callchain(machine, evsel, al.thread,
1593 sample, NULL) != 0) {
1554 if (verbose) 1594 if (verbose)
1555 error("Failed to resolve callchain. Skipping\n"); 1595 error("Failed to resolve callchain. Skipping\n");
1556 return; 1596 return;
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
index 1f7ec87db7d7..176a60902f56 100644
--- a/tools/perf/util/session.h
+++ b/tools/perf/util/session.h
@@ -129,9 +129,9 @@ size_t perf_session__fprintf_nr_events(struct perf_session *session, FILE *fp);
129struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, 129struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session,
130 unsigned int type); 130 unsigned int type);
131 131
132void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, 132void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event,
133 struct machine *machine, int print_sym, 133 struct perf_sample *sample, struct machine *machine,
134 int print_dso, int print_symoffset); 134 int print_sym, int print_dso, int print_symoffset);
135 135
136int perf_session__cpu_bitmap(struct perf_session *session, 136int perf_session__cpu_bitmap(struct perf_session *session,
137 const char *cpu_list, unsigned long *cpu_bitmap); 137 const char *cpu_list, unsigned long *cpu_bitmap);
diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h
index 7575dfd26e58..a55fd37ffea1 100644
--- a/tools/perf/util/trace-event.h
+++ b/tools/perf/util/trace-event.h
@@ -77,6 +77,8 @@ void tracing_data_put(struct tracing_data *tdata);
77 77
78struct addr_location; 78struct addr_location;
79 79
80struct perf_session;
81
80struct scripting_ops { 82struct scripting_ops {
81 const char *name; 83 const char *name;
82 int (*start_script) (const char *script, int argc, const char **argv); 84 int (*start_script) (const char *script, int argc, const char **argv);
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}
diff --git a/tools/perf/util/unwind.h b/tools/perf/util/unwind.h
new file mode 100644
index 000000000000..919bd6ad8501
--- /dev/null
+++ b/tools/perf/util/unwind.h
@@ -0,0 +1,34 @@
1#ifndef __UNWIND_H
2#define __UNWIND_H
3
4#include "types.h"
5#include "event.h"
6#include "symbol.h"
7
8struct unwind_entry {
9 struct map *map;
10 struct symbol *sym;
11 u64 ip;
12};
13
14typedef int (*unwind_entry_cb_t)(struct unwind_entry *entry, void *arg);
15
16#ifndef NO_LIBUNWIND_SUPPORT
17int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
18 struct machine *machine,
19 struct thread *thread,
20 u64 sample_uregs,
21 struct perf_sample *data);
22int unwind__arch_reg_id(int regnum);
23#else
24static inline int
25unwind__get_entries(unwind_entry_cb_t cb __used, void *arg __used,
26 struct machine *machine __used,
27 struct thread *thread __used,
28 u64 sample_uregs __used,
29 struct perf_sample *data __used)
30{
31 return 0;
32}
33#endif /* NO_LIBUNWIND_SUPPORT */
34#endif /* __UNWIND_H */