diff options
Diffstat (limited to 'tools/perf')
-rw-r--r-- | tools/perf/Makefile | 2 | ||||
-rw-r--r-- | tools/perf/arch/x86/Makefile | 3 | ||||
-rw-r--r-- | tools/perf/arch/x86/util/unwind.c | 111 | ||||
-rw-r--r-- | tools/perf/builtin-report.c | 18 | ||||
-rw-r--r-- | tools/perf/builtin-script.c | 4 | ||||
-rw-r--r-- | tools/perf/builtin-top.c | 6 | ||||
-rw-r--r-- | tools/perf/util/include/linux/compiler.h | 1 | ||||
-rw-r--r-- | tools/perf/util/map.h | 5 | ||||
-rw-r--r-- | tools/perf/util/session.c | 66 | ||||
-rw-r--r-- | tools/perf/util/session.h | 6 | ||||
-rw-r--r-- | tools/perf/util/trace-event.h | 2 | ||||
-rw-r--r-- | tools/perf/util/unwind.c | 567 | ||||
-rw-r--r-- | tools/perf/util/unwind.h | 34 |
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 | |||
334 | LIB_H += util/rblist.h | 334 | LIB_H += util/rblist.h |
335 | LIB_H += util/intlist.h | 335 | LIB_H += util/intlist.h |
336 | LIB_H += util/perf_regs.h | 336 | LIB_H += util/perf_regs.h |
337 | LIB_H += util/unwind.h | ||
337 | 338 | ||
338 | LIB_OBJS += $(OUTPUT)util/abspath.o | 339 | LIB_OBJS += $(OUTPUT)util/abspath.o |
339 | LIB_OBJS += $(OUTPUT)util/alias.o | 340 | LIB_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 | ||
550 | endif | 552 | endif |
551 | 553 | ||
552 | ifdef NO_NEWT | 554 | ifdef 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 | |||
2 | PERF_HAVE_DWARF_REGS := 1 | 2 | PERF_HAVE_DWARF_REGS := 1 |
3 | LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o | 3 | LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o |
4 | endif | 4 | endif |
5 | ifndef NO_LIBUNWIND | ||
6 | LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind.o | ||
7 | endif | ||
5 | LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o | 8 | LIB_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 | ||
8 | int 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 | ||
72 | int 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); | |||
158 | void machine__exit(struct machine *self); | 158 | void machine__exit(struct machine *self); |
159 | void machine__delete(struct machine *self); | 159 | void machine__delete(struct machine *self); |
160 | 160 | ||
161 | struct perf_evsel; | ||
162 | struct perf_sample; | ||
161 | int machine__resolve_callchain(struct machine *machine, | 163 | int 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); |
165 | int maps__set_kallsyms_ref_reloc_sym(struct map **maps, const char *symbol_name, | 168 | int 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 | ||
20 | static int perf_session__open(struct perf_session *self, bool force) | 21 | static 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 | ||
292 | int machine__resolve_callchain(struct machine *self, | 293 | static 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 | ||
363 | static 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 | |||
370 | int 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 | |||
358 | static int process_event_synth_tracing_data_stub(union perf_event *event __used, | 397 | static 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 | ||
1536 | void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, | 1575 | void 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); | |||
129 | struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, | 129 | struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, |
130 | unsigned int type); | 130 | unsigned int type); |
131 | 131 | ||
132 | void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, | 132 | void 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 | ||
136 | int perf_session__cpu_bitmap(struct perf_session *session, | 136 | int 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 | ||
78 | struct addr_location; | 78 | struct addr_location; |
79 | 79 | ||
80 | struct perf_session; | ||
81 | |||
80 | struct scripting_ops { | 82 | struct 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 | |||
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 __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 | |||
318 | static 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 | |||
325 | static 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 | |||
332 | static int | ||
333 | get_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 | |||
341 | static 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 | |||
363 | static 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 | |||
380 | static 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 | |||
425 | static 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 | |||
457 | static 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 | |||
464 | static 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 | |||
486 | static 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 | |||
503 | static 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 | |||
514 | static 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 | |||
542 | int 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 | |||
8 | struct unwind_entry { | ||
9 | struct map *map; | ||
10 | struct symbol *sym; | ||
11 | u64 ip; | ||
12 | }; | ||
13 | |||
14 | typedef int (*unwind_entry_cb_t)(struct unwind_entry *entry, void *arg); | ||
15 | |||
16 | #ifndef NO_LIBUNWIND_SUPPORT | ||
17 | int 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); | ||
22 | int unwind__arch_reg_id(int regnum); | ||
23 | #else | ||
24 | static inline int | ||
25 | unwind__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 */ | ||