提交 3e25ac47 编写于 作者: K Kairui Song 提交者: Caspar Zhang

perf/x86: Make perf callchains work without CONFIG_FRAME_POINTER

commit d15d356887e770c5f2dcf963b52c7cb510c9e42d upstream.

Currently perf callchain doesn't work well with ORC unwinder
when sampling from trace point. We'll get useless in kernel callchain
like this:

perf  6429 [000]    22.498450:             kmem:mm_page_alloc: page=0x176a17 pfn=1534487 order=0 migratetype=0 gfp_flags=GFP_KERNEL
    ffffffffbe23e32e __alloc_pages_nodemask+0x22e (/lib/modules/5.1.0-rc3+/build/vmlinux)
	7efdf7f7d3e8 __poll+0x18 (/usr/lib64/libc-2.28.so)
	5651468729c1 [unknown] (/usr/bin/perf)
	5651467ee82a main+0x69a (/usr/bin/perf)
	7efdf7eaf413 __libc_start_main+0xf3 (/usr/lib64/libc-2.28.so)
    5541f689495641d7 [unknown] ([unknown])

The root cause is that, for trace point events, it doesn't provide a
real snapshot of the hardware registers. Instead perf tries to get
required caller's registers and compose a fake register snapshot
which suppose to contain enough information for start a unwinding.
However without CONFIG_FRAME_POINTER, if failed to get caller's BP as the
frame pointer, so current frame pointer is returned instead. We get
a invalid register combination which confuse the unwinder, and end the
stacktrace early.

So in such case just don't try dump BP, and let the unwinder start
directly when the register is not a real snapshot. Use SP
as the skip mark, unwinder will skip all the frames until it meet
the frame of the trace point caller.

Tested with frame pointer unwinder and ORC unwinder, this makes perf
callchain get the full kernel space stacktrace again like this:

perf  6503 [000]  1567.570191:             kmem:mm_page_alloc: page=0x16c904 pfn=1493252 order=0 migratetype=0 gfp_flags=GFP_KERNEL
    ffffffffb523e2ae __alloc_pages_nodemask+0x22e (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb52383bd __get_free_pages+0xd (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb52fd28a __pollwait+0x8a (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb521426f perf_poll+0x2f (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb52fe3e2 do_sys_poll+0x252 (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb52ff027 __x64_sys_poll+0x37 (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb500418b do_syscall_64+0x5b (/lib/modules/5.1.0-rc3+/build/vmlinux)
    ffffffffb5a0008c entry_SYSCALL_64_after_hwframe+0x44 (/lib/modules/5.1.0-rc3+/build/vmlinux)
	7f71e92d03e8 __poll+0x18 (/usr/lib64/libc-2.28.so)
	55a22960d9c1 [unknown] (/usr/bin/perf)
	55a22958982a main+0x69a (/usr/bin/perf)
	7f71e9202413 __libc_start_main+0xf3 (/usr/lib64/libc-2.28.so)
    5541f689495641d7 [unknown] ([unknown])
Co-developed-by: NJosh Poimboeuf <jpoimboe@redhat.com>
Signed-off-by: NKairui Song <kasong@redhat.com>
Signed-off-by: NPeter Zijlstra (Intel) <peterz@infradead.org>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Dave Young <dyoung@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: https://lkml.kernel.org/r/20190422162652.15483-1-kasong@redhat.comSigned-off-by: NIngo Molnar <mingo@kernel.org>
Signed-off-by: NShile Zhang <shile.zhang@linux.alibaba.com>
Acked-by: NJoseph Qi <joseph.qi@linux.alibaba.com>
上级 4cef0f14
...@@ -2343,6 +2343,15 @@ void arch_perf_update_userpage(struct perf_event *event, ...@@ -2343,6 +2343,15 @@ void arch_perf_update_userpage(struct perf_event *event,
cyc2ns_read_end(); cyc2ns_read_end();
} }
/*
* Determine whether the regs were taken from an irq/exception handler rather
* than from perf_arch_fetch_caller_regs().
*/
static bool perf_hw_regs(struct pt_regs *regs)
{
return regs->flags & X86_EFLAGS_FIXED;
}
void void
perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs) perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs)
{ {
...@@ -2354,11 +2363,15 @@ perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, struct pt_regs *re ...@@ -2354,11 +2363,15 @@ perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, struct pt_regs *re
return; return;
} }
if (perf_callchain_store(entry, regs->ip)) if (perf_hw_regs(regs)) {
return; if (perf_callchain_store(entry, regs->ip))
return;
unwind_start(&state, current, regs, NULL);
} else {
unwind_start(&state, current, NULL, (void *)regs->sp);
}
for (unwind_start(&state, current, regs, NULL); !unwind_done(&state); for (; !unwind_done(&state); unwind_next_frame(&state)) {
unwind_next_frame(&state)) {
addr = unwind_get_return_address(&state); addr = unwind_get_return_address(&state);
if (!addr || perf_callchain_store(entry, addr)) if (!addr || perf_callchain_store(entry, addr))
return; return;
......
...@@ -312,14 +312,9 @@ extern unsigned long perf_misc_flags(struct pt_regs *regs); ...@@ -312,14 +312,9 @@ extern unsigned long perf_misc_flags(struct pt_regs *regs);
*/ */
#define perf_arch_fetch_caller_regs(regs, __ip) { \ #define perf_arch_fetch_caller_regs(regs, __ip) { \
(regs)->ip = (__ip); \ (regs)->ip = (__ip); \
(regs)->bp = caller_frame_pointer(); \ (regs)->sp = (unsigned long)__builtin_frame_address(0); \
(regs)->cs = __KERNEL_CS; \ (regs)->cs = __KERNEL_CS; \
regs->flags = 0; \ regs->flags = 0; \
asm volatile( \
_ASM_MOV "%%"_ASM_SP ", %0\n" \
: "=m" ((regs)->sp) \
:: "memory" \
); \
} }
struct perf_guest_switch_msr { struct perf_guest_switch_msr {
......
...@@ -98,19 +98,6 @@ struct stack_frame_ia32 { ...@@ -98,19 +98,6 @@ struct stack_frame_ia32 {
u32 return_address; u32 return_address;
}; };
static inline unsigned long caller_frame_pointer(void)
{
struct stack_frame *frame;
frame = __builtin_frame_address(0);
#ifdef CONFIG_FRAME_POINTER
frame = frame->next_frame;
#endif
return (unsigned long)frame;
}
void show_opcodes(struct pt_regs *regs, const char *loglvl); void show_opcodes(struct pt_regs *regs, const char *loglvl);
void show_ip(struct pt_regs *regs, const char *loglvl); void show_ip(struct pt_regs *regs, const char *loglvl);
#endif /* _ASM_X86_STACKTRACE_H */ #endif /* _ASM_X86_STACKTRACE_H */
...@@ -1055,12 +1055,18 @@ static inline void perf_arch_fetch_caller_regs(struct pt_regs *regs, unsigned lo ...@@ -1055,12 +1055,18 @@ static inline void perf_arch_fetch_caller_regs(struct pt_regs *regs, unsigned lo
#endif #endif
/* /*
* Take a snapshot of the regs. Skip ip and frame pointer to * When generating a perf sample in-line, instead of from an interrupt /
* the nth caller. We only need a few of the regs: * exception, we lack a pt_regs. This is typically used from software events
* like: SW_CONTEXT_SWITCHES, SW_MIGRATIONS and the tie-in with tracepoints.
*
* We typically don't need a full set, but (for x86) do require:
* - ip for PERF_SAMPLE_IP * - ip for PERF_SAMPLE_IP
* - cs for user_mode() tests * - cs for user_mode() tests
* - bp for callchains * - sp for PERF_SAMPLE_CALLCHAIN
* - eflags, for future purposes, just in case * - eflags for MISC bits and CALLCHAIN (see: perf_hw_regs())
*
* NOTE: assumes @regs is otherwise already 0 filled; this is important for
* things like PERF_SAMPLE_REGS_INTR.
*/ */
static inline void perf_fetch_caller_regs(struct pt_regs *regs) static inline void perf_fetch_caller_regs(struct pt_regs *regs)
{ {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册