Skip to contentSkip to conversation

perf: user space sframe unwinding

RFC PATCH Superseded Josh Poimboeuf 7 participants 36 messages Nov 9, 2023 – Sep 13, 2024

Subsystems (10)
Memory management x86 VFS BPF +6 more subsystems

v1 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Josh Poimboeuf

author maintainer
#0 lore raw

cover 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
author maintainer
#1 lore raw

RFC 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
index afb028c54f33..f4b05954076c 100644
@@ -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
index d6b277482085..b0b0fbff7c18 100644
@@ -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
index 1273be84392c..1e135195250c 100644
@@ -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
index 683dc086ef10..b0d62df7df4e 100644
@@ -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
maintainer Acked-by on v1 1/10
#2 lore raw

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
---
 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
index afb028c54f33..f4b05954076c 100644
@@ -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
index d6b277482085..b0b0fbff7c18 100644
@@ -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
index 1273be84392c..1e135195250c 100644
@@ -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
index 683dc086ef10..b0d62df7df4e 100644
@@ -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

author maintainer
#3 lore raw

RFC PATCH 2/10 perf: Remove get_perf_callchain() 'crosstask' argument

In reply to Josh Poimboeuf

get_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
index f4b05954076c..2d8fa253b9df 100644
@@ -1534,7 +1534,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct p
 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, 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
index b0b0fbff7c18..e4827ca5378d 100644
@@ -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
index 1e135195250c..aa5f9d11c28d 100644
@@ -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
index b0d62df7df4e..5e41a3b70bcd 100644
@@ -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
maintainer Acked-by on v1 2/10
#4 lore raw

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.

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
index f4b05954076c..2d8fa253b9df 100644
@@ -1534,7 +1534,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct p
 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, 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
index b0b0fbff7c18..e4827ca5378d 100644
@@ -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
index 1e135195250c..aa5f9d11c28d 100644
@@ -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
index b0d62df7df4e..5e41a3b70bcd 100644
@@ -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

#5 lore raw

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't

For 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.

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
index f4b05954076c..2d8fa253b9df 100644
@@ -1534,7 +1534,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct p
  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, 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
index b0b0fbff7c18..e4827ca5378d 100644
@@ -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
index 1e135195250c..aa5f9d11c28d 100644
@@ -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
index b0d62df7df4e..5e41a3b70bcd 100644
@@ -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

author maintainer
#6 lore raw

RFC PATCH 3/10 perf: Simplify get_perf_callchain() user logic

In reply to Josh Poimboeuf

Simplify 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
index aa5f9d11c28d..2bee8b6fda0e 100644
@@ -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
maintainer Acked-by on v1 3/10
#7 lore raw

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.

Thanks,
Namhyung

Quoted 39 lines
---
 kernel/events/callchain.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
kernel/events/callchain.c +7 -10
index aa5f9d11c28d..2bee8b6fda0e 100644
@@ -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

author maintainer
#8 lore raw

RFC PATCH 4/10 perf: Introduce deferred user callchains

In reply to 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).

---
 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
index f4b210ab0612..690c82212224 100644
@@ -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
index 2d8fa253b9df..2f232111dff2 100644
@@ -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 p
 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, 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
index 39c6a250dd1b..9a1127af4cda 100644
@@ -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
index e4827ca5378d..fcdd26715b12 100644
@@ -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
index 2bee8b6fda0e..16571c8d6771 100644
@@ -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
maintainer
#9 lore raw

Re: [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).

---
 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
index f4b210ab0612..690c82212224 100644
@@ -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
index 2d8fa253b9df..2f232111dff2 100644
@@ -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 p
 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, 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
index 39c6a250dd1b..9a1127af4cda 100644
@@ -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
index e4827ca5378d..fcdd26715b12 100644
@@ -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 -0
        if (unlikely(!trace))
                goto err_fault;
 
kernel/events/callchain.c +1 -1
index 2bee8b6fda0e..16571c8d6771 100644
@@ -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
index 5e41a3b70bcd..290e06b0071c 100644

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

author maintainer
#10 lore raw

Re: [PATCH RFC 04/10] perf: Introduce deferred user callchains

Quoted 6 lines · Namhyung Kim
Quoted 5 lines · Josh Poimboeuf

+++ b/kernel/bpf/stackmap.c

kernel/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
index 5e41a3b70bcd..290e06b0071c 100644
@@ -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_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.

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
author maintainer
#11 lore raw

Re: [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
maintainer
#12 lore raw

Re: [PATCH RFC 04/10] perf: Introduce deferred user callchains

In reply to Josh Poimboeuf
Quoted 23 lines · Josh Poimboeuf
Quoted 6 lines · Namhyung Kim
Quoted 5 lines · Josh Poimboeuf

+++ b/kernel/bpf/stackmap.c

kernel/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
index 5e41a3b70bcd..290e06b0071c 100644
@@ -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_DEFERRED at least. I think this should be controlled by a new feature bit in the perf_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

maintainer
#13 lore raw

Re: [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).

maintainer
#14 lore raw

Re: [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_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).

Yeah, I thought something like this first, but then I thought "can we just use PID for this?"

Thanks,
Namhyung

maintainer
#15 lore raw

Re: [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.

maintainer
#16 lore raw

Re: [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

maintainer
#17 lore raw

Re: [PATCH RFC 04/10] perf: Introduce deferred user callchains

In reply to Namhyung Kim
Quoted 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_DEFERRED at least. I think this should be controlled by a new feature bit in the perf_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
index 39c6a250dd1b..a3765ff59798 100644
@@ -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 */
 };
 
maintainer
#18 lore raw

Re: [PATCH RFC 04/10] perf: Introduce deferred user callchains

Quoted 16 lines · Namhyung Kim

---8<---

include/uapi/linux/perf_event.h +2 -1
index 39c6a250dd1b..a3765ff59798 100644
@@ -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.

author maintainer
#19 lore raw

Re: [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
index 39c6a250dd1b..a3765ff59798 100644
@@ -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
maintainer
#20 lore raw

Re: [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
index 39c6a250dd1b..a3765ff59798 100644
@@ -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.

author maintainer
#21 lore raw

Re: [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
index 39c6a250dd1b..a3765ff59798 100644
@@ -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
author maintainer
#22 lore raw

Re: [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
maintainer
#23 lore raw

Re: [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_DEFERRED event type?

This, have all those samples refer to the same future user callchain.

author maintainer
#24 lore raw

RFC PATCH 5/10 perf/x86: Add HAVE_PERF_CALLCHAIN_DEFERRED

In reply to Josh Poimboeuf

Enable 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
index 3762f41bb092..cacf11ac4b10 100644
@@ -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
author maintainer
#25 lore raw

RFC PATCH 6/10 unwind: Introduce generic user space unwinding interfaces

In reply to Josh Poimboeuf

Introduce 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
index 690c82212224..c4a08485835e 100644
@@ -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
new file mode 100644
index 000000000000..2812b88c95fd
@@ -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
index 3947122d618b..bddf58b3b496 100644
@@ -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
new file mode 100644
index 000000000000..eb466d6a3295
@@ -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
author maintainer
#26 lore raw

RFC PATCH 7/10 unwind/x86: Add HAVE_USER_UNWIND

In reply to Josh Poimboeuf

Use 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
index cacf11ac4b10..95939cd54dfe 100644
@@ -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
new file mode 100644
index 000000000000..caa6266abbb4
@@ -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
author maintainer
#27 lore raw

RFC PATCH 8/10 perf/x86: Use user_unwind interface

In reply to Josh Poimboeuf

Simplify __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
index ae264437f794..5c41a11f058f 100644
@@ -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
author maintainer
#28 lore raw

RFC PATCH 9/10 unwind: Introduce SFrame user space unwinding

In reply to 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.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
index c4a08485835e..b133b03102c7 100644
@@ -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
index 0da5c227f490..9cf9cae8345f 100644
@@ -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
index 957ce38768b2..7c361a9ccf75 100644
@@ -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
index 2812b88c95fd..9a5e6e557530 100644
@@ -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
index 9417309b7230..e3a08ee03fe4 100644
@@ -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
index 370ed14b1ae0..336277ea9782 100644
@@ -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
index 10917c3e1f03..0ec13004d86c 100644
@@ -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
index 420d9cb9cc8e..4f2d6f91814d 100644
@@ -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
index eb466d6a3295..6f202c5840cf 100644
@@ -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
index 8f9432306482..4194180df154 100644
@@ -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
index cfd367822cdd..288885a39e12 100644
@@ -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
#29 lore raw

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.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(-)
Quoted 4 lines

create mode 100644 include/linux/sframe.h
create mode 100644 kernel/unwind/sframe.c
create mode 100644 kernel/unwind/sframe.h

Thanks for working on this.

Quoted 309 lines
kernel/unwind/sframe.c new file +303 -0
new file mode 100644
index 000000000000..b167c19497e5
@@ -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 = &current->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.

author maintainer
#30 lore raw

Re: [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_2 onwards, if we have a choice.

Quoted 3 lines

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.

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
maintainer
#31 lore raw

Re: [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_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.

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

author maintainer
#32 lore raw

Re: [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_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.

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
index b167c19497e5..f8b2a520a689 100644
@@ -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
index 1f91b696daf5..1adfc88b784d 100644
@@ -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;
 
 /*
#33 lore raw

Re: [PATCH RFC 09/10] unwind: Introduce SFrame user space unwinding

In reply to Josh Poimboeuf
kernel 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
author maintainer
#34 lore raw

RFC PATCH 10/10 unwind/x86/64: Add HAVE_USER_UNWIND_SFRAME

In reply to Josh Poimboeuf

Binutils 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
index 95939cd54dfe..770d0528e4c9 100644
@@ -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
author maintainer
#35 lore raw
In reply to Josh Poimboeuf
Quoted 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
index 2923b1141d..333d7c39fd 100644
@@ -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 && defined PROT_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
index c6af095d87..36ac75680f 100644
@@ -348,6 +348,8 @@ struct link_map
     ElfW(Addr) l_relro_addr;
     size_t l_relro_size;
 
+    ElfW(Addr) l_sframe_addr;
+
     unsigned long long int l_serial;
   };