perf: user space sframe unwinding
Subsystems (10)
Josh Poimboeuf
maintainercover perf: user space sframe unwinding
Some distros have started compiling frame pointers into all their packages to enable the kernel to do system-wide profiling of user space. Unfortunately that creates a runtime performance penalty across the entire system. Using DWARF (or .eh_frame) instead isn't feasible because of complexity and slowness.
For in-kernel unwinding we solved this problem with the creation of the ORC unwinder for x86_64. Similarly, for user space the GNU assembler has created the SFrame ("Simple Frame") format starting with binutils 2.40.
These patches add support for unwinding user space from the kernel using SFrame with perf. It should be easy to add user unwinding support for other components like ftrace.
I tested it on Gentoo by recompiling everything with -Wa,-gsframe and using a custom glibc patch (which I'll send in a reply to this email).
The unwinding itself seems to work well, though I still have a major
problem: how to tell perf tool to stitch together the separate kernel+user callchains into a single event?
Right now I have a hack which somehow causes perf tool to overwrite the kernel callchain with the user one. I'm perf-clueless, any ideas or patches for a clean way to implement that would be most helpful.
Otherwise there were two main challenges:
1) Finding .sframe sections in shared/dlopened libraries
The kernel has no visibility to the contents of shared libraries. This was solved by adding a PR_ADD_SFRAME option to prctl() which allows the runtime linker to manually provide the in-memory address of an .sframe section to the kernel.
2) Dealing with page faults
Keeping all binaries' sframe data pinned would likely waste a lot of memory. Instead, read it from user space on demand. That can't be done from perf NMI context due to page faults, so defer the unwind to the next user exit. Since the NMI handler doesn't do exit work, self-IPI and then schedule task work to be run on exit from the IPI.
Special thanks to Indu for the original concept, and to Steven and Peter for helping a lot with the design. And to Steven for letting me do it ;-)
TODO:
- Stitch kernel+user events together in perf tool (help needed)
- Add arm64 support
- Add VDSO .sframe support
- Allow specifying FP vs sframe from perf tool? Right now it's auto-detected, maybe that's enough
- Port ftrace and others to use sframe
- Support sframe v2
- Determine the impact of missing DRAP support (aligned stacks which SFrame doesn't currently support)
- Add debugging hooks
Josh Poimboeuf (10):
perf: Remove get_perf_callchain() 'init_nr' argument
perf: Remove get_perf_callchain() 'crosstask' argument
perf: Simplify get_perf_callchain() user logic
perf: Introduce deferred user callchains perf/x86: Add HAVE_PERF_CALLCHAIN_DEFERRED
unwind: Introduce generic user space unwinding interfaces unwind/x86: Add HAVE_USER_UNWIND
perf/x86: Use user_unwind interface
unwind: Introduce SFrame user space unwinding unwind/x86/64: Add HAVE_USER_UNWIND_SFRAME
arch/Kconfig | 9 + arch/x86/Kconfig | 3 + arch/x86/events/core.c | 65 ++--- arch/x86/include/asm/mmu.h | 2 +- arch/x86/include/asm/user_unwind.h | 11 + fs/binfmt_elf.c | 46 +++- include/linux/mm_types.h | 3 + include/linux/perf_event.h | 24 +- include/linux/sframe.h | 46 ++++ include/linux/user_unwind.h | 33 +++ include/uapi/linux/elf.h | 1 + include/uapi/linux/perf_event.h | 1 + include/uapi/linux/prctl.h | 3 + kernel/Makefile | 1 + kernel/bpf/stackmap.c | 6 +- kernel/events/callchain.c | 39 ++- kernel/events/core.c | 96 ++++++- kernel/fork.c | 10 + kernel/sys.c | 11 + kernel/unwind/Makefile | 2 + kernel/unwind/sframe.c | 414 +++++++++++++++++++++++++++++ kernel/unwind/sframe.h | 217 +++++++++++++++ kernel/unwind/user.c | 86 ++++++ mm/init-mm.c | 2 + 24 files changed, 1060 insertions(+), 71 deletions(-) create mode 100644 arch/x86/include/asm/user_unwind.h create mode 100644 include/linux/sframe.h create mode 100644 include/linux/user_unwind.h create mode 100644 kernel/unwind/Makefile create mode 100644 kernel/unwind/sframe.c create mode 100644 kernel/unwind/sframe.h create mode 100644 kernel/unwind/user.c
Signature · 1 line
2.41.0
Josh Poimboeuf
maintainerRFC PATCH 1/10 perf: Remove get_perf_callchain() 'init_nr' argument
The 'init_nr' argument has double duty: it's used to initialize both the number of contexts and the number of stack entries. That's confusing and the callers always pass zero anyway. Hard code the zero.
--- include/linux/perf_event.h | 2 +- kernel/bpf/stackmap.c | 4 ++-- kernel/events/callchain.c | 12 ++++++------ kernel/events/core.c | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-)
include/linux/perf_event.h +1 -1
@@ -1533,7 +1533,7 @@ DECLARE_PER_CPU(struct perf_callchain_entry, perf_callchain_entry);extern void perf_callchain_user (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry * -get_perf_callchain (struct pt_regs *regs,u32 init_nr,bool kernel,bool user, +get_perf_callchain (struct pt_regs *regs,bool kernel,bool user,u32 max_stack,bool crosstask,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );
kernel/bpf/stackmap.c +2 -2
@@ -294,7 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs,0 , kernel, user, max_depth, + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace))
@@ -420,7 +420,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,else if (kernel && task) trace =get_callchain_entry_for_task (task, max_depth);else - trace =get_perf_callchain (regs,0 , kernel, user, max_depth, + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace)) goto err_fault;
kernel/events/callchain.c +6 -6
@@ -177,7 +177,7 @@ put_callchain_entry(int rctx) }struct perf_callchain_entry * -get_perf_callchain (struct pt_regs *regs,u32 init_nr,bool kernel,bool user, +get_perf_callchain (struct pt_regs *regs,bool kernel,bool user,u32 max_stack,bool crosstask,bool add_mark) {struct perf_callchain_entry *entry;
@@ -188,11 +188,11 @@ get_perf_callchain(struct pt_regs *regs, u32 init_nr, bool kernel, bool user,if (!entry)return NULL ; - ctx.entry = entry; - ctx.max_stack = max_stack; - ctx.nr = entry->nr = init_nr; - ctx.contexts =0 ; - ctx.contexts_maxed = false; + ctx.entry = entry; + ctx.max_stack = max_stack; + ctx.nr = entry->nr =0 ; + ctx.contexts =0 ; + ctx.contexts_maxed = false;if (kernel && !user_mode (regs)) {if (add_mark)
kernel/events/core.c +1 -1
@@ -7600,7 +7600,7 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs)if (!kernel && !user)return &__empty_callchain; - callchain =get_perf_callchain (regs,0 , kernel, user, + callchain =get_perf_callchain (regs, kernel, user, max_stack, crosstask, true);return callchain ?: &__empty_callchain; }
Signature · 1 line
2.41.0
Re: [PATCH RFC 01/10] perf: Remove get_perf_callchain() 'init_nr' argument
Hello,
Quoted 4 lines · Josh Poimboeuf
The '
init_nr' argument has double duty: it's used to initialize both the number of contexts and the number of stack entries. That's confusing and the callers always pass zero anyway. Hard code the zero.
IIRC it was used to skip a number of first stack entries in BPF. I changed the code to not use init_nr but forgot to update the perf code.
Thanks,
Namhyung
Quoted 90 lines
Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/perf_event.h | 2 +- kernel/bpf/stackmap.c | 4 ++-- kernel/events/callchain.c | 12 ++++++------ kernel/events/core.c | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-)include/linux/perf_event.h +1
-1@@ -1533,7 +1533,7 @@ DECLARE_PER_CPU(struct perf_callchain_entry, perf_callchain_entry);extern void perf_callchain_user (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry * -get_perf_callchain (struct pt_regs *regs,u32 init_nr,bool kernel,bool user, +get_perf_callchain (struct pt_regs *regs,bool kernel,bool user,u32 max_stack,bool crosstask,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );kernel/bpf/stackmap.c +2
-2@@ -294,7 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs,0 , kernel, user, max_depth, + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace))@@ -420,7 +420,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,else if (kernel && task) trace =get_callchain_entry_for_task (task, max_depth);else - trace =get_perf_callchain (regs,0 , kernel, user, max_depth, + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace)) goto err_fault;kernel/events/callchain.c +6
-6@@ -177,7 +177,7 @@ put_callchain_entry(int rctx) }struct perf_callchain_entry * -get_perf_callchain (struct pt_regs *regs,u32 init_nr,bool kernel,bool user, +get_perf_callchain (struct pt_regs *regs,bool kernel,bool user,u32 max_stack,bool crosstask,bool add_mark) {struct perf_callchain_entry *entry;@@ -188,11 +188,11 @@ get_perf_callchain(struct pt_regs *regs, u32 init_nr, bool kernel, bool user,if (!entry)return NULL ; - ctx.entry = entry; - ctx.max_stack = max_stack; - ctx.nr = entry->nr = init_nr; - ctx.contexts =0 ; - ctx.contexts_maxed = false; + ctx.entry = entry; + ctx.max_stack = max_stack; + ctx.nr = entry->nr =0 ; + ctx.contexts =0 ; + ctx.contexts_maxed = false;if (kernel && !user_mode (regs)) {if (add_mark)kernel/events/core.c +1
-2@@ -7600,7 +7600,7 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs)if (!kernel && !user)return &__empty_callchain; - callchain =get_perf_callchain (regs,0 , kernel, user, + callchain =get_perf_callchain (regs, kernel, user, max_stack, crosstask, true);return callchain ?: &__empty_callchain; } --2.41.0
Josh Poimboeuf
maintainerRFC PATCH 2/10 perf: Remove get_perf_callchain() 'crosstask' argument
In reply to Josh Poimboeufget_perf_callchain() doesn't support cross-task unwinding, so it doesn't make much sense to have 'crosstask' as an argument. Instead, have perf_callchain() adjust 'user' accordingly.
--- include/linux/perf_event.h | 2 +- kernel/bpf/stackmap.c | 5 ++--- kernel/events/callchain.c | 6 +----- kernel/events/core.c | 8 ++++---- 4 files changed, 8 insertions(+), 13 deletions(-)
include/linux/perf_event.h +1 -1
@@ -1534,7 +1534,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark); +u32 max_stack,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);
kernel/bpf/stackmap.c +2 -3
@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + trace =get_perf_callchain (regs, kernel, user, max_depth, false);if (unlikely (!trace))/* couldn't fetch the stack trace */
@@ -421,7 +420,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + false);if (unlikely (!trace)) goto err_fault;
kernel/events/callchain.c +1 -5
@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark) +u32 max_stack,bool add_mark) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;
@@ -209,9 +209,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, }if (regs) { -if (crosstask) - goto exit_put; -if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );
@@ -219,7 +216,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, } } -exit_put:put_callchain_entry (rctx);return entry;
kernel/events/core.c +4 -4
@@ -7592,16 +7592,16 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) {bool kernel = !event->attr.exclude_callchain_kernel;bool user = !event->attr.exclude_callchain_user; -/* Disallow cross-task user callchains. */ -bool crosstask = event->ctx->task && event->ctx->task != current;const u32 max_stack = event->attr.sample_max_stack;struct perf_callchain_entry *callchain; +/* Disallow cross-task user callchains. */ + user &= !event->ctx->task || event->ctx->task == current; +if (!kernel && !user)return &__empty_callchain; - callchain =get_perf_callchain (regs, kernel, user, - max_stack, crosstask, true); + callchain =get_perf_callchain (regs, kernel, user, max_stack, true);return callchain ?: &__empty_callchain; }
Signature · 1 line
2.41.0
Re: [PATCH RFC 02/10] perf: Remove get_perf_callchain() 'crosstask' argument
Quoted 2 lines · Josh Poimboeuf
get_perf_callchain()doesn't support cross-task unwinding, so it doesn't
For only user stacks, but it seems there's no place to get cross-task kernel stacks too.
Quoted 4 lines
make much sense to have 'crosstask' as an argument. Instead, have
perf_callchain()adjust 'user' accordingly.Signed-off-by Josh Poimboeuf <[email protected]>
Thanks,
Namhyung
Quoted 102 lines
--- include/linux/perf_event.h | 2 +- kernel/bpf/stackmap.c | 5 ++--- kernel/events/callchain.c | 6 +----- kernel/events/core.c | 8 ++++---- 4 files changed, 8 insertions(+), 13 deletions(-)include/linux/perf_event.h +1
-1@@ -1534,7 +1534,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark); +u32 max_stack,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);kernel/bpf/stackmap.c +2
-3@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + trace =get_perf_callchain (regs, kernel, user, max_depth, false);if (unlikely (!trace))/* couldn't fetch the stack trace */ @@ -421,7 +420,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + false);if (unlikely (!trace)) goto err_fault;kernel/events/callchain.c +1
-5@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark) +u32 max_stack,bool add_mark) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;@@ -209,9 +209,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, }if (regs) { -if (crosstask) - goto exit_put; -if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );@@ -219,7 +216,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, } } -exit_put:put_callchain_entry (rctx);return entry;kernel/events/core.c +4
-5@@ -7592,16 +7592,16 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) {bool kernel = !event->attr.exclude_callchain_kernel;bool user = !event->attr.exclude_callchain_user; -/* Disallow cross-task user callchains. */ -bool crosstask = event->ctx->task && event->ctx->task != current;const u32 max_stack = event->attr.sample_max_stack;struct perf_callchain_entry *callchain; +/* Disallow cross-task user callchains. */ + user &= !event->ctx->task || event->ctx->task == current; +if (!kernel && !user)return &__empty_callchain; - callchain =get_perf_callchain (regs, kernel, user, - max_stack, crosstask, true); + callchain =get_perf_callchain (regs, kernel, user, max_stack, true);return callchain ?: &__empty_callchain; } --2.41.0
Re: [PATCH RFC 02/10] perf: Remove get_perf_callchain() 'crosstask' argument
Quoted 7 lines · Namhyung Kim
Quoted 2 lines · Josh Poimboeuf
get_perf_callchain()doesn't support cross-task unwinding, so it doesn'tFor only user stacks, but it seems there's no place to get cross-task kernel stacks too.
There is bpf_get_task_stack in kernel/bpf/stackmap.c. This can be called inside of a BPF task iterator, where you can get the kernel stacks for every task on the host. But as this change points out, this doesn't work for crosstask user stack unwinding. I have a similar patch that just exits early in this case:
https://lore.kernel.org/linux-perf-users/[email protected]/
Though I'm not opposed with just removing the *crosstask* param entirely as a similar check was just added in the bpf tree for bpf_get_task_stack:
https://lore.kernel.org/bpf/[email protected]/
Quoted 112 lines
Quoted 4 lines
make much sense to have 'crosstask' as an argument. Instead, have
perf_callchain()adjust 'user' accordingly.Signed-off-by Josh Poimboeuf <[email protected]>Acked-by Namhyung Kim <[email protected]>Thanks,
NamhyungQuoted 102 lines
--- include/linux/perf_event.h | 2 +- kernel/bpf/stackmap.c | 5 ++--- kernel/events/callchain.c | 6 +----- kernel/events/core.c | 8 ++++---- 4 files changed, 8 insertions(+), 13 deletions(-)include/linux/perf_event.h +1
-1@@ -1534,7 +1534,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark); +u32 max_stack,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);kernel/bpf/stackmap.c +2
-3@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + trace =get_perf_callchain (regs, kernel, user, max_depth, false);if (unlikely (!trace))/* couldn't fetch the stack trace */ @@ -421,7 +420,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false, false); + false);if (unlikely (!trace)) goto err_fault;kernel/events/callchain.c +1
-5@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark) +u32 max_stack,bool add_mark) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;@@ -209,9 +209,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, }if (regs) { -if (crosstask) - goto exit_put; -if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );@@ -219,7 +216,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, } } -exit_put:put_callchain_entry (rctx);return entry;kernel/events/core.c +4
-5@@ -7592,16 +7592,16 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) {bool kernel = !event->attr.exclude_callchain_kernel;bool user = !event->attr.exclude_callchain_user; -/* Disallow cross-task user callchains. */ -bool crosstask = event->ctx->task && event->ctx->task != current;const u32 max_stack = event->attr.sample_max_stack;struct perf_callchain_entry *callchain; +/* Disallow cross-task user callchains. */ + user &= !event->ctx->task || event->ctx->task == current; +if (!kernel && !user)return &__empty_callchain; - callchain =get_perf_callchain (regs, kernel, user, - max_stack, crosstask, true); + callchain =get_perf_callchain (regs, kernel, user, max_stack, true);return callchain ?: &__empty_callchain; } --2.41.0
Josh Poimboeuf
maintainerRFC PATCH 3/10 perf: Simplify get_perf_callchain() user logic
In reply to Josh PoimboeufSimplify the get_perf_callchain() user logic a bit. task_pt_regs() should never be NULL.
--- kernel/events/callchain.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)
kernel/events/callchain.c +7 -9
@@ -202,20 +202,18 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user,if (user) {if (!user_mode (regs)) { -if (current->mm) - regs =task_pt_regs (current); -else - regs =NULL ; +if (!current->mm) + goto exit_put; + regs =task_pt_regs (current); } -if (regs) { -if (add_mark) -perf_callchain_store_context (&ctx,PERF_CONTEXT_USER ); +if (add_mark) +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER ); -perf_callchain_user (&ctx, regs); - } +perf_callchain_user (&ctx, regs); } +exit_put:put_callchain_entry (rctx);return entry;
Signature · 1 line
2.41.0
Re: [PATCH RFC 03/10] perf: Simplify get_perf_callchain() user logic
Quoted 5 lines · Josh Poimboeuf
Simplify the
get_perf_callchain()user logic a bit.task_pt_regs()should never be NULL.Signed-off-by Josh Poimboeuf <[email protected]>
Thanks,
Namhyung
Quoted 39 lines
--- kernel/events/callchain.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)kernel/events/callchain.c +7
-10@@ -202,20 +202,18 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user,if (user) {if (!user_mode (regs)) { -if (current->mm) - regs =task_pt_regs (current); -else - regs =NULL ; +if (!current->mm) + goto exit_put; + regs =task_pt_regs (current); } -if (regs) { -if (add_mark) -perf_callchain_store_context (&ctx,PERF_CONTEXT_USER ); +if (add_mark) +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER ); -perf_callchain_user (&ctx, regs); - } +perf_callchain_user (&ctx, regs); } +exit_put:put_callchain_entry (rctx);return entry; --2.41.0
Josh Poimboeuf
maintainerRFC PATCH 4/10 perf: Introduce deferred user callchains
In reply to Josh PoimboeufInstead of attempting to unwind user space from the NMI handler, defer it to run in task context by sending a self-IPI and then scheduling the unwind to run in the IRQ's exit task work before returning to user space.
This allows the user stack page to be paged in if needed, avoids duplicate unwinds for kernel-bound workloads, and prepares for SFrame unwinding (so .sframe sections can be paged in on demand).
--- arch/Kconfig | 3 ++ include/linux/perf_event.h | 22 ++++++-- include/uapi/linux/perf_event.h | 1 + kernel/bpf/stackmap.c | 5 +- kernel/events/callchain.c | 7 ++- kernel/events/core.c | 90 ++++++++++++++++++++++++++++++--- 6 files changed, 115 insertions(+), 13 deletions(-)
arch/Kconfig +3 -0
@@ -425,6 +425,9 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH It uses the same command line parameters, and sysctl interface, as the generic hardlockup detectors. +config HAVE_PERF_CALLCHAIN_DEFERRED + bool + config HAVE_PERF_REGS bool help
include/linux/perf_event.h +19 -3
@@ -786,6 +786,7 @@ struct perf_event {struct irq_work pending_irq;struct callback_head pending_task;unsigned int pending_work; +unsigned int pending_unwind;atomic_t event_limit;
@@ -1113,7 +1114,10 @@ int perf_event_read_local(struct perf_event *event, u64 *value,extern u64 perf_event_read_value (struct perf_event *event,u64 *enabled,u64 *running); -extern struct perf_callchain_entry *perf_callchain (struct perf_event *event,struct pt_regs *regs); +extern void perf_callchain (struct perf_sample_data *data, +struct perf_event *event,struct pt_regs *regs); +extern void perf_callchain_deferred (struct perf_sample_data *data, +struct perf_event *event,struct pt_regs *regs);static inline bool branch_sample_no_flags (const struct perf_event *event) {
@@ -1189,6 +1193,7 @@ struct perf_sample_data {u64 data_page_size;u64 code_page_size;u64 aux_size; +bool deferred; }____cacheline_aligned ;/* default value for data source */
@@ -1206,6 +1211,7 @@ static inline void perf_sample_data_init(struct perf_sample_data *data, data->sample_flags =PERF_SAMPLE_PERIOD ; data->period = period; data->dyn_size =0 ; + data->deferred = false;if (addr) { data->addr = addr;
@@ -1219,7 +1225,11 @@ static inline void perf_sample_save_callchain(struct perf_sample_data *data, {int size =1 ; - data->callchain =perf_callchain (event, regs); +if (data->deferred) +perf_callchain_deferred (data, event, regs); +else +perf_callchain (data, event, regs); + size += data->callchain->nr; data->dyn_size += size *sizeof (u64);
@@ -1534,12 +1544,18 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark); +u32 max_stack,bool add_mark,bool defer_user);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);extern void put_callchain_entry (int rctx); +#ifdef CONFIG_HAVE_PERF_CALLCHAIN_DEFERRED +extern void perf_callchain_user_deferred (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs); +#else +static inline void perf_callchain_user_deferred (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs) {} +#endif +extern int sysctl_perf_event_max_stack;extern int sysctl_perf_event_max_contexts_per_stack;
include/uapi/linux/perf_event.h +1 -0
@@ -1237,6 +1237,7 @@ enum perf_callchain_context {PERF_CONTEXT_HV = (__u64)-32 ,PERF_CONTEXT_KERNEL = (__u64)-128 ,PERF_CONTEXT_USER = (__u64)-512 , +PERF_CONTEXT_USER_DEFERRED = (__u64)-640 ,PERF_CONTEXT_GUEST = (__u64)-2048 , PERF_CONTEXT_GUEST_KERNEL = (__u64)-2176,
kernel/bpf/stackmap.c +2 -3
@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs, kernel, user, max_depth, false); - + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace))/* couldn't fetch the stack trace */ return -EFAULT ;
@@ -420,7 +419,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false); + false, false);if (unlikely (!trace)) goto err_fault;
kernel/events/callchain.c +6 -1
@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark) +u32 max_stack,bool add_mark,bool defer_user) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;
@@ -207,6 +207,11 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs =task_pt_regs (current); } +if (defer_user) { +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER_DEFERRED ); + goto exit_put; + } +if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );
kernel/events/core.c +84 -6
123 lines not on this page · raw message
Signature · 1 line
2.41.0
Namhyung Kim
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 135 lines · Josh Poimboeuf
Instead of attempting to unwind user space from the NMI handler, defer it to run in task context by sending a self-IPI and then scheduling the unwind to run in the IRQ's exit task work before returning to user space.
This allows the user stack page to be paged in if needed, avoids duplicate unwinds for kernel-bound workloads, and prepares for SFrame unwinding (so .sframe sections can be paged in on demand).
Suggested-by Steven Rostedt <[email protected]>Suggested-by Peter Zijlstra <[email protected]>Signed-off-by Josh Poimboeuf <[email protected]>--- arch/Kconfig | 3 ++ include/linux/perf_event.h | 22 ++++++-- include/uapi/linux/perf_event.h | 1 + kernel/bpf/stackmap.c | 5 +- kernel/events/callchain.c | 7 ++- kernel/events/core.c | 90 ++++++++++++++++++++++++++++++--- 6 files changed, 115 insertions(+), 13 deletions(-)arch/Kconfig +3
-0@@ -425,6 +425,9 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH It uses the same command line parameters, and sysctl interface, as the generic hardlockup detectors. +config HAVE_PERF_CALLCHAIN_DEFERRED + bool + config HAVE_PERF_REGS bool helpinclude/linux/perf_event.h +19
-3@@ -786,6 +786,7 @@ struct perf_event {struct irq_work pending_irq;struct callback_head pending_task;unsigned int pending_work; +unsigned int pending_unwind;atomic_t event_limit;@@ -1113,7 +1114,10 @@ int perf_event_read_local(struct perf_event *event, u64 *value,extern u64 perf_event_read_value (struct perf_event *event,u64 *enabled,u64 *running); -extern struct perf_callchain_entry *perf_callchain (struct perf_event *event,struct pt_regs *regs); +extern void perf_callchain (struct perf_sample_data *data, +struct perf_event *event,struct pt_regs *regs); +extern void perf_callchain_deferred (struct perf_sample_data *data, +struct perf_event *event,struct pt_regs *regs);static inline bool branch_sample_no_flags (const struct perf_event *event) {@@ -1189,6 +1193,7 @@ struct perf_sample_data {u64 data_page_size;u64 code_page_size;u64 aux_size; +bool deferred; }____cacheline_aligned ;/* default value for data source */ @@ -1206,6 +1211,7 @@ static inline void perf_sample_data_init(struct perf_sample_data *data, data->sample_flags =PERF_SAMPLE_PERIOD ; data->period = period; data->dyn_size =0 ; + data->deferred = false;if (addr) { data->addr = addr;@@ -1219,7 +1225,11 @@ static inline void perf_sample_save_callchain(struct perf_sample_data *data, {int size =1 ; - data->callchain =perf_callchain (event, regs); +if (data->deferred) +perf_callchain_deferred (data, event, regs); +else +perf_callchain (data, event, regs); + size += data->callchain->nr; data->dyn_size += size *sizeof (u64);@@ -1534,12 +1544,18 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark); +u32 max_stack,bool add_mark,bool defer_user);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);extern void put_callchain_entry (int rctx); +#ifdef CONFIG_HAVE_PERF_CALLCHAIN_DEFERRED +extern void perf_callchain_user_deferred (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs); +#else +static inline void perf_callchain_user_deferred (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs) {} +#endif +extern int sysctl_perf_event_max_stack;extern int sysctl_perf_event_max_contexts_per_stack;include/uapi/linux/perf_event.h +1
-0@@ -1237,6 +1237,7 @@ enum perf_callchain_context {PERF_CONTEXT_HV = (__u64)-32 ,PERF_CONTEXT_KERNEL = (__u64)-128 ,PERF_CONTEXT_USER = (__u64)-512 , +PERF_CONTEXT_USER_DEFERRED = (__u64)-640 ,PERF_CONTEXT_GUEST = (__u64)-2048 , PERF_CONTEXT_GUEST_KERNEL = (__u64)-2176,kernel/bpf/stackmap.c +1
-2@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack; - trace =get_perf_callchain (regs, kernel, user, max_depth, false); - + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace))/* couldn't fetch the stack trace */ return -EFAULT ;
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 422
Quoted 5 lines
kernel/bpf/stackmap.c +1
-1@@ -420,7 +419,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false); + false, false);
Hmm.. BPF is not supported. Any plans?
Quoted 17 lines
kernel/bpf/stackmap.c +0
-0if (unlikely (!trace)) goto err_fault;kernel/events/callchain.c +1
-1@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark) +u32 max_stack,bool add_mark,bool defer_user) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;
kernel/events/callchain.c get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, · line 212
Quoted 6 lines
kernel/events/callchain.c +3
-0@@ -207,6 +207,11 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs =task_pt_regs (current); } +if (defer_user) { +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER_DEFERRED ); + goto exit_put;
Hmm.. ok. Then now perf tools need to stitch the call stacks in two (or more?) samples.
Quoted 10 lines
kernel/events/callchain.c +2
-0+ } +if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );kernel/events/core.c +0
-0
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6756
Quoted 6 lines
kernel/events/core.c +3
-0@@ -6751,6 +6751,12 @@ static void perf_pending_irq(struct irq_work *entry) struct perf_event *event = container_of(entry, struct perf_event, pending_irq);int rctx; +if (!is_software_event (event)) { +if (event->pending_unwind) +task_work_add (current, &event->pending_task,TWA_RESUME );
I'm not familiar with this code. Is it possible to the current task is preempted before returning to user space (and run the perf_pending_task_unwind) ?
Quoted 12 lines
kernel/events/core.c +5
-0+return ; + } +/* * If we 'fail' here, that's OK, it means recursion is already disabled * and we won't recurse 'further'. @@ -6772,11 +6778,57 @@ static void perf_pending_irq(struct irq_work *entry)perf_swevent_put_recursion_context (rctx); } +static void perf_pending_task_unwind (struct perf_event *event) +{
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6792
Quoted 10 lines
kernel/events/core.c +10
-0+struct pt_regs *regs =task_pt_regs (current); +struct perf_output_handle handle; +struct perf_event_header header; +struct perf_sample_data data; +struct perf_callchain_entry *callchain; + + callchain =kmalloc (sizeof (struct perf_callchain_entry ) + + (sizeof (__u64) * event->attr.sample_max_stack) + + (sizeof (__u64) *1 )/* one context */ , +GFP_KERNEL );
Any chance it can reuse get_perf_callchain() instead of allocating the callchains every time?
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6799
Quoted 7 lines
kernel/events/core.c +7
-0+if (!callchain) +return ; + + callchain->nr =0 ; + data.callchain = callchain; + +perf_sample_data_init (&data,0 , event->hw.last_period);
It would double count the period for a sample.
I guess we want to set the period to 0.
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6803
Quoted 4 lines
kernel/events/core.c +4
-0+ + data.deferred = true; + +perf_prepare_sample (&data, event, regs);
I don't think this would work properly. Not to mention it duplicates sample data unnecessarily, some data needs special handling to avoid inefficient (full) data copy like for (user) stack, regs and aux.
Anyway I'm not sure it can support these additional samples for deferred callchains without breaking the existing perf tools. Anyway it doesn't know PERF_CONTEXT_USER_DEFERRED at least. I think this should be controlled by a new feature bit in the perf_event_attr.
Then we might add a breaking change to have a special sample record for the deferred callchains and sample ID only.
Quoted 35 lines
kernel/events/core.c +23
-0+ +perf_prepare_header (&header, &data, event, regs); + +if (perf_output_begin (&handle, &data, event, header.size)) + goto done; + +perf_output_sample (&handle, &header, &data, event); + +perf_output_end (&handle); + +done: +kfree (callchain); +} + +static void perf_pending_task (struct callback_head *head) { struct perf_event *event = container_of(head, struct perf_event, pending_task);int rctx; +if (!is_software_event (event)) { +if (event->pending_unwind) { +perf_pending_task_unwind (event); + event->pending_unwind =0 ; + } +return ; + } +/* * If we 'fail' here, that's OK, it means recursion is already disabled * and we won't recurse 'further'. @@ -7587,22 +7639,48 @@ static u64 perf_get_page_size(unsigned long addr)static struct perf_callchain_entry __empty_callchain = { .nr =0 , };
kernel/events/core.c static u64 perf_get_page_size(unsigned long addr) · line 7648
Quoted 10 lines
kernel/events/core.c +3
-3-struct perf_callchain_entry * -perf_callchain (struct perf_event *event,struct pt_regs *regs) +void perf_callchain (struct perf_sample_data *data,struct perf_event *event, +struct pt_regs *regs) {bool kernel = !event->attr.exclude_callchain_kernel;bool user = !event->attr.exclude_callchain_user;const u32 max_stack = event->attr.sample_max_stack; -struct perf_callchain_entry *callchain; +bool defer_user =IS_ENABLED (CONFIG_HAVE_PERF_CALLCHAIN_DEFERRED );
Is it always enabled depending on the kernel config? It could be controlled by event->attr.something..
Thanks,
Namhyung
Quoted 45 lines
kernel/events/core.c +29
-4/* Disallow cross-task user callchains. */ user &= !event->ctx->task || event->ctx->task == current;if (!kernel && !user) -return &__empty_callchain; + goto empty; - callchain =get_perf_callchain (regs, kernel, user, max_stack, true); -return callchain ?: &__empty_callchain; + data->callchain =get_perf_callchain (regs, kernel, user, max_stack, true, defer_user); +if (!data->callchain) + goto empty; + +if (user && defer_user && !event->pending_unwind) { + event->pending_unwind =1 ; +irq_work_queue (&event->pending_irq); + } + +return ; + +empty: + data->callchain = &__empty_callchain; +} + +void perf_callchain_deferred (struct perf_sample_data *data, +struct perf_event *event,struct pt_regs *regs) +{ +struct perf_callchain_entry_ctx ctx; + + ctx.entry = data->callchain; + ctx.max_stack = event->attr.sample_max_stack; + ctx.nr =0 ; + ctx.contexts =0 ; + ctx.contexts_maxed = false; + +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER ); +perf_callchain_user_deferred (&ctx, regs); } static __always_inline u64 __cond_set(u64 flags, u64 s, u64 d) --2.41.0
Josh Poimboeuf
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 6 lines · Namhyung Kim
Quoted 5 lines · Josh Poimboeuf
+++
b/kernel/bpf/stackmap.ckernel/bpf/stackmap.c +0
-0@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack;
Quoted 14 lines
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 422
Quoted 12 lines
kernel/bpf/stackmap.c +2
-3- trace =get_perf_callchain (regs, kernel, user, max_depth, false); - + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace))/* couldn't fetch the stack trace */ return -EFAULT ;@@ -420,7 +419,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false); + false, false);Hmm.. BPF is not supported. Any plans?
I'm not sure whether this feature makes sense for BPF. If the BPF program runs in atomic context then it would have to defer the unwind until right before exiting to user space. But then the program is no longer running.
Quoted 14 lines
Quoted 14 lines
kernel/events/callchain.c +2
-1+++ b/kernel/events/callchain.c@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark) +u32 max_stack,bool add_mark,bool defer_user) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;@@ -207,6 +207,11 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs =task_pt_regs (current); }
Quoted 6 lines
kernel/events/callchain.c get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, · line 212
Quoted 3 lines
kernel/events/callchain.c +3
-0+if (defer_user) { +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER_DEFERRED ); + goto exit_put;Hmm.. ok. Then now perf tools need to stitch the call stacks in two (or more?) samples.
Yes. And it could be more than two samples if there were multiple NMIs before returning to user space. In that case there would be a single user sample which needs to be stitched to each of the kernel samples.
Quoted 13 lines
Quoted 13 lines
kernel/events/callchain.c +2
-0+ } +if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );kernel/events/core.c +0
-0@@ -6751,6 +6751,12 @@ static void perf_pending_irq(struct irq_work *entry) struct perf_event *event = container_of(entry, struct perf_event, pending_irq);int rctx;
Quoted 7 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6756
Quoted 3 lines
kernel/events/core.c +3
-0+if (!is_software_event (event)) { +if (event->pending_unwind) +task_work_add (current, &event->pending_task,TWA_RESUME );I'm not familiar with this code. Is it possible to the current task is preempted before returning to user space (and run the
perf_pending_task_unwind) ?
Yes, the task work (perf_pending_task_unwind) is preemptible.
Quoted 7 lines
Quoted 7 lines
kernel/events/core.c +7
-0+static void perf_pending_task_unwind (struct perf_event *event) +{ +struct pt_regs *regs =task_pt_regs (current); +struct perf_output_handle handle; +struct perf_event_header header; +struct perf_sample_data data; +struct perf_callchain_entry *callchain;
Quoted 8 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6792
Quoted 5 lines
kernel/events/core.c +5
-0+ + callchain =kmalloc (sizeof (struct perf_callchain_entry ) + + (sizeof (__u64) * event->attr.sample_max_stack) + + (sizeof (__u64) *1 )/* one context */ , +GFP_KERNEL );Any chance it can reuse
get_perf_callchain()instead of allocating the callchains every time?
I don't think so, because if it gets preempted, the new task might also need to do an unwind. But there's only one task-context callchain per CPU.
The allocation is less than ideal. I could just allocate it on the stack, and keep the number of entries bounded to 128 entries or so.
Quoted 10 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6799
Quoted 7 lines
kernel/events/core.c +7
-0+if (!callchain) +return ; + + callchain->nr =0 ; + data.callchain = callchain; + +perf_sample_data_init (&data,0 , event->hw.last_period);It would double count the period for a sample.
I guess we want to set the period to 0.
Ok.
Quoted 8 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6803
Quoted 4 lines
kernel/events/core.c +4
-0+ + data.deferred = true; + +perf_prepare_sample (&data, event, regs);I don't think this would work properly. Not to mention it duplicates sample data unnecessarily, some data needs special handling to avoid inefficient (full) data copy like for (user) stack, regs and aux.
Yeah. I tried sending only the sample ID and callchain, but perf tool didn't appreciate that ;-) So for the RFC I gave up and did this.
Quoted 8 lines
Anyway I'm not sure it can support these additional samples for deferred callchains without breaking the existing perf tools. Anyway it doesn't know
PERF_CONTEXT_USER_DEFERREDat least. I think this should be controlled by a new feature bit in theperf_event_attr.Then we might add a breaking change to have a special sample record for the deferred callchains and sample ID only.
Sounds like a good idea. I'll need to study the code to figure out how to do that on the perf tool side. Or would you care to write a patch?
Quoted 5 lines
Quoted 5 lines
kernel/events/core.c +2
-2-struct perf_callchain_entry * -perf_callchain (struct perf_event *event,struct pt_regs *regs) +void perf_callchain (struct perf_sample_data *data,struct perf_event *event, +struct pt_regs *regs) {
Quoted 8 lines
kernel/events/core.c static u64 perf_get_page_size(unsigned long addr) · line 7648
Quoted 5 lines
bool kernel = !event->attr.exclude_callchain_kernel; bool user = !event->attr.exclude_callchain_user; const u32 max_stack = event->attr.sample_max_stack;kernel/events/core.c +1
-1-struct perf_callchain_entry *callchain; +bool defer_user =IS_ENABLED (CONFIG_HAVE_PERF_CALLCHAIN_DEFERRED );Is it always enabled depending on the kernel config? It could be controlled by
event->attr.something..
Possibly, depending on what y'all think. Also, fp vs sframe could be an attr (though sframe implies deferred).
Thanks for the review!
Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 17 lines · Josh Poimboeuf
Quoted 7 lines · Namhyung Kim
Quoted 7 lines
kernel/events/core.c +7
-0+static void perf_pending_task_unwind (struct perf_event *event) +{ +struct pt_regs *regs =task_pt_regs (current); +struct perf_output_handle handle; +struct perf_event_header header; +struct perf_sample_data data; +struct perf_callchain_entry *callchain;Quoted 8 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6792
Quoted 5 lines
kernel/events/core.c +5
-0+ + callchain =kmalloc (sizeof (struct perf_callchain_entry ) + + (sizeof (__u64) * event->attr.sample_max_stack) + + (sizeof (__u64) *1 )/* one context */ , +GFP_KERNEL );Any chance it can reuse
get_perf_callchain()instead of allocating the callchains every time?
Quoted 3 lines
I don't think so, because if it gets preempted, the new task might also need to do an unwind. But there's only one task-context callchain per CPU.
BTW it's not just preemption, this code can also block when the unwinder tries to copy from user space. So disabling preemption isn't an option.
Signature · 1 line
Josh
Namhyung Kim
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
In reply to Josh PoimboeufQuoted 23 lines · Josh Poimboeuf
Quoted 6 lines · Namhyung Kim
Quoted 5 lines · Josh Poimboeuf
+++
b/kernel/bpf/stackmap.ckernel/bpf/stackmap.c +0
-0@@ -294,8 +294,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,if (max_depth > sysctl_perf_event_max_stack) max_depth = sysctl_perf_event_max_stack;Quoted 14 lines
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 422
Quoted 12 lines
kernel/bpf/stackmap.c +2
-3- trace =get_perf_callchain (regs, kernel, user, max_depth, false); - + trace =get_perf_callchain (regs, kernel, user, max_depth, false, false);if (unlikely (!trace))/* couldn't fetch the stack trace */ return -EFAULT ;@@ -420,7 +419,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace =get_callchain_entry_for_task (task, max_depth); else trace =get_perf_callchain (regs, kernel, user, max_depth, - false); + false, false);Hmm.. BPF is not supported. Any plans?
Quoted 4 lines
I'm not sure whether this feature makes sense for BPF. If the BPF program runs in atomic context then it would have to defer the unwind until right before exiting to user space. But then the program is no longer running.
Ok, then BPF gets no user call stacks even with sframes.
Quoted 22 lines
Quoted 15 lines
Quoted 15 lines
kernel/events/callchain.c +2
-1+++ b/kernel/events/callchain.c@@ -178,7 +178,7 @@ put_callchain_entry(int rctx)struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark) +u32 max_stack,bool add_mark,bool defer_user) {struct perf_callchain_entry *entry;struct perf_callchain_entry_ctx ctx;@@ -207,6 +207,11 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs =task_pt_regs (current); }Quoted 6 lines
kernel/events/callchain.c get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, · line 212
Quoted 3 lines
kernel/events/callchain.c +3
-0+if (defer_user) { +perf_callchain_store_context (&ctx,PERF_CONTEXT_USER_DEFERRED ); + goto exit_put;Hmm.. ok. Then now perf tools need to stitch the call stacks in two (or more?) samples.
Quoted 3 lines
Yes. And it could be more than two samples if there were multiple NMIs before returning to user space. In that case there would be a single user sample which needs to be stitched to each of the kernel samples.
Ok, but ...
Quoted 22 lines
Quoted 14 lines
Quoted 14 lines
kernel/events/callchain.c +2
-0+ } +if (add_mark)perf_callchain_store_context (&ctx,PERF_CONTEXT_USER );kernel/events/core.c +0
-0@@ -6751,6 +6751,12 @@ static void perf_pending_irq(struct irq_work *entry) struct perf_event *event = container_of(entry, struct perf_event, pending_irq);int rctx;Quoted 7 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6756
Quoted 3 lines
kernel/events/core.c +3
-0+if (!is_software_event (event)) { +if (event->pending_unwind) +task_work_add (current, &event->pending_task,TWA_RESUME );I'm not familiar with this code. Is it possible to the current task is preempted before returning to user space (and run the
perf_pending_task_unwind) ?
Quoted 1 line
Yes, the task work (
perf_pending_task_unwind) is preemptible.
If the task is preempted, the call stack would be from another task (if it also has the pending call stacks) and we need to check which user call stack matches which kernel call stack. So there's no guarantee we can just use adjacent samples.
Quoted 17 lines
Quoted 16 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6792
Quoted 13 lines
kernel/events/core.c +12
-0+static void perf_pending_task_unwind (struct perf_event *event) +{ +struct pt_regs *regs =task_pt_regs (current); +struct perf_output_handle handle; +struct perf_event_header header; +struct perf_sample_data data; +struct perf_callchain_entry *callchain; + + callchain =kmalloc (sizeof (struct perf_callchain_entry ) + + (sizeof (__u64) * event->attr.sample_max_stack) + + (sizeof (__u64) *1 )/* one context */ , +GFP_KERNEL );Any chance it can reuse
get_perf_callchain()instead of allocating the callchains every time?
Quoted 6 lines
I don't think so, because if it gets preempted, the new task might also need to do an unwind. But there's only one task-context callchain per CPU.
The allocation is less than ideal. I could just allocate it on the stack, and keep the number of entries bounded to 128 entries or so.
Yeah, probably that's better.
Quoted 23 lines
Quoted 11 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6799
Quoted 8 lines
kernel/events/core.c +7
-0+if (!callchain) +return ; + + callchain->nr =0 ; + data.callchain = callchain; + +perf_sample_data_init (&data,0 , event->hw.last_period);It would double count the period for a sample.
I guess we want to set the period to 0.Ok.
Quoted 8 lines
kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6803
Quoted 4 lines
kernel/events/core.c +4
-0+ + data.deferred = true; + +perf_prepare_sample (&data, event, regs);I don't think this would work properly. Not to mention it duplicates sample data unnecessarily, some data needs special handling to avoid inefficient (full) data copy like for (user) stack, regs and aux.
Quoted 2 lines
Yeah. I tried sending only the sample ID and callchain, but perf tool didn't appreciate that ;-) So for the RFC I gave up and did this.
Right, it should have some compatible sample ID header fields. It's dynamic and depends on the attr but at least it should have a PID to match callchains.
Quoted 7 lines
Quoted 7 lines
Anyway I'm not sure it can support these additional samples for deferred callchains without breaking the existing perf tools. Anyway it doesn't know
PERF_CONTEXT_USER_DEFERREDat least. I think this should be controlled by a new feature bit in theperf_event_attr.
Quoted 5 lines
Quoted 2 lines
Then we might add a breaking change to have a special sample record for the deferred callchains and sample ID only.
Sounds like a good idea. I'll need to study the code to figure out how to do that on the perf tool side. Or would you care to write a patch?
Sure, I'd be happy to write one.
Quoted 15 lines
Quoted 14 lines
kernel/events/core.c static u64 perf_get_page_size(unsigned long addr) · line 7648
Quoted 11 lines
kernel/events/core.c +3
-3-struct perf_callchain_entry * -perf_callchain (struct perf_event *event,struct pt_regs *regs) +void perf_callchain (struct perf_sample_data *data,struct perf_event *event, +struct pt_regs *regs) {bool kernel = !event->attr.exclude_callchain_kernel;bool user = !event->attr.exclude_callchain_user;const u32 max_stack = event->attr.sample_max_stack; -struct perf_callchain_entry *callchain; +bool defer_user =IS_ENABLED (CONFIG_HAVE_PERF_CALLCHAIN_DEFERRED );Is it always enabled depending on the kernel config? It could be controlled by
event->attr.something..
Quoted 4 lines
Possibly, depending on what y'all think. Also, fp vs sframe could be an attr (though sframe implies deferred).
Thanks for the review!
Thanks for your work,
Namhyung
Peter Zijlstra
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 1 line · Namhyung Kim
Ok, then BPF gets no user call stacks even with sframes.
Well, you could obviously run another BPF program at return to user and stitch there.
Quoted 6 lines
Ok, but ...
If the task is preempted, the call stack would be from another task (if it also has the pending call stacks) and we need to check which user call stack matches which kernel call stack. So there's no guarantee we can just use adjacent samples.
So upon requesting a user backtrace for the task it could request a token (from eg a global atomic u64 in the most crude case) and place this in the task_struct. The backtrace will emit this as forward reference for the user trace.
Once the task_work generates the user stacktrace, it will again dump this token along with the user unwind, after which it will reset the token for this this task (to 0).
Namhyung Kim
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 7 lines · Peter Zijlstra
Quoted 1 line · Namhyung Kim
Ok, then BPF gets no user call stacks even with sframes.
Well, you could obviously run another BPF program at return to user and stitch there.
Right, maybe some more work needed for bpf_get_stackid() but it seems possible.
Quoted 8 lines
Quoted 7 lines
Ok, but ...
If the task is preempted, the call stack would be from another task (if it also has the pending call stacks) and we need to check which user call stack matches which kernel call stack. So there's no guarantee we can just use adjacent samples.
Quoted 8 lines
So upon requesting a user backtrace for the task it could request a token (from eg a global atomic u64 in the most crude case) and place this in the
task_struct. The backtrace will emit this as forward reference for the user trace.Once the
task_workgenerates the user stacktrace, it will again dump this token along with the user unwind, after which it will reset the token for this this task (to 0).
Yeah, I thought something like this first, but then I thought "can we just use PID for this?"
Thanks,
Namhyung
Peter Zijlstra
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 2 lines · Namhyung Kim
Yeah, I thought something like this first, but then I thought "can we just use PID for this?"
TID, and assuming things are otherwise time ordered, yes.
Namhyung Kim
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 7 lines · Peter Zijlstra
Quoted 2 lines · Namhyung Kim
Yeah, I thought something like this first, but then I thought "can we just use PID for this?"
TID, and assuming things are otherwise time ordered, yes.
Right, I meant that, not TGID.
At least, the perf tools handle events in time ordered.
Thanks,
Namhyung
Namhyung Kim
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
In reply to Namhyung KimQuoted 9 lines · Namhyung Kim
Quoted 8 lines · Josh Poimboeuf
Quoted 6 lines · Namhyung Kim
Anyway I'm not sure it can support these additional samples for deferred callchains without breaking the existing perf tools. Anyway it doesn't know
PERF_CONTEXT_USER_DEFERREDat least. I think this should be controlled by a new feature bit in theperf_event_attr.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
Then we might add a breaking change to have a special sample record for the deferred callchains and sample ID only.
Sounds like a good idea. I'll need to study the code to figure out how to do that on the perf tool side. Or would you care to write a patch?
Sure, I'd be happy to write one.
I think we can start with something like the below. The sample id (attr.sample_type) should have IDENTIFIER | TID | TIME to enable defer_callchain in order to match sample and callchain records.
Thanks,
Namhyung
---8<---
include/uapi/linux/perf_event.h +16 -1
@@ -456,7 +456,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate DEFERRED_CALLCHAINS records for userspace */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wakeup every n events */
@@ -1207,6 +1208,20 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID =21 , +/* +* Deferred user stack callchains (for SFrame). Previous samples would +* have kernel callchains only and they need to be stitched with this +* to make full callchains. +* +* struct { +* struct perf_event_header header; +* u64 nr; +* u64 ips[nr]; +* struct sample_id sample_id; +* }; +*/ +PERF_RECORD_DEFERRED_CALLCHAINS =22 , + PERF_RECORD_MAX, /* non-ABI */ };
Peter Zijlstra
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 16 lines · Namhyung Kim
---8<---
include/uapi/linux/perf_event.h +2
-1@@ -456,7 +456,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate DEFERRED_CALLCHAINS records for userspace */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wakeup every n events */
include/uapi/linux/perf_event.h enum perf_event_type { · line 1212
Quoted 5 lines
include/uapi/linux/perf_event.h +2
-0@@ -1207,6 +1208,20 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID = 21, +/* +* Deferred user stack callchains (for SFrame). Previous samples would
Possibly also useful for ShadowStack based unwinders. And by virtue of it possibly saving work when multiple consecutive samples hit the same kernel section, for everything.
Quoted 5 lines
include/uapi/linux/perf_event.h +5
-0+* have kernel callchains only and they need to be stitched with this +* to make full callchains. +* +* struct { +* struct perf_event_header header;
include/uapi/linux/perf_event.h enum perf_event_type { · line 1227
Quoted 10 lines
include/uapi/linux/perf_event.h +7
-0+* u64 nr; +* u64 ips[nr]; +* struct sample_id sample_id; +* }; +*/ +PERF_RECORD_DEFERRED_CALLCHAINS =22 , + PERF_RECORD_MAX, /* non-ABI */ };
Anyway, yeah, that should do I suppose.
Josh Poimboeuf
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 21 lines · Peter Zijlstra
Quoted 19 lines · Namhyung Kim
---8<---
include/uapi/linux/perf_event.h +2
-1@@ -456,7 +456,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate DEFERRED_CALLCHAINS records for userspace */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wakeup every n events */ @@ -1207,6 +1208,20 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID = 21,
Quoted 6 lines
include/uapi/linux/perf_event.h enum perf_event_type { · line 1212
Quoted 2 lines
include/uapi/linux/perf_event.h +2
-0+/* +* Deferred user stack callchains (for SFrame). Previous samples would Possibly also useful for ShadowStack based unwinders. And by virtue of it possibly saving work when multiple consecutive samples hit the same kernel section, for everything.
[ necroing old thread as I'm finally working on a v2 ]
Peter, can you elaborate? What did you mean by "same kernel section"?
Like if there's a duplicate kernel callchain? Or something else?
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 29 lines · Josh Poimboeuf
Quoted 21 lines · Peter Zijlstra
Quoted 19 lines · Namhyung Kim
---8<---
include/uapi/linux/perf_event.h +2
-1@@ -456,7 +456,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate DEFERRED_CALLCHAINS records for userspace */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wakeup every n events */ @@ -1207,6 +1208,20 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID = 21,Quoted 6 lines
include/uapi/linux/perf_event.h enum perf_event_type { · line 1212
Quoted 2 lines
include/uapi/linux/perf_event.h +2
-0+/* +* Deferred user stack callchains (for SFrame). Previous samples would Possibly also useful for ShadowStack based unwinders. And by virtue of it possibly saving work when multiple consecutive samples hit the same kernel section, for everything.
Quoted 5 lines
[ necroing old thread as I'm finally working on a v2 ]
Peter, can you elaborate? What did you mean by "same kernel section"?
Like if there's a duplicate kernel callchain? Or something else?
Yeah, multiple samples hitting the same syscall invocation will, by necessity, have the same user callchain.
Josh Poimboeuf
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 30 lines · Peter Zijlstra
Quoted 29 lines · Josh Poimboeuf
Quoted 27 lines · Peter Zijlstra
Quoted 16 lines · Namhyung Kim
---8<---
include/uapi/linux/perf_event.h +2
-1@@ -456,7 +456,8 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* generate DEFERRED_CALLCHAINS records for userspace */ + __reserved_1 : 25;union {__u32 wakeup_events;/* wakeup every n events */ include/uapi/linux/perf_event.h enum perf_event_type { · line 1212
Quoted 5 lines
include/uapi/linux/perf_event.h +2
-0@@ -1207,6 +1208,20 @@ enum perf_event_type {*/ PERF_RECORD_AUX_OUTPUT_HW_ID = 21, +/* +* Deferred user stack callchains (for SFrame). Previous samples would Possibly also useful for ShadowStack based unwinders. And by virtue of it possibly saving work when multiple consecutive samples hit the same kernel section, for everything.
Quoted 8 lines
Quoted 5 lines
[ necroing old thread as I'm finally working on a v2 ]
Peter, can you elaborate? What did you mean by "same kernel section"?
Like if there's a duplicate kernel callchain? Or something else?
Yeah, multiple samples hitting the same syscall invocation will, by necessity, have the same user callchain.
Ah, I thought you were talking about kernel callchains for some reason. n/m...
Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 11 lines · Josh Poimboeuf
Quoted 10 lines · Peter Zijlstra
Quoted 9 lines · Josh Poimboeuf
Quoted 7 lines · Peter Zijlstra
include/uapi/linux/perf_event.h enum perf_event_type { · line 1212
Quoted 2 lines · Namhyung Kim
include/uapi/linux/perf_event.h +2
-0+/* +* Deferred user stack callchains (for SFrame). Previous samples would Possibly also useful for ShadowStack based unwinders. And by virtue of it possibly saving work when multiple consecutive samples hit the same kernel section, for everything.
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines
[ necroing old thread as I'm finally working on a v2 ]
Peter, can you elaborate? What did you mean by "same kernel section"?
Like if there's a duplicate kernel callchain? Or something else?
Yeah, multiple samples hitting the same syscall invocation will, by necessity, have the same user callchain.
If duplicate user space stacks were removed then would that not give a skewed perf profile? Or at least a less detailed one? i.e., it might be good to know if a certain system call was taking forever.
If that's not an issue, I was also wondering if we would want deferred even types for the other user options like PERF_SAMPLE_STACK_DEFERRED and PERF_RECORD_USER_REGS_DEFERRED?
Or if it would instead make sense to combine all the deferred user options into a single variable length PERF_RECORD_USER_DEFERRED event type?
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH RFC 04/10] perf: Introduce deferred user callchains
Quoted 3 lines · Josh Poimboeuf
Or if it would instead make sense to combine all the deferred user options into a single variable length
PERF_RECORD_USER_DEFERREDevent type?
This, have all those samples refer to the same future user callchain.
Josh Poimboeuf
maintainerRFC PATCH 5/10 perf/x86: Add HAVE_PERF_CALLCHAIN_DEFERRED
In reply to Josh PoimboeufEnable deferred user space unwinding on x86.
--- arch/x86/Kconfig | 1 + arch/x86/events/core.c | 47 ++++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 16 deletions(-)
arch/x86/Kconfig +1 -0
@@ -256,6 +256,7 @@ config X86 select HAVE_PERF_EVENTS_NMI select HAVE_HARDLOCKUP_DETECTOR_PERF if PERF_EVENTS && HAVE_PERF_EVENTS_NMI select HAVE_PCI + select HAVE_PERF_CALLCHAIN_DEFERRED select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP select MMU_GATHER_RCU_TABLE_FREE if PARAVIRT
arch/x86/events/core.c +31 -16
85 lines not on this page · raw message
Signature · 1 line
2.41.0
Josh Poimboeuf
maintainerRFC PATCH 6/10 unwind: Introduce generic user space unwinding interfaces
In reply to Josh PoimboeufIntroduce generic user space unwinder interfaces which will provide a unified way for architectures to unwind different user space stack frame types.
--- arch/Kconfig | 3 ++ include/linux/user_unwind.h | 32 +++++++++++++++ kernel/Makefile | 1 + kernel/unwind/Makefile | 1 + kernel/unwind/user.c | 77 +++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 include/linux/user_unwind.h create mode 100644 kernel/unwind/Makefile create mode 100644 kernel/unwind/user.c
arch/Kconfig +3 -0
@@ -428,6 +428,9 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH config HAVE_PERF_CALLCHAIN_DEFERRED bool +config HAVE_USER_UNWIND + bool + config HAVE_PERF_REGS bool help
include/linux/user_unwind.h new file +32 -0
@@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_USER_UNWIND_H +#define _LINUX_USER_UNWIND_H + +#include <linux/types.h> +#include <linux/sframe.h> + +enum user_unwind_type { +USER_UNWIND_TYPE_AUTO , +USER_UNWIND_TYPE_FP , +}; + +struct user_unwind_frame { +s32 cfa_off; +s32 ra_off; +s32 fp_off; +bool use_fp; +}; + +struct user_unwind_state { +unsigned long ip, sp, fp; +enum user_unwind_type type; +bool done; +}; + +extern int user_unwind_start (struct user_unwind_state *state,enum user_unwind_type ); +extern int user_unwind_next (struct user_unwind_state *state); + +#define for_each_user_frame (state, type) \ + for (user_unwind_start(&state, type); !state.done; user_unwind_next(&state)) + +#endif /* _LINUX_USER_UNWIND_H */
kernel/Makefile +1 -0
@@ -50,6 +50,7 @@ obj-y += rcu/ obj-y += livepatch/ obj-y += dma/ obj-y += entry/ +obj-y += unwind/ obj-$(CONFIG_MODULES) += module/ obj-$(CONFIG_KCMP) += kcmp.o
kernel/unwind/Makefile new file +1 -0
@@ -0,0 +1 @@ +obj-$(CONFIG_HAVE_USER_UNWIND) += user.o
kernel/unwind/user.c new file +77 -0
77 lines not on this page · raw message
Signature · 1 line
2.41.0
Josh Poimboeuf
maintainerRFC PATCH 7/10 unwind/x86: Add HAVE_USER_UNWIND
In reply to Josh PoimboeufUse ARCH_INIT_USER_FP_FRAME to describe how frame pointers are unwound on x86, and enable HAVE_USER_UNWIND accordinlgy so the user unwind interfaces can be used.
--- arch/x86/Kconfig | 1 + arch/x86/include/asm/user_unwind.h | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 arch/x86/include/asm/user_unwind.h
arch/x86/Kconfig +1 -0
@@ -278,6 +278,7 @@ config X86 select HAVE_UACCESS_VALIDATION if HAVE_OBJTOOL select HAVE_UNSTABLE_SCHED_CLOCK select HAVE_USER_RETURN_NOTIFIER + select HAVE_USER_UNWIND select HAVE_GENERIC_VDSO select HOTPLUG_PARALLEL if SMP && X86_64 select HOTPLUG_SMT if SMP
arch/x86/include/asm/user_unwind.h new file +11 -0
@@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_USER_UNWIND_H +#define _ASM_X86_USER_UNWIND_H + +#define ARCH_INIT_USER_FP_FRAME \ + .ra_off = sizeof(long) * -1, \ + .cfa_off = sizeof(long) * 2, \ + .fp_off = sizeof(long) * -2, \ + .use_fp = true, + +#endif /* _ASM_X86_USER_UNWIND_H */
Signature · 1 line
2.41.0
Josh Poimboeuf
maintainerRFC PATCH 8/10 perf/x86: Use user_unwind interface
In reply to Josh PoimboeufSimplify __perf_callchain_user() and prepare for sframe user space unwinding by switching to the generic user unwind interface.
--- arch/x86/events/core.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-)
arch/x86/events/core.c +5 -15
@@ -29,6 +29,7 @@#include <linux/device.h> #include <linux/nospec.h> #include <linux/static_call.h> +#include <linux/user_unwind.h> #include <asm/apic.h> #include <asm/stacktrace.h>
@@ -2856,8 +2857,7 @@ static inline int __perf_callchain_user32(struct pt_regs *regs,void __perf_callchain_user (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs,bool atomic) { -struct stack_frame frame; -const struct stack_frame __user *fp; +struct user_unwind_state state;if (perf_guest_state ()) {/* TODO: We don't support guest os callchain now */
@@ -2870,8 +2870,6 @@ void __perf_callchain_user(struct perf_callchain_entry_ctx *entry,if (regs->flags & (X86_VM_MASK |PERF_EFLAGS_VM ))return ; - fp = (void __user *)regs->bp; -perf_callchain_store (entry, regs->ip);if (atomic && !nmi_uaccess_okay ())
@@ -2883,17 +2881,9 @@ void __perf_callchain_user(struct perf_callchain_entry_ctx *entry,if (__perf_callchain_user32 (regs, entry)) goto done; -while (entry->nr < entry->max_stack) { -if (!valid_user_frame (fp,sizeof (frame))) -break ; - -if (__get_user (frame.next_frame, &fp->next_frame)) -break ; -if (__get_user (frame.return_address, &fp->return_address)) -break ; - -perf_callchain_store (entry, frame.return_address); - fp = (void __user *)frame.next_frame; +for_each_user_frame (state,USER_UNWIND_TYPE_AUTO ) { +if (perf_callchain_store (entry, state.ip)) + goto done; } done:if (atomic)
Signature · 1 line
2.41.0
Josh Poimboeuf
maintainerRFC PATCH 9/10 unwind: Introduce SFrame user space unwinding
In reply to Josh PoimboeufSome distros have started compiling frame pointers into all their packages to enable the kernel to do system-wide profiling of user space. Unfortunately that creates a runtime performance penalty across the entire system. Using DWARF instead isn't feasible due to complexity and slowness.
For in-kernel unwinding we solved this problem with the creation of the ORC unwinder for x86_64. Similarly, for user space the GNU assembler has created the SFrame format starting with binutils 2.40. SFrame is a simpler version of .eh_frame which gets placed in the .sframe section.
Add support for unwinding user space using SFrame.
More information about SFrame can be found here:
- https://lwn.net/Articles/932209/
- https://lwn.net/Articles/940686/
- https://sourceware.org/binutils/docs/sframe-spec.html
--- arch/Kconfig | 3 + arch/x86/include/asm/mmu.h | 2 +- fs/binfmt_elf.c | 46 +++- include/linux/mm_types.h | 3 + include/linux/sframe.h | 46 ++++ include/linux/user_unwind.h | 1 + include/uapi/linux/elf.h | 1 + include/uapi/linux/prctl.h | 3 + kernel/fork.c | 10 + kernel/sys.c | 11 + kernel/unwind/Makefile | 1 + kernel/unwind/sframe.c | 414 ++++++++++++++++++++++++++++++++++++ kernel/unwind/sframe.h | 217 +++++++++++++++++++ kernel/unwind/user.c | 15 +- mm/init-mm.c | 2 + 15 files changed, 768 insertions(+), 7 deletions(-) create mode 100644 include/linux/sframe.h create mode 100644 kernel/unwind/sframe.c create mode 100644 kernel/unwind/sframe.h
arch/Kconfig +3 -0
@@ -431,6 +431,9 @@ config HAVE_PERF_CALLCHAIN_DEFERRED config HAVE_USER_UNWIND bool +config HAVE_USER_UNWIND_SFRAME + bool + config HAVE_PERF_REGS bool help
arch/x86/include/asm/mmu.h +1 -1
@@ -73,7 +73,7 @@ typedef struct { .context = { \ .ctx_id = 1, \ .lock = __MUTEX_INITIALIZER(mm.context.lock), \ - } + },void leave_mm (int cpu);#define leave_mm leave_mm
fs/binfmt_elf.c +43 -3
93 lines not on this page · raw message
include/linux/mm_types.h +3 -0
@@ -974,6 +974,9 @@ struct mm_struct { #endif } lru_gen;#endif /* CONFIG_LRU_GEN */ +#ifdef CONFIG_HAVE_USER_UNWIND_SFRAME +struct maple_tree sframe_mt; +#endif } __randomize_layout;/*
include/linux/sframe.h new file +46 -0
46 lines not on this page · raw message
include/linux/user_unwind.h +1 -0
@@ -8,6 +8,7 @@enum user_unwind_type {USER_UNWIND_TYPE_AUTO ,USER_UNWIND_TYPE_FP , +USER_UNWIND_TYPE_SFRAME , };struct user_unwind_frame {
include/uapi/linux/elf.h +1 -0
@@ -39,6 +39,7 @@ typedef __s64 Elf64_Sxword;#define PT_GNU_STACK (PT_LOOS + 0x474e551)#define PT_GNU_RELRO (PT_LOOS + 0x474e552)#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553) +#define PT_GNU_SFRAME (PT_LOOS + 0x474e554)/* ARM MTE memory tag segment type */
include/uapi/linux/prctl.h +3 -0
@@ -306,4 +306,7 @@ struct prctl_mm_map {# define PR_RISCV_V_VSTATE_CTRL_NEXT_MASK 0xc# define PR_RISCV_V_VSTATE_CTRL_MASK 0x1f +#define PR_ADD_SFRAME 71 +#define PR_REMOVE_SFRAME 72 +#endif /* _LINUX_PRCTL_H */
kernel/fork.c +10 -0
@@ -99,6 +99,7 @@#include <linux/stackprotector.h> #include <linux/user_events.h> #include <linux/iommu.h> +#include <linux/sframe.h> #include <asm/pgalloc.h> #include <linux/uaccess.h>
@@ -924,6 +925,7 @@ void __mmdrop(struct mm_struct *mm)mm_pasid_drop (mm);mm_destroy_cid (mm);percpu_counter_destroy_many (mm->rss_stat,NR_MM_COUNTERS ); +sframe_free_mm (mm);free_mm (mm); }
@@ -1254,6 +1256,13 @@ static void mm_init_uprobes_state(struct mm_struct *mm)#endif } +static void mm_init_sframe (struct mm_struct *mm) +{ +#ifdef CONFIG_HAVE_USER_UNWIND_SFRAME +mt_init (&mm->sframe_mt); +#endif +} +static struct mm_struct *mm_init (struct mm_struct *mm,struct task_struct *p,struct user_namespace *user_ns) {
@@ -1285,6 +1294,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p, mm->pmd_huge_pte =NULL ;#endif mm_init_uprobes_state (mm); +mm_init_sframe (mm);hugetlb_count_init (mm);if (current->mm) {
kernel/sys.c +11 -0
@@ -64,6 +64,7 @@#include <linux/rcupdate.h> #include <linux/uidgid.h> #include <linux/cred.h> +#include <linux/sframe.h> #include <linux/nospec.h>
@@ -2739,6 +2740,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,case PR_RISCV_V_GET_CONTROL : error =RISCV_V_GET_CONTROL ();break ; +case PR_ADD_SFRAME : +if (arg5) +return -EINVAL ; + error =sframe_add_section (arg2, arg3, arg4); +break ; +case PR_REMOVE_SFRAME : +if (arg3 || arg4 || arg5) +return -EINVAL ; + error =sframe_remove_section (arg2); +break ;default : error = -EINVAL ;break ;
kernel/unwind/Makefile +1 -0
@@ -1 +1,2 @@ obj-$(CONFIG_HAVE_USER_UNWIND) += user.o +obj-$(CONFIG_HAVE_USER_UNWIND_SFRAME) += sframe.o
kernel/unwind/sframe.c new file +414 -0
414 lines not on this page · raw message
kernel/unwind/sframe.h new file +217 -0
217 lines not on this page · raw message
kernel/unwind/user.c +12 -3
@@ -26,6 +26,11 @@ int user_unwind_next(struct user_unwind_state *state)case USER_UNWIND_TYPE_FP : frame = &fp_frame;break ; +case USER_UNWIND_TYPE_SFRAME : + ret =sframe_find (state->ip, frame); +if (ret) + goto the_end; +break ;default :BUG (); }
@@ -64,10 +69,14 @@ int user_unwind_start(struct user_unwind_state *state,return -EINVAL ; } -if (type ==USER_UNWIND_TYPE_AUTO ) - state->type =USER_UNWIND_TYPE_FP ; -else +if (type ==USER_UNWIND_TYPE_AUTO ) { + state->type =sframe_enabled_current () ?USER_UNWIND_TYPE_SFRAME + :USER_UNWIND_TYPE_FP ; + }else { +if (type ==USER_UNWIND_TYPE_SFRAME && !sframe_enabled_current ()) +return -EINVAL ; state->type = type; + } state->sp =user_stack_pointer (regs); state->ip =instruction_pointer (regs);
mm/init-mm.c +2 -0
@@ -11,6 +11,7 @@#include <linux/atomic.h> #include <linux/user_namespace.h> #include <linux/iommu.h> +#include <linux/sframe.h> #include <asm/mmu.h> #ifndef INIT_MM_CONTEXT
@@ -48,6 +49,7 @@ struct mm_struct init_mm = { .pasid = IOMMU_PASID_INVALID,#endif INIT_MM_CONTEXT (init_mm) +INIT_MM_SFRAME }; void setup_initial_init_mm(void *start_code, void *end_code,
Signature · 1 line
2.41.0
Re: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding
Quoted 37 lines · Josh Poimboeuf
Some distros have started compiling frame pointers into all their packages to enable the kernel to do system-wide profiling of user space. Unfortunately that creates a runtime performance penalty across the entire system. Using DWARF instead isn't feasible due to complexity and slowness.
For in-kernel unwinding we solved this problem with the creation of the ORC unwinder for
x86_64. Similarly, for user space the GNU assembler has created the SFrame format starting with binutils 2.40. SFrame is a simpler version of .eh_frame which gets placed in the .sframe section.Add support for unwinding user space using SFrame.
More information about SFrame can be found here:
- https://lwn.net/Articles/932209/
- https://lwn.net/Articles/940686/
- https://sourceware.org/binutils/docs/sframe-spec.htmlSigned-off-by Josh Poimboeuf <[email protected]>--- arch/Kconfig | 3 + arch/x86/include/asm/mmu.h | 2 +- fs/binfmt_elf.c | 46 +++- include/linux/mm_types.h | 3 + include/linux/sframe.h | 46 ++++ include/linux/user_unwind.h | 1 + include/uapi/linux/elf.h | 1 + include/uapi/linux/prctl.h | 3 + kernel/fork.c | 10 + kernel/sys.c | 11 + kernel/unwind/Makefile | 1 + kernel/unwind/sframe.c | 414 ++++++++++++++++++++++++++++++++++++ kernel/unwind/sframe.h | 217 +++++++++++++++++++ kernel/unwind/user.c | 15 +- mm/init-mm.c | 2 + 15 files changed, 768 insertions(+), 7 deletions(-)
Quoted 4 lines
create mode 100644
include/linux/sframe.h
create mode 100644kernel/unwind/sframe.c
create mode 100644kernel/unwind/sframe.h
Thanks for working on this.
Quoted 309 lines
kernel/unwind/sframe.c new file +303
-0@@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/srcu.h> +#include <linux/uaccess.h> +#include <linux/mm.h> +#include <linux/sframe.h> +#include <linux/user_unwind.h> + +#include "sframe.h" + +#define SFRAME_FILENAME_LEN 32 + +struct sframe_section { +struct rcu_head rcu; + +unsigned long sframe_addr; +unsigned long text_addr; + +unsigned long fdes_addr; +unsigned long fres_addr; +unsigned int fdes_num; +signed char ra_off, fp_off; +}; + +DEFINE_STATIC_SRCU (sframe_srcu); + +#define __SFRAME_GET_USER (out, user_ptr, type) \ +({ \ + type __tmp; \ + if (get_user(__tmp, (type *)user_ptr)) \ + return -EFAULT; \ + user_ptr += sizeof(__tmp); \ + out = __tmp; \ +}) + +#define SFRAME_GET_USER_SIGNED (out, user_ptr, size) \ +({ \ + switch (size) { \ + case 1: \ + __SFRAME_GET_USER(out, user_ptr, s8); \ + break; \ + case 2: \ + __SFRAME_GET_USER(out, user_ptr, s16); \ + break; \ + case 4: \ + __SFRAME_GET_USER(out, user_ptr, s32); \ + break; \ + default: \ + return -EINVAL; \ + } \ +}) + +#define SFRAME_GET_USER_UNSIGNED (out, user_ptr, size) \ +({ \ + switch (size) { \ + case 1: \ + __SFRAME_GET_USER(out, user_ptr, u8); \ + break; \ + case 2: \ + __SFRAME_GET_USER(out, user_ptr, u16); \ + break; \ + case 4: \ + __SFRAME_GET_USER(out, user_ptr, u32); \ + break; \ + default: \ + return -EINVAL; \ + } \ +}) + +static unsigned char fre_type_to_size (unsigned char fre_type) +{ +if (fre_type >2 ) +return 0 ; +return 1 << fre_type; +} + +static unsigned char offset_size_enum_to_size (unsigned char off_size) +{ +if (off_size >2 ) +return 0 ; +return 1 << off_size; +} + +static int find_fde (struct sframe_section *sec,unsigned long ip, +struct sframe_fde *fde) +{ +s32 func_off, ip_off; +struct sframe_fde __user *first, *last, *mid, *found; + + ip_off = ip - sec->sframe_addr; + + first = (void *)sec->fdes_addr; + last = first + sec->fdes_num; +while (first <= last) { + mid = first + ((last - first) /2 ); +if (get_user (func_off, (s32 *)mid)) +return -EFAULT ; +if (ip_off >= func_off) { + found = mid; + first = mid +1 ; + }else + last = mid -1 ; + } + +if (!found) +return -EINVAL ; + +if (copy_from_user (fde, found,sizeof (*fde))) +return -EFAULT ; + +return 0 ; +} + +static int find_fre (struct sframe_section *sec,struct sframe_fde *fde, +unsigned long ip,struct user_unwind_frame *frame) +{ +unsigned char fde_type =SFRAME_FUNC_FDE_TYPE (fde->info); +unsigned char fre_type =SFRAME_FUNC_FRE_TYPE (fde->info); +s32 fre_ip_off, cfa_off, ra_off, fp_off, ip_off; +unsigned char offset_count, offset_size; +unsigned char addr_size; +void __user *f, *last_f; +u8 fre_info; +int i; + + addr_size =fre_type_to_size (fre_type); +if (!addr_size) +return -EINVAL ; + + ip_off = ip - sec->sframe_addr - fde->start_addr; + + f = (void *)sec->fres_addr + fde->fres_off; + +for (i =0 ; i < fde->fres_num; i++) { + +SFRAME_GET_USER_UNSIGNED (fre_ip_off, f, addr_size); + +if (fde_type ==SFRAME_FDE_TYPE_PCINC ) { +if (fre_ip_off > ip_off) +break ; + }else { +/* SFRAME_FDE_TYPE_PCMASK */ +#if 0 /* sframe v2 */ +if (ip_off % fde->rep_size < fre_ip_off) +break ; +#endif + } + +SFRAME_GET_USER_UNSIGNED (fre_info, f,1 ); + + offset_count =SFRAME_FRE_OFFSET_COUNT (fre_info); + offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (fre_info)); + +if (!offset_count || !offset_size) +return -EINVAL ; + + last_f = f; + f += offset_count * offset_size; + } + +if (!last_f) +return -EINVAL ; + + f = last_f; + +SFRAME_GET_USER_UNSIGNED (cfa_off, f, offset_size); + offset_count--; + + ra_off = sec->ra_off; +if (!ra_off) { +if (!offset_count--) +return -EINVAL ; +SFRAME_GET_USER_SIGNED (ra_off, f, offset_size); + } + + fp_off = sec->fp_off; +if (!fp_off && offset_count) { + offset_count--; +SFRAME_GET_USER_SIGNED (fp_off, f, offset_size); + } + +if (offset_count) +return -EINVAL ; + + frame->cfa_off = cfa_off; + frame->ra_off = ra_off; + frame->fp_off = fp_off; + frame->use_fp =SFRAME_FRE_CFA_BASE_REG_ID (fre_info) ==SFRAME_BASE_REG_FP ; + +return 0 ; +} + +int sframe_find (unsigned long ip,struct user_unwind_frame *frame) +{ +struct mm_struct *mm = current->mm; +struct sframe_section *sec; +struct sframe_fde fde; +int srcu_idx; +int ret = -EINVAL ; + + srcu_idx =srcu_read_lock (&sframe_srcu); + + sec =mtree_load (&mm->sframe_mt, ip); +if (!sec) { +srcu_read_unlock (&sframe_srcu, srcu_idx); +return -EINVAL ; + } + + + ret =find_fde (sec, ip, &fde); +if (ret) + goto err_unlock; + + ret =find_fre (sec, &fde, ip, frame); +if (ret) + goto err_unlock; + +srcu_read_unlock (&sframe_srcu, srcu_idx); +return 0 ; + +err_unlock: +srcu_read_unlock (&sframe_srcu, srcu_idx); +return ret; +} + +static int get_sframe_file (unsigned long sframe_addr,struct sframe_file *file) +{ +struct mm_struct *mm = current->mm; +struct vm_area_struct *sframe_vma, *text_vma, *vma; +VMA_ITERATOR (vmi, mm,0 ); + +mmap_read_lock (mm); + + sframe_vma =vma_lookup (mm, sframe_addr); +if (!sframe_vma || !sframe_vma->vm_file) + goto err_unlock; + + text_vma =NULL ; + +for_each_vma (vmi, vma) { +if (vma->vm_file != sframe_vma->vm_file) +continue ; +if (vma->vm_flags &VM_EXEC ) { +if (text_vma) { +/* +* Multiple EXEC segments in a single file +* aren't currently supported, is that a thing? +*/ +WARN_ON_ONCE (1 ); + goto err_unlock; + } + text_vma = vma; + } + } + + file->sframe_addr = sframe_addr; + file->text_start = text_vma->vm_start; + file->text_end = text_vma->vm_end; + +mmap_read_unlock (mm); +return 0 ; + +err_unlock: +mmap_read_unlock (mm); +return -EINVAL ; +} + +static int validate_sframe_addrs (struct sframe_file *file) +{ +struct mm_struct *mm = current->mm; +struct vm_area_struct *text_vma; + +mmap_read_lock (mm); + +if (!vma_lookup (mm, file->sframe_addr)) + goto err_unlock; + + text_vma =vma_lookup (mm, file->text_start); +if (!(text_vma->vm_flags &VM_EXEC )) + goto err_unlock; + +if (vma_lookup (mm, file->text_end-1 ) != text_vma) + goto err_unlock; + +mmap_read_unlock (mm); +return 0 ; + +err_unlock: +mmap_read_unlock (mm); +return -EINVAL ; +} + +int __sframe_add_section (struct sframe_file *file) +{ +struct maple_tree *sframe_mt = ¤t->mm->sframe_mt; +struct sframe_section *sec; +struct sframe_header shdr; +unsigned long header_end; +int ret; + +if (copy_from_user (&shdr, (void *)file->sframe_addr,sizeof (shdr)))
kernel/unwind/sframe.c line 313
Quoted 10 lines
kernel/unwind/sframe.c new file +10
-0+return -EFAULT ; + +if (shdr.preamble.magic !=SFRAME_MAGIC || + shdr.preamble.version !=SFRAME_VERSION_1 || + (!shdr.preamble.flags &SFRAME_F_FDE_SORTED ) || + shdr.auxhdr_len || !shdr.num_fdes || !shdr.num_fres || + shdr.fdes_off > shdr.fres_off) { +return -EINVAL ; + } +
I would say that it will be ideal to start the support with SFRAME_VERSION_2 onwards, if we have a choice.
The structure SFrame FDE in SFRAME_VERSION_1 was unaligned on-disk. We fixed that in SFRAME_VERSION_2 (Binutils 2.41) by adding some padding as you have already noted. For x86_64, its not an issue though, yes.
Josh Poimboeuf
maintainerRe: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding
Quoted 12 lines · Indu Bhagat
kernel/unwind/sframe.c line 313
Quoted 8 lines
kernel/unwind/sframe.c new file +8
-0+if (shdr.preamble.magic !=SFRAME_MAGIC || + shdr.preamble.version !=SFRAME_VERSION_1 || + (!shdr.preamble.flags &SFRAME_F_FDE_SORTED ) || + shdr.auxhdr_len || !shdr.num_fdes || !shdr.num_fres || + shdr.fdes_off > shdr.fres_off) { +return -EINVAL ; + } +I would say that it will be ideal to start the support with
SFRAME_VERSION_2onwards, if we have a choice.
Quoted 3 lines
The structure SFrame FDE in
SFRAME_VERSION_1was unaligned on-disk. We fixed that inSFRAME_VERSION_2(Binutils 2.41) by adding some padding as you have already noted. Forx86_64, its not an issue though, yes.
Agreed. I actually had v2 implemented but then realized my binutils was older so I changed to v1 for testing. But yeah, we can make v2 the minimum.
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding
Quoted 7 lines · On Thu, 9 Nov 2023 11:37:59 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 3 lines
The structure SFrame FDE in
SFRAME_VERSION_1was unaligned on-disk. We fixed that inSFRAME_VERSION_2(Binutils 2.41) by adding some padding as you have already noted. Forx86_64, its not an issue though, yes.Agreed. I actually had v2 implemented but then realized my binutils was older so I changed to v1 for testing. But yeah, we can make v2 the minimum.
Cool, my test box has binutils 2.41, so it should be good to go ;-)
-- Steve
Josh Poimboeuf
maintainerRe: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding
Quoted 7 lines · Steven Rostedt
Quoted 4 lines · On Thu, 9 Nov 2023 11:37:59 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 3 lines
The structure SFrame FDE in
SFRAME_VERSION_1was unaligned on-disk. We fixed that inSFRAME_VERSION_2(Binutils 2.41) by adding some padding as you have already noted. Forx86_64, its not an issue though, yes.
Quoted 5 lines
Quoted 3 lines
Agreed. I actually had v2 implemented but then realized my binutils was older so I changed to v1 for testing. But yeah, we can make v2 the minimum.
Cool, my test box has binutils 2.41, so it should be good to go ;-)
You'll need something like this (untested!)
kernel/unwind/sframe.c +1 -3
@@ -142,10 +142,8 @@ static int find_fre(struct sframe_section *sec, struct sframe_fde *fde,break ; }else {/* SFRAME_FDE_TYPE_PCMASK */ -#if 0 /* sframe v2 */ if (ip_off % fde->rep_size < fre_ip_off)break ; -#endif }SFRAME_GET_USER_UNSIGNED (fre_info, f,1 );
@@ -304,7 +302,7 @@ int __sframe_add_section(struct sframe_file *file)return -EFAULT ;if (shdr.preamble.magic !=SFRAME_MAGIC || - shdr.preamble.version !=SFRAME_VERSION_1 || + shdr.preamble.version !=SFRAME_VERSION_2 || (!shdr.preamble.flags &SFRAME_F_FDE_SORTED ) || shdr.auxhdr_len || !shdr.num_fdes || !shdr.num_fres || shdr.fdes_off > shdr.fres_off) {
kernel/unwind/sframe.h +0 -2
@@ -125,10 +125,8 @@ struct sframe_fde {u32 fres_off;u32 fres_num;u8 info; -#if 0 /* TODO sframe v2 */ u8 rep_size;u16 padding; -#endif } __packed;/*
kernel test robot
build botRe: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding
In reply to Josh Poimboeufkernel test robot noticed the following build warnings:
Hi Josh,
[This is a private test report for your RFC patch.] kernel test robot noticed the following build warnings:
[auto build test WARNING on linus/master]
[also build test WARNING on next-20231110]
[cannot apply to perf-tools-next/perf-tools-next tip/perf/core perf-tools/perf-tools acme/perf/core v6.6] [If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Josh-Poimboeuf/perf-Remove-get_perf_callchain-init_nr-argument/20231109-085548 base: linus/master patch link: https://lore.kernel.org/r/09460e60dd1c2f8ea1abb8d9188195db699ce76f.1699487758.git.jpoimboe%40kernel.org patch subject: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding config: x86_64-randconfig-001-20231110 (https://download.01.org/0day-ci/archive/20231110/[email protected]/config) compiler: gcc-7 (Ubuntu 7.5.0-6ubuntu2) 7.5.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231110/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
In file included from kernel/unwind/user.c:9:0:
>> arch/x86/include/asm/user_unwind.h:6:13: warning: overflow in implicit constant conversion [-Woverflow]
.ra_off = sizeof(long) * -1, \
^
kernel/unwind/user.c:12:2: note: in expansion of macro 'ARCH_INIT_USER_FP_FRAME'
ARCH_INIT_USER_FP_FRAME
^~~~~~~~~~~~~~~~~~~~~~~
arch/x86/include/asm/user_unwind.h:8:13: warning: overflow in implicit constant conversion [-Woverflow]
.fp_off = sizeof(long) * -2, \
^
kernel/unwind/user.c:12:2: note: in expansion of macro 'ARCH_INIT_USER_FP_FRAME'
ARCH_INIT_USER_FP_FRAME
^~~~~~~~~~~~~~~~~~~~~~~vim +6 arch/x86/include/asm/user_unwind.h
c8c95a7eb658588 Josh Poimboeuf 2023-11-08 4
c8c95a7eb658588 Josh Poimboeuf 2023-11-08 5 #define ARCH_INIT_USER_FP_FRAME \ c8c95a7eb658588 Josh Poimboeuf 2023-11-08 @6 .ra_off = sizeof(long) * -1, \ c8c95a7eb658588 Josh Poimboeuf 2023-11-08 7 .cfa_off = sizeof(long) * 2, \ c8c95a7eb658588 Josh Poimboeuf 2023-11-08 8 .fp_off = sizeof(long) * -2, \ c8c95a7eb658588 Josh Poimboeuf 2023-11-08 9 .use_fp = true,
c8c95a7eb658588 Josh Poimboeuf 2023-11-08 10
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Josh Poimboeuf
maintainerRFC PATCH 10/10 unwind/x86/64: Add HAVE_USER_UNWIND_SFRAME
In reply to Josh PoimboeufBinutils 2.40 supports generating sframe for x86_64. It works well in testing so enable it.
NOTE: An out-of-tree glibc patch is still needed to enable setting PR_ADD_SFRAME for shared libraries and dlopens.
--- arch/x86/Kconfig | 1 + 1 file changed, 1 insertion(+)
arch/x86/Kconfig +1 -0
@@ -279,6 +279,7 @@ config X86 select HAVE_UNSTABLE_SCHED_CLOCK select HAVE_USER_RETURN_NOTIFIER select HAVE_USER_UNWIND + select HAVE_USER_UNWIND_SFRAME if X86_64 select HAVE_GENERIC_VDSO select HOTPLUG_PARALLEL if SMP && X86_64 select HOTPLUG_SMT if SMP
Signature · 1 line
2.41.0
Josh Poimboeuf
maintainerQuoted 11 lines · Josh Poimboeuf
Some distros have started compiling frame pointers into all their packages to enable the kernel to do system-wide profiling of user space. Unfortunately that creates a runtime performance penalty across the entire system. Using DWARF (or .eh_frame) instead isn't feasible because of complexity and slowness.
For in-kernel unwinding we solved this problem with the creation of the ORC unwinder for
x86_64. Similarly, for user space the GNU assembler has created the SFrame ("Simple Frame") format starting with binutils 2.40.
Quoted 6 lines
These patches add support for unwinding user space from the kernel using SFrame with perf. It should be easy to add user unwinding support for other components like ftrace.
I tested it on Gentoo by recompiling everything with -Wa,-gsframe and using a custom glibc patch (which I'll send in a reply to this email).
Here's my glibc patch:
elf/dl-load.c +18 -0
@@ -29,6 +29,7 @@#include <bits/wordsize.h> #include <sys/mman.h> #include <sys/param.h> +#include <sys/prctl.h> #include <sys/stat.h> #include <sys/types.h> #include <gnu/lib-names.h>
@@ -88,6 +89,10 @@ struct filebuf#define STRING (x) __STRING (x) +#ifndef PT_GNU_SFRAME +#define PT_GNU_SFRAME 0x6474e554 +#endif + int __stack_prot attribute_hidden attribute_relro#if _STACK_GROWS_DOWN && definedPROT_GROWSDOWN
@@ -1213,6 +1218,10 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, l->l_relro_addr = ph->p_vaddr; l->l_relro_size = ph->p_memsz;break ; + +case PT_GNU_SFRAME : + l->l_sframe_addr = ph->p_vaddr; +break ; }if (__glibc_unlikely (nloadcmds ==0 ))
@@ -1263,6 +1272,8 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, l->l_map_start = l->l_map_end =0 ; goto lose; } + + }if (l->l_ld !=0 )
@@ -1376,6 +1387,13 @@ cannot enable executable stack as shared object requires");break ; } +#define PR_ADD_SFRAME 71 +if (l->l_sframe_addr !=0 ) + { + l->l_sframe_addr += l->l_addr; +__prctl (PR_ADD_SFRAME , l->l_sframe_addr,NULL ,NULL ,NULL ); + } +/* We are done mapping in the file. We no longer need the descriptor. */ if (__glibc_unlikely (__close_nocancel (fd) !=0 )) {
include/link.h +2 -0
@@ -348,6 +348,8 @@ struct link_mapElfW (Addr) l_relro_addr;size_t l_relro_size; +ElfW (Addr) l_sframe_addr; +unsigned long long int l_serial; };