Skip to contentSkip to conversation

unwind, perf: sframe user space unwinding

PATCH Superseded Josh Poimboeuf 10 participants 99 messages Oct 28, 2024 – Dec 11, 2024

Subsystems (16)
Memory management x86 VFS BPF +12 more subsystems

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Josh Poimboeuf

author maintainer
#0 lore raw

v3 cover unwind, perf: sframe user space unwinding

This has all the changes discussed in v2, plus VDSO sframe support and Namhyung's perf tool patches (see detailed changelog below).

I did quite a bit of testing, it seems to work well. It still needs some binutils and glibc patches which I'll send in a reply.

Questions for perf experts:

- Is the perf_event lifetime managed correctly or do we need to do something to ensure it exists in unwind_user_task_work()?

Or alternatively is the original perf_event even needed in unwind_user_task_work() or can a new one be created on demand?

- Is --call-graph=sframe needed for consistency?

- Should perf use the context cookie? Note that because the callback is usually only called once for multiple NMIs in the same entry context, it's possible for the PERF_RECORD_CALLCHAIN_DEFERRED event to arrive *before* some of the corresponding kernel events. The context cookie disambiguates the corner cases.

Based on tip/master.

Also at:

git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git sframe-v3

v3:
- move the "deferred" logic out of perf and into unwind_user with new unwind_user_deferred() interface [Steven, Mathieu]
- add more sframe sanity checks [Steven]
- make frame pointers optional depending on arch [Jens]
- fix perf event output [Namhyung]
- include Namhyung's perf tool patches
- enable sframe generation in VDSO
- fix build errors [robot]

v2: https://lore.kernel.org/[email protected]
- rebase on v6.11-rc7
- reorganize the patches to add sframe first
- change to sframe v2
- add new perf event type: PERF_RECORD_CALLCHAIN_DEFERRED
- add new perf attribute: defer_callchain

v1: https://lore.kernel.org/[email protected]

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") v2 format starting with binutils 2.41.

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.

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 ;-)

Josh Poimboeuf (15):
x86/vdso: Fix DWARF generation for getrandom() x86/asm: Avoid emitting DWARF CFI for non-VDSO x86/asm: Fix VDSO DWARF generation with kernel IBT enabled x86/vdso: Use SYM_FUNC_{START,END} in __kernel_vsyscall() x86/vdso: Use CFI macros in __vdso_sgx_enter_enclave() x86/vdso: Enable sframe generation in VDSO
unwind: Add user space unwinding API
unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_FP
unwind: Introduce sframe user space unwinding unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_SFRAME
unwind: Add deferred user space unwinding API
perf: Remove get_perf_callchain() 'init_nr' argument
perf: Remove get_perf_callchain() 'crosstask' argument
perf: Simplify get_perf_callchain() user logic
perf: Add deferred user callchains

Namhyung Kim (4):
perf tools: Minimal CALLCHAIN_DEFERRED support perf record: Enable defer_callchain for user callchains perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED perf tools: Merge deferred user callchains

 arch/Kconfig                              |  14 +
 arch/x86/Kconfig                          |   2 +
 arch/x86/entry/vdso/Makefile              |   6 +-
 arch/x86/entry/vdso/vdso-layout.lds.S     |   5 +-
 arch/x86/entry/vdso/vdso32/system_call.S  |  10 +-
 arch/x86/entry/vdso/vgetrandom-chacha.S   |   3 +-
 arch/x86/entry/vdso/vsgx.S                |  19 +-
 arch/x86/include/asm/dwarf2.h             |  40 ++-
 arch/x86/include/asm/linkage.h            |  29 +-
 arch/x86/include/asm/mmu.h                |   2 +-
 arch/x86/include/asm/unwind_user.h        |  11 +
 arch/x86/include/asm/vdso.h               |   1 -
 fs/binfmt_elf.c                           |  35 +-
 include/linux/entry-common.h              |   3 +
 include/linux/mm_types.h                  |   3 +
 include/linux/perf_event.h                |  12 +-
 include/linux/sched.h                     |   5 +
 include/linux/sframe.h                    |  41 +++
 include/linux/unwind_user.h               |  99 ++++++
 include/uapi/linux/elf.h                  |   1 +
 include/uapi/linux/perf_event.h           |  22 +-
 include/uapi/linux/prctl.h                |   3 +
 kernel/Makefile                           |   1 +
 kernel/bpf/stackmap.c                     |  14 +-
 kernel/events/callchain.c                 |  47 +--
 kernel/events/core.c                      |  70 +++-
 kernel/fork.c                             |  14 +
 kernel/sys.c                              |  11 +
 kernel/unwind/Makefile                    |   2 +
 kernel/unwind/sframe.c                    | 380 ++++++++++++++++++++++
 kernel/unwind/sframe.h                    | 215 ++++++++++++
 kernel/unwind/user.c                      | 318 ++++++++++++++++++
 mm/init-mm.c                              |   6 +
 tools/include/uapi/linux/perf_event.h     |  22 +-
 tools/lib/perf/include/perf/event.h       |   7 +
 tools/perf/Documentation/perf-script.txt  |   5 +
 tools/perf/builtin-script.c               |  92 ++++++
 tools/perf/util/callchain.c               |  24 ++
 tools/perf/util/callchain.h               |   3 +
 tools/perf/util/event.c                   |   1 +
 tools/perf/util/evlist.c                  |   1 +
 tools/perf/util/evlist.h                  |   1 +
 tools/perf/util/evsel.c                   |  32 +-
 tools/perf/util/evsel.h                   |   1 +
 tools/perf/util/machine.c                 |   1 +
 tools/perf/util/perf_event_attr_fprintf.c |   1 +
 tools/perf/util/sample.h                  |   3 +-
 tools/perf/util/session.c                 |  78 +++++
 tools/perf/util/tool.c                    |   2 +
 tools/perf/util/tool.h                    |   4 +-
 50 files changed, 1634 insertions(+), 88 deletions(-)
 create mode 100644 arch/x86/include/asm/unwind_user.h
 create mode 100644 include/linux/sframe.h
 create mode 100644 include/linux/unwind_user.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.47.0
author maintainer
#1 lore raw

PATCH v3 1/19 x86/vdso: Fix DWARF generation for getrandom()

Enable DWARF generation for the VDSO implementation of getrandom().

Fixes 33385150ac45 ("x86: vdso: Wire up getrandom() vDSO implementation")
---
 arch/x86/entry/vdso/vgetrandom-chacha.S | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
arch/x86/entry/vdso/vgetrandom-chacha.S +3 -2
index bcba5639b8ee..cc82da9216fb 100644
@@ -4,7 +4,7 @@
  */
 
 #include <linux/linkage.h>
-#include <asm/frame.h>
+#include <asm/dwarf2.h>
 
 .section	.rodata, "a"
 .align 16
@@ -22,7 +22,7 @@ CONSTANTS:	.octa 0x6b20657479622d323320646e61707865
  *	rcx: number of 64-byte blocks to write to output
  */
 SYM_FUNC_START(__arch_chacha20_blocks_nostack)
-
+	CFI_STARTPROC
 .set	output,		%rdi
 .set	key,		%rsi
 .set	counter,	%rdx
@@ -175,4 +175,5 @@ SYM_FUNC_START(__arch_chacha20_blocks_nostack)
 	pxor		temp,temp
 
 	ret
+	CFI_ENDPROC
 SYM_FUNC_END(__arch_chacha20_blocks_nostack)
Signature · 1 line
2.47.0
author maintainer
#2 lore raw

PATCH v3 2/19 x86/asm: Avoid emitting DWARF CFI for non-VDSO

In reply to Josh Poimboeuf

VDSO is the only part of the "kernel" using DWARF CFI directives. For the kernel proper, ensure the CFI_* macros don't do anything.

These macros aren't yet being used outside of VDSO, so there's no functional change.

---
 arch/x86/include/asm/dwarf2.h | 37 +++++++++++++++++++++++++++--------
 1 file changed, 29 insertions(+), 8 deletions(-)
arch/x86/include/asm/dwarf2.h +29 -8
index 430fca13bb56..b1aa3fcd5bca 100644
@@ -6,6 +6,15 @@
 #warning "asm/dwarf2.h should be only included in pure assembly files"
 #endif
 
+#ifdef BUILD_VDSO
+
+	/*
+	 * For the vDSO, emit both runtime unwind information and debug
+	 * symbols for the .dbg file.
+	 */
+
+	.cfi_sections .eh_frame, .debug_frame
+
 #define CFI_STARTPROC		.cfi_startproc
 #define CFI_ENDPROC		.cfi_endproc
 #define CFI_DEF_CFA		.cfi_def_cfa
@@ -21,7 +30,8 @@
 #define CFI_UNDEFINED		.cfi_undefined
 #define CFI_ESCAPE		.cfi_escape
 
-#ifndef BUILD_VDSO
+#else /* !BUILD_VDSO */
+
 	/*
 	 * Emit CFI data in .debug_frame sections, not .eh_frame sections.
 	 * The latter we currently just discard since we don't do DWARF
@@ -29,13 +39,24 @@
 	 * useful to anyone.  Note we should not use this directive if we
 	 * ever decide to enable DWARF unwinding at runtime.
 	 */
+
 	.cfi_sections .debug_frame
-#else
-	 /*
-	  * For the vDSO, emit both runtime unwind information and debug
-	  * symbols for the .dbg file.
-	  */
-	.cfi_sections .eh_frame, .debug_frame
-#endif
+
+#define CFI_STARTPROC
+#define CFI_ENDPROC
+#define CFI_DEF_CFA
+#define CFI_DEF_CFA_REGISTER
+#define CFI_DEF_CFA_OFFSET
+#define CFI_ADJUST_CFA_OFFSET
+#define CFI_OFFSET
+#define CFI_REL_OFFSET
+#define CFI_REGISTER
+#define CFI_RESTORE
+#define CFI_REMEMBER_STATE
+#define CFI_RESTORE_STATE
+#define CFI_UNDEFINED
+#define CFI_ESCAPE
+
+#endif /* !BUILD_VDSO */
 
 #endif /* _ASM_X86_DWARF2_H */
Signature · 1 line
2.47.0
#3 lore raw

Re: [PATCH v3 02/19] x86/asm: Avoid emitting DWARF CFI for non-VDSO

Quoted 5 lines · Josh Poimboeuf

VDSO is the only part of the "kernel" using DWARF CFI directives. For the kernel proper, ensure the CFI_* macros don't do anything.

These macros aren't yet being used outside of VDSO, so there's no functional change.

...

Quoted 4 lines
arch/x86/include/asm/dwarf2.h +0 -0
index 430fca13bb56..b1aa3fcd5bca 100644

...

Quoted 18 lines
arch/x86/include/asm/dwarf2.h +6 -7
@@ -29,13 +39,24 @@
  	 * useful to anyone.  Note we should not use this directive if we
  	 * ever decide to enable DWARF unwinding at runtime.
  	 */
+
  	.cfi_sections .debug_frame
-#else
-	 /*
-	  * For the vDSO, emit both runtime unwind information and debug
-	  * symbols for the .dbg file.
-	  */
-	.cfi_sections .eh_frame, .debug_frame
-#endif
+
+#define CFI_STARTPROC
+#define CFI_ENDPROC
+#define CFI_DEF_CFA
+#define CFI_DEF_CFA_REGISTER

arch/x86/include/asm/dwarf2.h line 58

Quoted 10 lines
arch/x86/include/asm/dwarf2.h +10 -0
+#define CFI_DEF_CFA_OFFSET
+#define CFI_ADJUST_CFA_OFFSET
+#define CFI_OFFSET
+#define CFI_REL_OFFSET
+#define CFI_REGISTER
+#define CFI_RESTORE
+#define CFI_REMEMBER_STATE
+#define CFI_RESTORE_STATE
+#define CFI_UNDEFINED
+#define CFI_ESCAPE

Don't all of the above need to be defined to "#", so that they change any parameters into an assembler comment? For example:

#define CFI_STARTPROC #

arch/x86/include/asm/dwarf2.h line 62

Quoted 4 lines
arch/x86/include/asm/dwarf2.h +2 -0
+
+#endif /* !BUILD_VDSO */
 
  #endif /* _ASM_X86_DWARF2_H */

Thanks and regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
author maintainer
#4 lore raw

Re: [PATCH v3 02/19] x86/asm: Avoid emitting DWARF CFI for non-VDSO

Quoted 18 lines · Jens Remus
Quoted 4 lines
arch/x86/include/asm/dwarf2.h +4 -0
+#define CFI_STARTPROC
+#define CFI_ENDPROC
+#define CFI_DEF_CFA
+#define CFI_DEF_CFA_REGISTER

arch/x86/include/asm/dwarf2.h line 58

Quoted 10 lines
arch/x86/include/asm/dwarf2.h +10 -0
+#define CFI_DEF_CFA_OFFSET
+#define CFI_ADJUST_CFA_OFFSET
+#define CFI_OFFSET
+#define CFI_REL_OFFSET
+#define CFI_REGISTER
+#define CFI_RESTORE
+#define CFI_REMEMBER_STATE
+#define CFI_RESTORE_STATE
+#define CFI_UNDEFINED
+#define CFI_ESCAPE

Don't all of the above need to be defined to "#", so that they change any parameters into an assembler comment? For example:

Quoted 1 line

#define CFI_STARTPROC #

Indeed. Or maybe they could be .macros which ignore the arguments.

Signature · 1 line
Josh
author maintainer
#5 lore raw

PATCH v3 3/19 x86/asm: Fix VDSO DWARF generation with kernel IBT enabled

In reply to Josh Poimboeuf

The DWARF .cfi_startproc annotation needs to be at the very beginning of a function. But with kernel IBT that doesn't happen as ENDBR is sneakily embedded in SYM_FUNC_START. So the DWARF unwinding info is wrong at the beginning of the VDSO functions.

Fix it by adding CFI_STARTPROC and CFI_ENDPROC to SYM_FUNC_START_* and SYM_FUNC_END respectively. Note this only affects VDSO, as the CFI_* macros are empty for the kernel proper.

Fixes c4691712b546 ("x86/linkage: Add ENDBR to SYM_FUNC_START*()")
---
 arch/x86/entry/vdso/vdso-layout.lds.S   |  2 +-
 arch/x86/entry/vdso/vgetrandom-chacha.S |  2 --
 arch/x86/entry/vdso/vsgx.S              |  4 ----
 arch/x86/include/asm/linkage.h          | 29 +++++++++++++++++++------
 arch/x86/include/asm/vdso.h             |  1 -
 5 files changed, 23 insertions(+), 15 deletions(-)
arch/x86/entry/vdso/vdso-layout.lds.S +1 -1
index bafa73f09e92..a42c7d4a33da 100644
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-2.0 */
-#include <asm/vdso.h>
+#include <asm/page_types.h>
 
 /*
  * Linker script for vDSO.  This is an ELF shared object prelinked to
arch/x86/entry/vdso/vgetrandom-chacha.S +0 -2
index cc82da9216fb..a33212594731 100644
@@ -22,7 +22,6 @@ CONSTANTS:	.octa 0x6b20657479622d323320646e61707865
  *	rcx: number of 64-byte blocks to write to output
  */
 SYM_FUNC_START(__arch_chacha20_blocks_nostack)
-	CFI_STARTPROC
 .set	output,		%rdi
 .set	key,		%rsi
 .set	counter,	%rdx
@@ -175,5 +174,4 @@ SYM_FUNC_START(__arch_chacha20_blocks_nostack)
 	pxor		temp,temp
 
 	ret
-	CFI_ENDPROC
 SYM_FUNC_END(__arch_chacha20_blocks_nostack)
arch/x86/entry/vdso/vsgx.S +0 -4
index 37a3d4c02366..c0342238c976 100644
@@ -24,8 +24,6 @@
 .section .text, "ax"
 
 SYM_FUNC_START(__vdso_sgx_enter_enclave)
-	/* Prolog */
-	.cfi_startproc
 	push	%rbp
 	.cfi_adjust_cfa_offset	8
 	.cfi_rel_offset		%rbp, 0
@@ -143,8 +141,6 @@ SYM_FUNC_START(__vdso_sgx_enter_enclave)
 	jle	.Lout
 	jmp	.Lenter_enclave
 
-	.cfi_endproc
-
 _ASM_VDSO_EXTABLE_HANDLE(.Lenclu_eenter_eresume, .Lhandle_exception)
 
 SYM_FUNC_END(__vdso_sgx_enter_enclave)
arch/x86/include/asm/linkage.h +22 -7
index dc31b13b87a0..2866d57ef907 100644
@@ -40,6 +40,10 @@
 
 #ifdef __ASSEMBLY__
 
+#ifndef LINKER_SCRIPT
+#include <asm/dwarf2.h>
+#endif
+
 #if defined(CONFIG_MITIGATION_RETHUNK) && !defined(__DISABLE_EXPORTS) && !defined(BUILD_VDSO)
 #define RET	jmp __x86_return_thunk
 #else /* CONFIG_MITIGATION_RETPOLINE */
@@ -112,40 +116,51 @@
 # define SYM_FUNC_ALIAS_MEMFUNC	SYM_FUNC_ALIAS
 #endif
 
+#define __SYM_FUNC_START				\
+	CFI_STARTPROC ASM_NL				\
+	ENDBR
+
+#define __SYM_FUNC_END					\
+	CFI_ENDPROC ASM_NL
+
 /* SYM_TYPED_FUNC_START -- use for indirectly called globals, w/ CFI type */
 #define SYM_TYPED_FUNC_START(name)				\
 	SYM_TYPED_START(name, SYM_L_GLOBAL, SYM_F_ALIGN)	\
-	ENDBR
+	__SYM_FUNC_START
 
 /* SYM_FUNC_START -- use for global functions */
 #define SYM_FUNC_START(name)				\
 	SYM_START(name, SYM_L_GLOBAL, SYM_F_ALIGN)	\
-	ENDBR
+	__SYM_FUNC_START
 
 /* SYM_FUNC_START_NOALIGN -- use for global functions, w/o alignment */
 #define SYM_FUNC_START_NOALIGN(name)			\
 	SYM_START(name, SYM_L_GLOBAL, SYM_A_NONE)	\
-	ENDBR
+	__SYM_FUNC_START
 
 /* SYM_FUNC_START_LOCAL -- use for local functions */
 #define SYM_FUNC_START_LOCAL(name)			\
 	SYM_START(name, SYM_L_LOCAL, SYM_F_ALIGN)	\
-	ENDBR
+	__SYM_FUNC_START
 
 /* SYM_FUNC_START_LOCAL_NOALIGN -- use for local functions, w/o alignment */
 #define SYM_FUNC_START_LOCAL_NOALIGN(name)		\
 	SYM_START(name, SYM_L_LOCAL, SYM_A_NONE)	\
-	ENDBR
+	__SYM_FUNC_START
 
 /* SYM_FUNC_START_WEAK -- use for weak functions */
 #define SYM_FUNC_START_WEAK(name)			\
 	SYM_START(name, SYM_L_WEAK, SYM_F_ALIGN)	\
-	ENDBR
+	__SYM_FUNC_START
 
 /* SYM_FUNC_START_WEAK_NOALIGN -- use for weak functions, w/o alignment */
 #define SYM_FUNC_START_WEAK_NOALIGN(name)		\
 	SYM_START(name, SYM_L_WEAK, SYM_A_NONE)		\
-	ENDBR
+	__SYM_FUNC_START
+
+#define SYM_FUNC_END(name)				\
+	__SYM_FUNC_END					\
+	SYM_END(name, SYM_T_FUNC)
 
 #endif /* _ASM_X86_LINKAGE_H */
 
arch/x86/include/asm/vdso.h +0 -1
index d7f6592b74a9..0111c349bbc5 100644
@@ -2,7 +2,6 @@
 #ifndef _ASM_X86_VDSO_H
 #define _ASM_X86_VDSO_H
 
-#include <asm/page_types.h>
 #include <linux/linkage.h>
 #include <linux/init.h>
 
Signature · 1 line
2.47.0
author maintainer
#6 lore raw

PATCH v3 4/19 x86/vdso: Use SYM_FUNC_{START,END} in __kernel_vsyscall()

In reply to Josh Poimboeuf

Use SYM_FUNC_{START,END} instead of all the boilerplate. No functional change.

---
 arch/x86/entry/vdso/vdso32/system_call.S | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)
arch/x86/entry/vdso/vdso32/system_call.S +2 -8
index d33c6513fd2c..bdc576548240 100644
@@ -9,11 +9,7 @@
 #include <asm/alternative.h>
 
 	.text
-	.globl __kernel_vsyscall
-	.type __kernel_vsyscall,@function
-	ALIGN
-__kernel_vsyscall:
-	CFI_STARTPROC
+SYM_FUNC_START(__kernel_vsyscall)
 	/*
 	 * Reshuffle regs so that all of any of the entry instructions
 	 * will preserve enough state.
@@ -79,7 +75,5 @@ SYM_INNER_LABEL(int80_landing_pad, SYM_L_GLOBAL)
 	CFI_RESTORE		ecx
 	CFI_ADJUST_CFA_OFFSET	-4
 	RET
-	CFI_ENDPROC
-
-	.size __kernel_vsyscall,.-__kernel_vsyscall
+SYM_FUNC_END(__kernel_vsyscall)
 	.previous
Signature · 1 line
2.47.0
author maintainer
#7 lore raw

PATCH v3 5/19 x86/vdso: Use CFI macros in __vdso_sgx_enter_enclave()

In reply to Josh Poimboeuf

Use the CFI macros instead of the raw .cfi_* directives to be consistent with the rest of the VDSO asm. It's also easier on the eyes.

No functional changes.

---
 arch/x86/entry/vdso/vsgx.S | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)
arch/x86/entry/vdso/vsgx.S +7 -8
index c0342238c976..8d7b8eb45c50 100644
@@ -24,13 +24,14 @@
 .section .text, "ax"
 
 SYM_FUNC_START(__vdso_sgx_enter_enclave)
+	SYM_F_ALIGN
 	push	%rbp
-	.cfi_adjust_cfa_offset	8
-	.cfi_rel_offset		%rbp, 0
+	CFI_ADJUST_CFA_OFFSET	8
+	CFI_REL_OFFSET		%rbp, 0
 	mov	%rsp, %rbp
-	.cfi_def_cfa_register	%rbp
+	CFI_DEF_CFA_REGISTER	%rbp
 	push	%rbx
-	.cfi_rel_offset		%rbx, -8
+	CFI_REL_OFFSET		%rbx, -8
 
 	mov	%ecx, %eax
 .Lenter_enclave:
@@ -77,13 +78,11 @@ SYM_FUNC_START(__vdso_sgx_enter_enclave)
 .Lout:
 	pop	%rbx
 	leave
-	.cfi_def_cfa		%rsp, 8
+	CFI_DEF_CFA		%rsp, 8
 	RET
 
-	/* The out-of-line code runs with the pre-leave stack frame. */
-	.cfi_def_cfa		%rbp, 16
-
 .Linvalid_input:
+	CFI_DEF_CFA		%rbp, 16
 	mov	$(-EINVAL), %eax
 	jmp	.Lout
 
Signature · 1 line
2.47.0
author maintainer
#8 lore raw

PATCH v3 6/19 x86/vdso: Enable sframe generation in VDSO

In reply to Josh Poimboeuf

Enable sframe generation in the VDSO library so kernel and user space can unwind through it.

---
 arch/x86/entry/vdso/Makefile          | 6 ++++--
 arch/x86/entry/vdso/vdso-layout.lds.S | 3 +++
 arch/x86/include/asm/dwarf2.h         | 5 ++++-
 3 files changed, 11 insertions(+), 3 deletions(-)
arch/x86/entry/vdso/Makefile +4 -2
index c9216ac4fb1e..75ae9e093a2d 100644
@@ -47,13 +47,15 @@ quiet_cmd_vdso2c = VDSO2C  $@
 $(obj)/vdso-image-%.c: $(obj)/vdso%.so.dbg $(obj)/vdso%.so $(obj)/vdso2c FORCE
 	$(call if_changed,vdso2c)
 
+SFRAME_CFLAGS := $(call as-option,-Wa$(comma)-gsframe,)
+
 #
 # Don't omit frame pointers for ease of userspace debugging, but do
 # optimize sibling calls.
 #
 CFL := $(PROFILING) -mcmodel=small -fPIC -O2 -fasynchronous-unwind-tables -m64 \
        $(filter -g%,$(KBUILD_CFLAGS)) -fno-stack-protector \
-       -fno-omit-frame-pointer -foptimize-sibling-calls \
+       -fno-omit-frame-pointer $(SFRAME_CFLAGS) -foptimize-sibling-calls \
        -DDISABLE_BRANCH_PROFILING -DBUILD_VDSO
 
 ifdef CONFIG_MITIGATION_RETPOLINE
@@ -63,7 +65,7 @@ endif
 endif
 
 $(vobjs): KBUILD_CFLAGS := $(filter-out $(PADDING_CFLAGS) $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) $(RANDSTRUCT_CFLAGS) $(GCC_PLUGINS_CFLAGS) $(RETPOLINE_CFLAGS),$(KBUILD_CFLAGS)) $(CFL)
-$(vobjs): KBUILD_AFLAGS += -DBUILD_VDSO
+$(vobjs): KBUILD_AFLAGS += -DBUILD_VDSO $(SFRAME_CFLAGS)
 
 #
 # vDSO code runs in userspace and -pg doesn't help with profiling anyway.
arch/x86/entry/vdso/vdso-layout.lds.S +3 -0
index a42c7d4a33da..f6cd6654bb9e 100644
@@ -69,6 +69,7 @@ SECTIONS
 	.eh_frame_hdr	: { *(.eh_frame_hdr) }		:text	:eh_frame_hdr
 	.eh_frame	: { KEEP (*(.eh_frame)) }	:text
 
+	.sframe		: { *(.sframe) }		:text	:sframe
 
 	/*
 	 * Text is well-separated from actual data: there's plenty of
@@ -97,6 +98,7 @@ SECTIONS
  * Very old versions of ld do not recognize this name token; use the constant.
  */
 #define PT_GNU_EH_FRAME	0x6474e550
+#define PT_GNU_SFRAME	0x6474e554
 
 /*
  * We must supply the ELF program headers explicitly to get just one
@@ -108,4 +110,5 @@ PHDRS
 	dynamic		PT_DYNAMIC	FLAGS(4);		/* PF_R */
 	note		PT_NOTE		FLAGS(4);		/* PF_R */
 	eh_frame_hdr	PT_GNU_EH_FRAME;
+	sframe		PT_GNU_SFRAME;
 }
arch/x86/include/asm/dwarf2.h +4 -1
index b1aa3fcd5bca..1a49492817a1 100644
@@ -12,8 +12,11 @@
 	 * For the vDSO, emit both runtime unwind information and debug
 	 * symbols for the .dbg file.
 	 */
-
+#ifdef __x86_64__
+	.cfi_sections .eh_frame, .debug_frame, .sframe
+#else
 	.cfi_sections .eh_frame, .debug_frame
+#endif
 
 #define CFI_STARTPROC		.cfi_startproc
 #define CFI_ENDPROC		.cfi_endproc
Signature · 1 line
2.47.0
#9 lore raw

Re: [PATCH v3 06/19] x86/vdso: Enable sframe generation in VDSO

Quoted 2 lines · Josh Poimboeuf

Enable sframe generation in the VDSO library so kernel and user space can unwind through it.

...

Applying similar changes to s390 and using the current binutils release without SFrame support on s390 results in build errors.

AFAIK the kernel has a minimum binutils requirement of 2.25 [1] and the assembler option "--gsframe was introduced with 2.40.

arch/x86/entry/vdso/Makefile quiet_cmd_vdso2c = VDSO2C $@ · line 50

Quoted 9 lines
arch/x86/entry/vdso/Makefile +1 -0
index c9216ac4fb1e..75ae9e093a2d 100644
@@ -47,13 +47,15 @@ quiet_cmd_vdso2c = VDSO2C  $@
  $(obj)/vdso-image-%.c: $(obj)/vdso%.so.dbg $(obj)/vdso%.so $(obj)/vdso2c FORCE
  	$(call if_changed,vdso2c)
 
+SFRAME_CFLAGS := $(call as-option,-Wa$(comma)-gsframe,)

I have the impression this test might not work as expected. On s390 the assembler accepts option --gsframe and only generates an error when the assembler code actually contains any CFI directives:

$ cat test-nocfi.a
        la      %r1,42
$ as --gsframe test-nocfi.a
$ echo $?
0
$ cat test-cfi.a
        .cfi_startproc
        .cfi_endproc
$ as --gsframe test-cfi.a
test-cfi.a: Assembler messages:
test-cfi.a: Error: .sframe not supported for target
$ echo $?
1

But the following assembler code triggers an error:

$ cat test-sframe.a
        .cfi_sections .sframe
        .cfi_startproc
        .cfi_endproc
$ as test-sframe.a
test-sframe.a: Assembler messages:
test-sframe.a: Error: .sframe not supported for target
$ echo $?
1

Maybe the following would be an alternative test in the Makefile?

SFRAME_CFLAGS := $(call as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_SFRAME=1) ifneq ($(SFRAME_CFLAGS),)

       SFRAME_CFLAGS += -Wa,--gsframe

endif

arch/x86/include/asm/dwarf2.h line 15

Quoted 10 lines
arch/x86/include/asm/dwarf2.h +1 -1
index b1aa3fcd5bca..1a49492817a1 100644
@@ -12,8 +12,11 @@
  	 * For the vDSO, emit both runtime unwind information and debug
  	 * symbols for the .dbg file.
  	 */
-
+#ifdef __x86_64__

#if defined(__x86_64__) && defined(CONFIG_AS_SFRAME)

arch/x86/include/asm/dwarf2.h line 22

Quoted 7 lines
arch/x86/include/asm/dwarf2.h +3 -0
+	.cfi_sections .eh_frame, .debug_frame, .sframe
+#else
  	.cfi_sections .eh_frame, .debug_frame
+#endif
 
  #define CFI_STARTPROC		.cfi_startproc
  #define CFI_ENDPROC		.cfi_endproc

[1]: https://docs.kernel.org/process/changes.html

Regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
author maintainer
#10 lore raw

Re: [PATCH v3 06/19] x86/vdso: Enable sframe generation in VDSO

Quoted 10 lines · Jens Remus

arch/x86/entry/vdso/Makefile quiet_cmd_vdso2c = VDSO2C $@ · line 49

Quoted 8 lines
arch/x86/entry/vdso/Makefile +1 -0
index c9216ac4fb1e..75ae9e093a2d 100644
@@ -47,13 +47,15 @@ quiet_cmd_vdso2c = VDSO2C  $@
  $(obj)/vdso-image-%.c: $(obj)/vdso%.so.dbg $(obj)/vdso%.so $(obj)/vdso2c FORCE
  	$(call if_changed,vdso2c)
+SFRAME_CFLAGS := $(call as-option,-Wa$(comma)-gsframe,)

I have the impression this test might not work as expected.

I suspect it works fine on x86-64, since that was the first arch supported, so --gsframe being supported also means x86-64 is supported.

But yeah, other arches (and x86-32) are a different story.

Quoted 6 lines

Maybe the following would be an alternative test in the Makefile?

SFRAME_CFLAGS := $(call as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_SFRAME=1) ifneq ($(SFRAME_CFLAGS),)

       SFRAME_CFLAGS += -Wa,--gsframe

endif

Looks good, though the ifneq isn't needed:

SFRAME_CFLAGS := $(call as-instr, \
		   .cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc, \
		   Wa$(comma)--gsframe -DCONFIG_AS_SFRAME=1)

Though, if multiple arches are going to be using that, maybe it should just be a config option:

arch/Kconfig +3 -0
index 33449485eafd..676dd45a7255 100644
@@ -446,6 +446,9 @@ config HAVE_UNWIND_USER_SFRAME
 	bool
 	select UNWIND_USER
 
+config AS_SFRAME
+	def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc)
+
 config HAVE_PERF_CALLCHAIN_DEFERRED
 	bool
 
author maintainer
#11 lore raw

PATCH v3 7/19 unwind: Add user space unwinding API

In reply to Josh Poimboeuf

Introduce a user space unwinder API which provides a generic way to unwind user stacks.

---
 arch/Kconfig                |  7 +++
 include/linux/unwind_user.h | 41 +++++++++++++++
 kernel/Makefile             |  1 +
 kernel/unwind/Makefile      |  1 +
 kernel/unwind/user.c        | 99 +++++++++++++++++++++++++++++++++++++
 5 files changed, 149 insertions(+)
 create mode 100644 include/linux/unwind_user.h
 create mode 100644 kernel/unwind/Makefile
 create mode 100644 kernel/unwind/user.c
arch/Kconfig +7 -0
index 7a95c1052cd5..ee8ec97ea0ef 100644
@@ -435,6 +435,13 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH
 	  It uses the same command line parameters, and sysctl interface,
 	  as the generic hardlockup detectors.
 
+config UNWIND_USER
+	bool
+
+config HAVE_UNWIND_USER_FP
+	bool
+	select UNWIND_USER
+
 config HAVE_PERF_REGS
 	bool
 	help
include/linux/unwind_user.h new file +41 -0

41 lines not on this page · raw message

kernel/Makefile +1 -0
index 87866b037fbe..6cb4b0e02a34 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..349ce3677526
@@ -0,0 +1 @@
+ obj-$(CONFIG_UNWIND_USER) += user.o
kernel/unwind/user.c new file +99 -0

99 lines not on this page · raw message

Signature · 1 line
2.47.0
#12 lore raw

Re: [PATCH v3 07/19] unwind: Add user space unwinding API

Quoted 2 lines · Josh Poimboeuf

Introduce a user space unwinder API which provides a generic way to unwind user stacks.

...

Quoted 1 line
kernel/unwind/user.c +0 -0

...

Quoted 16 lines
kernel/unwind/user.c +16 -0
+int unwind_user_next(struct unwind_user_state *state)
+{
+	struct unwind_user_frame _frame;
+	struct unwind_user_frame *frame = &_frame;
+	unsigned long prev_ip, cfa, fp, ra = 0;
+
+	if (state->done)
+		return -EINVAL;
+
+	prev_ip = state->ip;
+
+	switch (state->type) {
+	case UNWIND_USER_TYPE_FP:
+		frame = &fp_frame;
+		break;
+	default:

kernel/unwind/user.c line 43

Quoted 10 lines
kernel/unwind/user.c +10 -0
+		BUG();
+	}
+
+	cfa = (frame->use_fp ? state->fp : state->sp) + frame->cfa_off;
+
+	if (frame->ra_off && get_user(ra, (unsigned long __user *)(cfa + frame->ra_off)))
+		goto the_end;
+
+	if (ra == prev_ip)
+		goto the_end;

This seems too restrictive to me, as it effectively prevents unwinding from recursive functions, e.g. Glibc internal merge sort msort_with_tmp():

$ perf record -F 9999 --call-graph fp /usr/bin/objdump -wdWF /usr/bin/objdump
$ perf script
...
objdump    8314 236064.515562:     100010 task-clock:ppp:
                 100630a compare_symbols+0x2a (/usr/bin/objdump)
             3ffb9e58e7c msort_with_tmp.part.0+0x15c (/usr/lib64/libc.so.6)
             3ffb9e58d76 msort_with_tmp.part.0+0x56 (/usr/lib64/libc.so.6)
[unwinding unexpectedly stops]

Would it be an option to only stop unwinding if both the IP and SP do not change?

if (sp == prev_sp && ra == prev_ra)

	gote the_end;
Quoted 5 lines
kernel/unwind/user.c +5 -0
+
+	if (frame->fp_off && get_user(fp, (unsigned long __user *)(cfa + frame->fp_off)))
+		goto the_end;
+
+	state->sp = cfa;

kernel/unwind/user.c line 58

Quoted 10 lines
kernel/unwind/user.c +10 -0
+	state->ip = ra;
+	if (frame->fp_off)
+		state->fp = fp;
+
+	return 0;
+
+the_end:
+	state->done = true;
+	return -EINVAL;
+}

...

Thanks and regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
author maintainer
#13 lore raw

Re: [PATCH v3 07/19] unwind: Add user space unwinding API

Quoted 17 lines · Jens Remus

Review

Quoted 2 lines · Josh Poimboeuf
(quoted diff) +2 -0
+	if (ra == prev_ip)
+		goto the_end;

This seems too restrictive to me, as it effectively prevents unwinding from recursive functions, e.g. Glibc internal merge sort msort_with_tmp():

$ perf record -F 9999 --call-graph fp /usr/bin/objdump -wdWF /usr/bin/objdump
$ perf script
...
objdump    8314 236064.515562:     100010 task-clock:ppp:
                 100630a compare_symbols+0x2a (/usr/bin/objdump)
             3ffb9e58e7c msort_with_tmp.part.0+0x15c (/usr/lib64/libc.so.6)
             3ffb9e58d76 msort_with_tmp.part.0+0x56 (/usr/lib64/libc.so.6)
[unwinding unexpectedly stops]
Quoted 5 lines

Would it be an option to only stop unwinding if both the IP and SP do not change?

if (sp == prev_sp && ra == prev_ra)

	gote the_end;

Good point, I've already fixed that for the next version (not yet posted). I believe the only thing we really need to check here is that the unwind is heading in the right direction:

if (cfa <= state->sp)

	goto the_end;
Signature · 1 line
Josh
#14 lore raw

Re: [PATCH v3 07/19] unwind: Add user space unwinding API

Quoted 24 lines · Josh Poimboeuf
Quoted 17 lines · Jens Remus

Review

Quoted 2 lines · Josh Poimboeuf
(quoted diff) +2 -0
+	if (ra == prev_ip)
+		goto the_end;

This seems too restrictive to me, as it effectively prevents unwinding from recursive functions, e.g. Glibc internal merge sort msort_with_tmp():

$ perf record -F 9999 --call-graph fp /usr/bin/objdump -wdWF /usr/bin/objdump
$ perf script
...
objdump    8314 236064.515562:     100010 task-clock:ppp:
                  100630a compare_symbols+0x2a (/usr/bin/objdump)
              3ffb9e58e7c msort_with_tmp.part.0+0x15c (/usr/lib64/libc.so.6)
              3ffb9e58d76 msort_with_tmp.part.0+0x56 (/usr/lib64/libc.so.6)
[unwinding unexpectedly stops]
Quoted 5 lines

Would it be an option to only stop unwinding if both the IP and SP do not change?

if (sp == prev_sp && ra == prev_ra)

	gote the_end;
Quoted 6 lines

Good point, I've already fixed that for the next version (not yet posted). I believe the only thing we really need to check here is that the unwind is heading in the right direction:

if (cfa <= state->sp)

	goto the_end;

Assuming the x86 definition of the CFA (CFA == SP at call site) this translates into:

if (sp <= state->sp)

	goto the_end;

That won't work for architectures that pass the return address in a register instead of on the stack, such as s390. At least in the topmost frame the unwound SP may be unchanged. For instance when in the function prologue or when in a leaf function.

One of my patches for s390 support introduces a state->first flag, indicating whether it is the topmost user space frame. Using that your check could be extended to:

if ((state->first && sp < state->sp) || (!state->first && sp <= state->sp))

	goto the_end;

Which could be simplified to:

if (sp <= state->sp - state->first)

	goto the_end;

Btw. neither would work for architectures with an upwards-growing stack, such as hppa. Not sure if that needs to be considered.

Regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
author maintainer
#15 lore raw

Re: [PATCH v3 07/19] unwind: Add user space unwinding API

Quoted 19 lines · Jens Remus
Quoted 2 lines · Josh Poimboeuf

if (cfa <= state->sp)

	goto the_end;

Assuming the x86 definition of the CFA (CFA == SP at call site) this translates into:

if (sp <= state->sp)

	goto the_end;

That won't work for architectures that pass the return address in a register instead of on the stack, such as s390. At least in the topmost frame the unwound SP may be unchanged. For instance when in the function prologue or when in a leaf function.

One of my patches for s390 support introduces a state->first flag, indicating whether it is the topmost user space frame. Using that your check could be extended to:

Quoted 7 lines

if ((state->first && sp < state->sp) || (!state->first && sp <= state->sp))

	goto the_end;

Which could be simplified to:

if (sp <= state->sp - state->first)

	goto the_end;

Since my patches are x86-only, how about I leave the "sp <= state->sp" check and then you add something like that in your patches on top?

Quoted 2 lines

Btw. neither would work for architectures with an upwards-growing stack, such as hppa. Not sure if that needs to be considered.

I don't think that's needed until if/when sframe becomes supported for such an arch.

Signature · 1 line
Josh
author maintainer
#16 lore raw

PATCH v3 8/19 unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_FP

In reply to Josh Poimboeuf

Use ARCH_INIT_USER_FP_FRAME to describe how frame pointers are unwound on x86, and enable CONFIG_HAVE_UNWIND_USER_FP accordingly so the unwind_user interfaces can be used.

---
 arch/x86/Kconfig                   |  1 +
 arch/x86/include/asm/unwind_user.h | 11 +++++++++++
 2 files changed, 12 insertions(+)
 create mode 100644 arch/x86/include/asm/unwind_user.h
arch/x86/Kconfig +1 -0
index 0bdb7a394f59..f91098d6f535 100644
@@ -289,6 +289,7 @@ config X86
 	select HAVE_SYSCALL_TRACEPOINTS
 	select HAVE_UACCESS_VALIDATION		if HAVE_OBJTOOL
 	select HAVE_UNSTABLE_SCHED_CLOCK
+	select HAVE_UNWIND_USER_FP		if X86_64
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_GENERIC_VDSO
 	select VDSO_GETRANDOM			if X86_64
arch/x86/include/asm/unwind_user.h new file +11 -0
new file mode 100644
index 000000000000..19df26a65132
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_X86_UNWIND_USER_H
+#define _ASM_X86_UNWIND_USER_H
+
+#define ARCH_INIT_USER_FP_FRAME							\
+	.ra_off		= (s32)sizeof(long) * -1,				\
+	.cfa_off	= (s32)sizeof(long) * 2,				\
+	.fp_off		= (s32)sizeof(long) * -2,				\
+	.use_fp		= true,
+
+#endif /* _ASM_X86_UNWIND_USER_H */
Signature · 1 line
2.47.0
maintainer
#17 lore raw

Re: [PATCH v3 08/19] unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_FP

Quoted 30 lines · Josh Poimboeuf

Use ARCH_INIT_USER_FP_FRAME to describe how frame pointers are unwound on x86, and enable CONFIG_HAVE_UNWIND_USER_FP accordingly so the unwind_user interfaces can be used.

---
 arch/x86/Kconfig                   |  1 +
 arch/x86/include/asm/unwind_user.h | 11 +++++++++++
 2 files changed, 12 insertions(+)
 create mode 100644 arch/x86/include/asm/unwind_user.h
arch/x86/Kconfig +1 -0
index 0bdb7a394f59..f91098d6f535 100644
@@ -289,6 +289,7 @@ config X86
 	select HAVE_SYSCALL_TRACEPOINTS
 	select HAVE_UACCESS_VALIDATION		if HAVE_OBJTOOL
 	select HAVE_UNSTABLE_SCHED_CLOCK
+	select HAVE_UNWIND_USER_FP		if X86_64
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_GENERIC_VDSO
 	select VDSO_GETRANDOM			if X86_64
arch/x86/include/asm/unwind_user.h new file +1 -0
new file mode 100644
index 000000000000..19df26a65132
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */

arch/x86/include/asm/unwind_user.h line 11

Quoted 10 lines
arch/x86/include/asm/unwind_user.h new file +10 -0
+#ifndef _ASM_X86_UNWIND_USER_H
+#define _ASM_X86_UNWIND_USER_H
+
+#define ARCH_INIT_USER_FP_FRAME							\
+	.ra_off		= (s32)sizeof(long) * -1,				\
+	.cfa_off	= (s32)sizeof(long) * 2,				\
+	.fp_off		= (s32)sizeof(long) * -2,				\
+	.use_fp		= true,
+
+#endif /* _ASM_X86_UNWIND_USER_H */

What about compat / 32bit userspace?

author maintainer
#18 lore raw

Re: [PATCH v3 08/19] unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_FP

Quoted 35 lines · Peter Zijlstra
Quoted 34 lines · Josh Poimboeuf

Use ARCH_INIT_USER_FP_FRAME to describe how frame pointers are unwound on x86, and enable CONFIG_HAVE_UNWIND_USER_FP accordingly so the unwind_user interfaces can be used.

---
 arch/x86/Kconfig                   |  1 +
 arch/x86/include/asm/unwind_user.h | 11 +++++++++++
 2 files changed, 12 insertions(+)
 create mode 100644 arch/x86/include/asm/unwind_user.h
arch/x86/Kconfig +1 -0
index 0bdb7a394f59..f91098d6f535 100644
@@ -289,6 +289,7 @@ config X86
 	select HAVE_SYSCALL_TRACEPOINTS
 	select HAVE_UACCESS_VALIDATION		if HAVE_OBJTOOL
 	select HAVE_UNSTABLE_SCHED_CLOCK
+	select HAVE_UNWIND_USER_FP		if X86_64
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_GENERIC_VDSO
 	select VDSO_GETRANDOM			if X86_64
arch/x86/include/asm/unwind_user.h new file +5 -0
new file mode 100644
index 000000000000..19df26a65132
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_X86_UNWIND_USER_H
+#define _ASM_X86_UNWIND_USER_H
+
+#define ARCH_INIT_USER_FP_FRAME							\
Quoted 8 lines

arch/x86/include/asm/unwind_user.h line 11

Quoted 6 lines
arch/x86/include/asm/unwind_user.h new file +6 -0
+	.ra_off		= (s32)sizeof(long) * -1,				\
+	.cfa_off	= (s32)sizeof(long) * 2,				\
+	.fp_off		= (s32)sizeof(long) * -2,				\
+	.use_fp		= true,
+
+#endif /* _ASM_X86_UNWIND_USER_H */

What about compat / 32bit userspace?

Sframe doesn't support 32-bit binaries at the moment. Does anybody actually care?

You're right this regresses existing perf behavior for the frame pointer case. I'll try to fix that.

Signature · 1 line
Josh
maintainer
#19 lore raw

Re: [PATCH v3 08/19] unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_FP

Quoted 12 lines · Josh Poimboeuf
Quoted 9 lines · Peter Zijlstra

arch/x86/include/asm/unwind_user.h line 11

Quoted 7 lines
arch/x86/include/asm/unwind_user.h new file +7 -0
+#define ARCH_INIT_USER_FP_FRAME							\
+	.ra_off		= (s32)sizeof(long) * -1,				\
+	.cfa_off	= (s32)sizeof(long) * 2,				\
+	.fp_off		= (s32)sizeof(long) * -2,				\
+	.use_fp		= true,
+
+#endif /* _ASM_X86_UNWIND_USER_H */

What about compat / 32bit userspace?

Quoted 2 lines

Sframe doesn't support 32-bit binaries at the moment. Does anybody actually care?

Dunno, I think the only 32bit code I've recently ran is in selftests and wine32 -- because Monkey Island rules :-)

Quoted 2 lines

You're right this regresses existing perf behavior for the frame pointer case. I'll try to fix that.

Thanks, this patch is the frame pointer unwinder, that really should do 32bit.

author maintainer
#20 lore raw

PATCH v3 9/19 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 the complexity it would add to the kernel.

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.41 for sframe v2. Sframe is a simpler version of .eh_frame. It 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

Glibc support is needed to implement the prctl() calls to tell the kernel where the .sframe segments are.

---
 arch/Kconfig                |   4 +
 arch/x86/include/asm/mmu.h  |   2 +-
 fs/binfmt_elf.c             |  35 +++-
 include/linux/mm_types.h    |   3 +
 include/linux/sframe.h      |  41 ++++
 include/linux/unwind_user.h |   2 +
 include/uapi/linux/elf.h    |   1 +
 include/uapi/linux/prctl.h  |   3 +
 kernel/fork.c               |  10 +
 kernel/sys.c                |  11 ++
 kernel/unwind/Makefile      |   3 +-
 kernel/unwind/sframe.c      | 380 ++++++++++++++++++++++++++++++++++++
 kernel/unwind/sframe.h      | 215 ++++++++++++++++++++
 kernel/unwind/user.c        |  24 ++-
 mm/init-mm.c                |   6 +
 15 files changed, 732 insertions(+), 8 deletions(-)
 create mode 100644 include/linux/sframe.h
 create mode 100644 kernel/unwind/sframe.c
 create mode 100644 kernel/unwind/sframe.h
arch/Kconfig +4 -0
index ee8ec97ea0ef..e769c39dd221 100644
@@ -442,6 +442,10 @@ config HAVE_UNWIND_USER_FP
 	bool
 	select UNWIND_USER
 
+config HAVE_UNWIND_USER_SFRAME
+	bool
+	select UNWIND_USER
+
 config HAVE_PERF_REGS
 	bool
 	help
arch/x86/include/asm/mmu.h +1 -1
index ce4677b8b735..12ea831978cc 100644
@@ -73,7 +73,7 @@ typedef struct {
 	.context = {							\
 		.ctx_id = 1,						\
 		.lock = __MUTEX_INITIALIZER(mm.context.lock),		\
-	}
+	},
 
 void leave_mm(void);
 #define leave_mm leave_mm
fs/binfmt_elf.c +31 -4
index 06dc4a57ba78..434c548f0837 100644
@@ -47,6 +47,7 @@
 #include <linux/dax.h>
 #include <linux/uaccess.h>
 #include <linux/rseq.h>
+#include <linux/sframe.h>
 #include <asm/param.h>
 #include <asm/page.h>
 
@@ -633,11 +634,13 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 		unsigned long no_base, struct elf_phdr *interp_elf_phdata,
 		struct arch_elf_state *arch_state)
 {
-	struct elf_phdr *eppnt;
+	struct elf_phdr *eppnt, *sframe_phdr = NULL;
 	unsigned long load_addr = 0;
 	int load_addr_set = 0;
 	unsigned long error = ~0UL;
 	unsigned long total_size;
+	unsigned long start_code = ~0UL;
+	unsigned long end_code = 0;
 	int i;
 
 	/* First of all, some simple consistency checks */
@@ -659,7 +662,8 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 
 	eppnt = interp_elf_phdata;
 	for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
-		if (eppnt->p_type == PT_LOAD) {
+		switch (eppnt->p_type) {
+		case PT_LOAD: {
 			int elf_type = MAP_PRIVATE;
 			int elf_prot = make_prot(eppnt->p_flags, arch_state,
 						 true, true);
@@ -688,7 +692,7 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 			/*
 			 * Check to see if the section's size will overflow the
 			 * allowed task size. Note that p_filesz must always be
-			 * <= p_memsize so it's only necessary to check p_memsz.
+			 * <= p_memsz so it's only necessary to check p_memsz.
 			 */
 			k = load_addr + eppnt->p_vaddr;
 			if (BAD_ADDR(k) ||
@@ -698,9 +702,24 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 				error = -ENOMEM;
 				goto out;
 			}
+
+			if ((eppnt->p_flags & PF_X) && k < start_code)
+				start_code = k;
+
+			if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
+				end_code = k + eppnt->p_filesz;
+			break;
+		}
+		case PT_GNU_SFRAME:
+			sframe_phdr = eppnt;
+			break;
 		}
 	}
 
+	if (sframe_phdr)
+		sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+				   start_code, end_code);
+
 	error = load_addr;
 out:
 	return error;
@@ -823,7 +842,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	int first_pt_load = 1;
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-	struct elf_phdr *elf_property_phdata = NULL;
+	struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
 	unsigned long elf_brk;
 	int retval, i;
 	unsigned long elf_entry;
@@ -931,6 +950,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
 				executable_stack = EXSTACK_DISABLE_X;
 			break;
 
+		case PT_GNU_SFRAME:
+			sframe_phdr = elf_ppnt;
+			break;
+
 		case PT_LOPROC ... PT_HIPROC:
 			retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
 						  bprm->file, false,
@@ -1321,6 +1344,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
 					    task_pid_nr(current), retval);
 	}
 
+	if (sframe_phdr)
+		sframe_add_section(load_bias + sframe_phdr->p_vaddr,
+				   start_code, end_code);
+
 	regs = current_pt_regs();
 #ifdef ELF_PLAT_INIT
 	/*
include/linux/mm_types.h +3 -0
index 381d22eba088..6e7561c1a5fc 100644
@@ -1052,6 +1052,9 @@ struct mm_struct {
 #endif
 		} lru_gen;
 #endif /* CONFIG_LRU_GEN_WALKS_MMU */
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+		struct maple_tree sframe_mt;
+#endif
 	} __randomize_layout;
 
 	/*
include/linux/sframe.h new file +41 -0

41 lines not on this page · raw message

include/linux/unwind_user.h +2 -0
index 9d28db06f33f..cde0fde4923e 100644
@@ -5,7 +5,9 @@
 #include <linux/types.h>
 
 enum unwind_user_type {
+	UNWIND_USER_TYPE_NONE,
 	UNWIND_USER_TYPE_FP,
+	UNWIND_USER_TYPE_SFRAME,
 };
 
 struct unwind_stacktrace {
include/uapi/linux/elf.h +1 -0
index b9935988da5c..4dc3f0ca5af5 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 35791791a879..69511077c910 100644
@@ -328,4 +328,7 @@ struct prctl_mm_map {
 # define PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC	0x10 /* Clear the aspect on exec */
 # define PR_PPC_DEXCR_CTRL_MASK		0x1f
 
+#define PR_ADD_SFRAME			74
+#define PR_REMOVE_SFRAME		75
+
 #endif /* _LINUX_PRCTL_H */
kernel/fork.c +10 -0
index c056ea95fe8c..60f14fbab956 100644
@@ -105,6 +105,7 @@
 #include <linux/rseq.h>
 #include <uapi/linux/pidfd.h>
 #include <linux/pidfs.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);
 }
@@ -1251,6 +1253,13 @@ static void mm_init_uprobes_state(struct mm_struct *mm)
 #endif
 }
 
+static void mm_init_sframe(struct mm_struct *mm)
+{
+#ifdef CONFIG_HAVE_UNWIND_USER_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)
 {
@@ -1282,6 +1291,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 4da31f28fda8..7d05a67727db 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>
 
@@ -2784,6 +2785,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_RISCV_SET_ICACHE_FLUSH_CTX:
 		error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
 		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 +2 -1
index 349ce3677526..f70380d7a6a6 100644
@@ -1 +1,2 @@
- obj-$(CONFIG_UNWIND_USER) += user.o
+ obj-$(CONFIG_UNWIND_USER)		+= user.o
+ obj-$(CONFIG_HAVE_UNWIND_USER_SFRAME)	+= sframe.o
kernel/unwind/sframe.c new file +380 -0

380 lines not on this page · raw message

kernel/unwind/sframe.h new file +215 -0

215 lines not on this page · raw message

kernel/unwind/user.c +22 -2
index 54b989810a0e..8e47c80e3e54 100644
@@ -8,12 +8,17 @@
 #include <linux/sched.h>
 #include <linux/sched/task_stack.h>
 #include <linux/unwind_user.h>
+#include <linux/sframe.h>
 #include <linux/uaccess.h>
-#include <asm/unwind_user.h>
 
+#ifdef CONFIG_HAVE_UNWIND_USER_FP
+#include <asm/unwind_user.h>
 static struct unwind_user_frame fp_frame = {
 	ARCH_INIT_USER_FP_FRAME
 };
+#else
+static struct unwind_user_frame fp_frame;
+#endif
 
 int unwind_user_next(struct unwind_user_state *state)
 {
@@ -30,6 +35,16 @@ int unwind_user_next(struct unwind_user_state *state)
 	case UNWIND_USER_TYPE_FP:
 		frame = &fp_frame;
 		break;
+	case UNWIND_USER_TYPE_SFRAME:
+		if (sframe_find(state->ip, frame)) {
+			if (!IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))
+				goto the_end;
+
+			frame = &fp_frame;
+		}
+		break;
+	case UNWIND_USER_TYPE_NONE:
+		goto the_end;
 	default:
 		BUG();
 	}
@@ -68,7 +83,12 @@ int unwind_user_start(struct unwind_user_state *state)
 		return -EINVAL;
 	}
 
-	state->type = UNWIND_USER_TYPE_FP;
+	if (current_has_sframe())
+		state->type = UNWIND_USER_TYPE_SFRAME;
+	else if (IS_ENABLED(CONFIG_UNWIND_USER_FP))
+		state->type = UNWIND_USER_TYPE_FP;
+	else
+		state->type = UNWIND_USER_TYPE_NONE;
 
 	state->sp = user_stack_pointer(regs);
 	state->ip = instruction_pointer(regs);
mm/init-mm.c +6 -0
index 24c809379274..8eb1b122b7bf 100644
@@ -11,12 +11,17 @@
 #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
 #define INIT_MM_CONTEXT(name)
 #endif
 
+#ifndef INIT_MM_SFRAME
+#define INIT_MM_SFRAME
+#endif
+
 const struct vm_operations_struct vma_dummy_vm_ops;
 
 /*
@@ -45,6 +50,7 @@ struct mm_struct init_mm = {
 	.user_ns	= &init_user_ns,
 	.cpu_bitmap	= CPU_BITS_NONE,
 	INIT_MM_CONTEXT(init_mm)
+	INIT_MM_SFRAME
 };
 
 void setup_initial_init_mm(void *start_code, void *end_code,
Signature · 1 line
2.47.0
maintainer
#21 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 41 lines · Josh Poimboeuf
kernel/unwind/sframe.c new file +41 -0
+static int __sframe_add_section(unsigned long sframe_addr,
+				unsigned long text_start,
+				unsigned long text_end)
+{
+	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 __user *)sframe_addr, sizeof(shdr)))
+		return -EFAULT;
+
+	if (shdr.preamble.magic != SFRAME_MAGIC ||
+	    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) {
+		return -EINVAL;
+	}
+
+	sec = kmalloc(sizeof(*sec), GFP_KERNEL);
+	if (!sec)
+		return -ENOMEM;
+
+	header_end = sframe_addr + SFRAME_HDR_SIZE(shdr);
+
+	sec->sframe_addr	= sframe_addr;
+	sec->text_addr		= text_start;
+	sec->fdes_addr		= header_end + shdr.fdes_off;
+	sec->fres_addr		= header_end + shdr.fres_off;
+	sec->fdes_nr		= shdr.num_fdes;
+	sec->ra_off		= shdr.cfa_fixed_ra_offset;
+	sec->fp_off		= shdr.cfa_fixed_fp_offset;
+
+	ret = mtree_insert_range(sframe_mt, text_start, text_end, sec, GFP_KERNEL);
+	if (ret) {
+		kfree(sec);
+		return ret;
+	}
+

kernel/unwind/sframe.c line 286

Quoted 10 lines
kernel/unwind/sframe.c new file +10 -0
+	return 0;
+}
+
+int sframe_add_section(unsigned long sframe_addr, unsigned long text_start,
+		       unsigned long text_end)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *sframe_vma;
+
+	mmap_read_lock(mm);

DEFINE_GUARD(mmap_read_lock, struct mm_struct *,

	     mmap_read_lock(_T), mmap_read_unlock(_T))

in include/linux/mmap_lock.h ?

Quoted 79 lines
kernel/unwind/sframe.c new file +60 -0
+
+	sframe_vma = vma_lookup(mm, sframe_addr);
+	if (!sframe_vma)
+		goto err_unlock;
+
+	if (text_start && text_end) {
+		struct vm_area_struct *text_vma;
+
+		text_vma = vma_lookup(mm, text_start);
+		if (!(text_vma->vm_flags & VM_EXEC))
+			goto err_unlock;
+
+		if (PAGE_ALIGN(text_end) != text_vma->vm_end)
+			goto err_unlock;
+	} else {
+		struct vm_area_struct *vma, *text_vma = NULL;
+		VMA_ITERATOR(vmi, mm, 0);
+
+		for_each_vma(vmi, vma) {
+			if (vma->vm_file != sframe_vma->vm_file ||
+			    !(vma->vm_flags & VM_EXEC))
+				continue;
+
+			if (text_vma) {
+				pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+					     current->comm, current->pid);
+				goto err_unlock;
+			}
+
+			text_vma = vma;
+		}
+
+		if (!text_vma)
+			goto err_unlock;
+
+		text_start = text_vma->vm_start;
+		text_end   = text_vma->vm_end;
+	}
+
+	mmap_read_unlock(mm);
+
+	return __sframe_add_section(sframe_addr, text_start, text_end);
+
+err_unlock:
+	mmap_read_unlock(mm);
+	return -EINVAL;
+}
 
+int sframe_remove_section(unsigned long sframe_addr)
+{
+	struct mm_struct *mm = current->mm;
+	struct sframe_section *sec;
+	unsigned long index = 0;
+
+	mt_for_each(&mm->sframe_mt, sec, index, ULONG_MAX) {
+		if (sec->sframe_addr == sframe_addr)
+			return __sframe_remove_section(mm, sec);
+	}
+
+	return -EINVAL;
+}
kernel/sys.c +4 -0
@@ -64,6 +64,7 @@
 #include <linux/rcupdate.h>
 #include <linux/uidgid.h>
 #include <linux/cred.h>
+#include <linux/sframe.h>
 
 #include <linux/nospec.h>
 
@@ -2784,6 +2785,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_RISCV_SET_ICACHE_FLUSH_CTX:
 		error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
 		break;
+	case PR_ADD_SFRAME:
+		if (arg5)
+			return -EINVAL;

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2800

Quoted 10 lines
kernel/sys.c +7 -0
+		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;

So I realize that mtree has an internal lock, but are we sure we don't want a lock around those prctl()s?

author maintainer
#22 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 9 lines · Peter Zijlstra

kernel/unwind/sframe.c line 286

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+int sframe_add_section(unsigned long sframe_addr, unsigned long text_start,
+		       unsigned long text_end)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *sframe_vma;
+
+	mmap_read_lock(mm);

DEFINE_GUARD(mmap_read_lock, struct mm_struct *,

Quoted 3 lines
	     mmap_read_lock(_T), mmap_read_unlock(_T))

in include/linux/mmap_lock.h ?

Will do.

Quoted 12 lines
Quoted 12 lines
kernel/sys.c +8 -0
@@ -2784,6 +2785,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_RISCV_SET_ICACHE_FLUSH_CTX:
 		error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
 		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;
Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2800

Quoted 5 lines
kernel/sys.c +2 -0
+		error = sframe_remove_section(arg2);
+		break;
 	default:
 		error = -EINVAL;
 		break;

So I realize that mtree has an internal lock, but are we sure we don't want a lock around those prctl()s?

Not that I can tell? It relies on the mtree internal locking for atomicity.

For sframe_remove_section() it uses srcu to delay the freeing until all sframe_find()'s have completed.

Signature · 1 line
Josh
maintainer
#23 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 37 lines · Josh Poimboeuf
Quoted 9 lines · Peter Zijlstra

kernel/unwind/sframe.c line 286

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+int sframe_add_section(unsigned long sframe_addr, unsigned long text_start,
+		       unsigned long text_end)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *sframe_vma;
+
+	mmap_read_lock(mm);

DEFINE_GUARD(mmap_read_lock, struct mm_struct *,

Quoted 3 lines
	     mmap_read_lock(_T), mmap_read_unlock(_T))

in include/linux/mmap_lock.h ?

Will do.

Quoted 12 lines
Quoted 12 lines
kernel/sys.c +8 -0
@@ -2784,6 +2785,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_RISCV_SET_ICACHE_FLUSH_CTX:
 		error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
 		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;
Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2800

Quoted 5 lines
kernel/sys.c +2 -0
+		error = sframe_remove_section(arg2);
+		break;
 	default:
 		error = -EINVAL;
 		break;

So I realize that mtree has an internal lock, but are we sure we don't want a lock around those prctl()s?

Quoted 5 lines

Not that I can tell? It relies on the mtree internal locking for atomicity.

For sframe_remove_section() it uses srcu to delay the freeing until all sframe_find()'s have completed.

Yeah it does all that. But I was sorta looking at all that kmalloc and copy from user stuff, but I suppose you can race that without problem.

#24 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf
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 the complexity it would add to the kernel.

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.41 for sframe v2. Sframe is a simpler version of .eh_frame. It 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

Glibc support is needed to implement the prctl() calls to tell the kernel where the .sframe segments are.

---
 arch/Kconfig                |   4 +
 arch/x86/include/asm/mmu.h  |   2 +-
 fs/binfmt_elf.c             |  35 +++-
 include/linux/mm_types.h    |   3 +
 include/linux/sframe.h      |  41 ++++
 include/linux/unwind_user.h |   2 +
 include/uapi/linux/elf.h    |   1 +
 include/uapi/linux/prctl.h  |   3 +
 kernel/fork.c               |  10 +
 kernel/sys.c                |  11 ++
 kernel/unwind/Makefile      |   3 +-
Quoted 9 lines
 kernel/unwind/sframe.c      | 380 ++++++++++++++++++++++++++++++++++++
 kernel/unwind/sframe.h      | 215 ++++++++++++++++++++
 kernel/unwind/user.c        |  24 ++-
 mm/init-mm.c                |   6 +
 15 files changed, 732 insertions(+), 8 deletions(-)
 create mode 100644 include/linux/sframe.h
 create mode 100644 kernel/unwind/sframe.c
 create mode 100644 kernel/unwind/sframe.h

It feels like this patch is trying to do too much. There is both new UAPI introduction, and SFrame format definition, and unwinder integration, etc, etc. Do you think it can be split further into more focused smaller patches?

Quoted 13 lines
fs/binfmt_elf.c +1 -1
@@ -688,7 +692,7 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
                        /*
                         * Check to see if the section's size will overflow the
                         * allowed task size. Note that p_filesz must always be
-                        * <= p_memsize so it's only necessary to check p_memsz.
+                        * <= p_memsz so it's only necessary to check p_memsz.
                         */
                        k = load_addr + eppnt->p_vaddr;
                        if (BAD_ADDR(k) ||
@@ -698,9 +702,24 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
                                error = -ENOMEM;
                                goto out;
                        }

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 714

Quoted 10 lines
fs/binfmt_elf.c +10 -0
+
+                       if ((eppnt->p_flags & PF_X) && k < start_code)
+                               start_code = k;
+
+                       if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
+                               end_code = k + eppnt->p_filesz;
+                       break;
+               }
+               case PT_GNU_SFRAME:
+                       sframe_phdr = eppnt;

if I understand correctly, there has to be only one sframe, is that right? Should we validate that?

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 722

Quoted 8 lines
fs/binfmt_elf.c +5 -0
+                       break;
                }
        }
 
+       if (sframe_phdr)
+               sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

no error checking?

Quoted 24 lines
fs/binfmt_elf.c +5 -1
        error = load_addr;
 out:
        return error;
@@ -823,7 +842,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
        int first_pt_load = 1;
        unsigned long error;
        struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-       struct elf_phdr *elf_property_phdata = NULL;
+       struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
        unsigned long elf_brk;
        int retval, i;
        unsigned long elf_entry;
@@ -931,6 +950,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                executable_stack = EXSTACK_DISABLE_X;
                        break;
 
+               case PT_GNU_SFRAME:
+                       sframe_phdr = elf_ppnt;
+                       break;
+
                case PT_LOPROC ... PT_HIPROC:
                        retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
                                                  bprm->file, false,

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 1350

Quoted 7 lines
fs/binfmt_elf.c +4 -0
@@ -1321,6 +1344,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                            task_pid_nr(current), retval);
        }
 
+       if (sframe_phdr)
+               sframe_add_section(load_bias + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

error checking missing?

Quoted 28 lines
fs/binfmt_elf.c +0 -0
        regs = current_pt_regs();
 #ifdef ELF_PLAT_INIT
        /*
include/linux/mm_types.h +3 -0
index 381d22eba088..6e7561c1a5fc 100644
@@ -1052,6 +1052,9 @@ struct mm_struct {
 #endif
                } lru_gen;
 #endif /* CONFIG_LRU_GEN_WALKS_MMU */
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+               struct maple_tree sframe_mt;
+#endif
        } __randomize_layout;
 
        /*
include/linux/sframe.h new file +5 -0
new file mode 100644
index 000000000000..d167e01817c4
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_SFRAME_H
+#define _LINUX_SFRAME_H
+
+#include <linux/mm_types.h>

include/linux/sframe.h line 15

Quoted 10 lines
include/linux/sframe.h new file +10 -0
+
+struct unwind_user_frame;
+
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+
+extern void sframe_free_mm(struct mm_struct *mm);
+
+/* text_start, text_end, file_name are optional */

what file_name? was that an extra argument that got removed?

Quoted 13 lines
include/linux/sframe.h new file +13 -0
+extern int sframe_add_section(unsigned long sframe_addr, unsigned long text_start,
+                             unsigned long text_end);
+
+extern int sframe_remove_section(unsigned long sframe_addr);
+extern int sframe_find(unsigned long ip, struct unwind_user_frame *frame);
+
+static inline bool current_has_sframe(void)
+{
+       struct mm_struct *mm = current->mm;
+
+       return mm && !mtree_empty(&mm->sframe_mt);
+}
+

[...]

Quoted 13 lines
kernel/sys.c +1 -0
index 4da31f28fda8..7d05a67727db 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>
 

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2791

Quoted 7 lines
kernel/sys.c +4 -0
@@ -2784,6 +2785,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
        case PR_RISCV_SET_ICACHE_FLUSH_CTX:
                error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
                break;
+       case PR_ADD_SFRAME:
+               if (arg5)
+                       return -EINVAL;
+               error = sframe_add_section(arg2, arg3, arg4);

wouldn't it be better to make this interface extendable from the get go? Instead of passing 3 arguments with fixed meaning, why not pass a pointer to an extendable binary struct like seems to be the trend nowadays with nicely extensible APIs. See [0] for one such example (specifically, struct procmap_query). Seems more prudent, as we'll most probably will be adding flags, options, extra information, etc)

[0] https://lore.kernel.org/linux-mm/[email protected]/

Quoted 9 lines
kernel/sys.c +6 -0
+               break;
+       case PR_REMOVE_SFRAME:
+               if (arg3 || arg4 || arg5)
+                       return -EINVAL;
+               error = sframe_remove_section(arg2);
+               break;
        default:
                error = -EINVAL;
                break;

[...]

Quoted 11 lines
kernel/sys.c +11 -0
+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;

kernel/sys.c

Quoted 10 lines
kernel/sys.c +10 -0
+       return 1 << off_size;
+}
+
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

and also, does it mean that SFrame doesn't support executables with text bigger than 4GB?

Quoted 45 lines
kernel/sys.c +45 -0
+
+       first = (void __user *)sec->fdes_addr;
+       last = first + sec->fdes_nr;
+       while (first <= last) {
+               struct sframe_fde __user *mid;
+               u32 func_off;
+
+               mid = first + ((last - first) / 2);
+
+               if (get_user(func_off, (s32 __user *)mid))
+                       return -EFAULT;
+
+               if (ip_off >= func_off) {
+                       /* validate sort order */
+                       if (func_off < func_off_low)
+                               return -EINVAL;
+
+                       func_off_low = func_off;
+
+                       found = mid;
+                       first = mid + 1;
+               } else {
+                       /* validate sort order */
+                       if (func_off > func_off_high)
+                               return -EINVAL;
+
+                       func_off_high = func_off;
+
+                       last = mid - 1;
+               }
+       }
+
+       if (!found)
+               return -EINVAL;
+
+       if (copy_from_user(fde, found, sizeof(*fde)))
+               return -EFAULT;
+
+       /* check for gaps */
+       if (ip_off < fde->start_addr || ip_off >= fde->start_addr + fde->size)
+               return -EINVAL;
+
+       return 0;
+}
+

[...]

Quoted 23 lines
kernel/sys.c +23 -0
+int sframe_add_section(unsigned long sframe_addr, unsigned long text_start,
+                      unsigned long text_end)
+{
+       struct mm_struct *mm = current->mm;
+       struct vm_area_struct *sframe_vma;
+
+       mmap_read_lock(mm);
+
+       sframe_vma = vma_lookup(mm, sframe_addr);
+       if (!sframe_vma)
+               goto err_unlock;
+
+       if (text_start && text_end) {
+               struct vm_area_struct *text_vma;
+
+               text_vma = vma_lookup(mm, text_start);
+               if (!(text_vma->vm_flags & VM_EXEC))
+                       goto err_unlock;
+
+               if (PAGE_ALIGN(text_end) != text_vma->vm_end)
+                       goto err_unlock;
+       } else {
+               struct vm_area_struct *vma, *text_vma = NULL;

kernel/sys.c

Quoted 10 lines
kernel/sys.c +10 -0
+               VMA_ITERATOR(vmi, mm, 0);
+
+               for_each_vma(vmi, vma) {
+                       if (vma->vm_file != sframe_vma->vm_file ||
+                           !(vma->vm_flags & VM_EXEC))
+                               continue;
+
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

Quoted 17 lines
kernel/sys.c +17 -0
+                               goto err_unlock;
+                       }
+
+                       text_vma = vma;
+               }
+
+               if (!text_vma)
+                       goto err_unlock;
+
+               text_start = text_vma->vm_start;
+               text_end   = text_vma->vm_end;
+       }
+
+       mmap_read_unlock(mm);
+
+       return __sframe_add_section(sframe_addr, text_start, text_end);
+

[...]

author maintainer
#25 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 4 lines · Andrii Nakryiko

It feels like this patch is trying to do too much. There is both new UAPI introduction, and SFrame format definition, and unwinder integration, etc, etc. Do you think it can be split further into more focused smaller patches?

True, let me see if I can split it up.

Quoted 5 lines
Quoted 5 lines
fs/binfmt_elf.c +5 -0
+
+                       if ((eppnt->p_flags & PF_X) && k < start_code)
+                               start_code = k;
+
+                       if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
Quoted 8 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 714

Quoted 5 lines
fs/binfmt_elf.c +5 -0
+                               end_code = k + eppnt->p_filesz;
+                       break;
+               }
+               case PT_GNU_SFRAME:
+                       sframe_phdr = eppnt;

if I understand correctly, there has to be only one sframe, is that right? Should we validate that?

Yes, there shouldn't be more than one PT_GNU_SFRAME for the executable itself. I can validate that.

Quoted 10 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 722

Quoted 8 lines
fs/binfmt_elf.c +5 -0
+                       break;
                }
        }
 
+       if (sframe_phdr)
+               sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

no error checking?

Good point. I remember discussing this with some people at Cauldon/LPC, I just forgot to do it!

Right now it does all the validation at unwind, which could really slow things down unnecessarily if the sframe isn't valid.

Quoted 9 lines

include/linux/sframe.h line 15

Quoted 7 lines
include/linux/sframe.h new file +7 -0
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+
+extern void sframe_free_mm(struct mm_struct *mm);
+
+/* text_start, text_end, file_name are optional */

what file_name? was that an extra argument that got removed?

Indeed, that was for some old code.

Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2791

Quoted 7 lines
        case PR_RISCV_SET_ICACHE_FLUSH_CTX:
                error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
                break;
kernel/sys.c +4 -0
+       case PR_ADD_SFRAME:
+               if (arg5)
+                       return -EINVAL;
+               error = sframe_add_section(arg2, arg3, arg4);
Quoted 8 lines

wouldn't it be better to make this interface extendable from the get go? Instead of passing 3 arguments with fixed meaning, why not pass a pointer to an extendable binary struct like seems to be the trend nowadays with nicely extensible APIs. See [0] for one such example (specifically, struct procmap_query). Seems more prudent, as we'll most probably will be adding flags, options, extra information, etc)

[0] https://lore.kernel.org/linux-mm/[email protected]/

This ioctl interface was admittedly hacked together. I was hoping somebody would suggest something better :-) I'll take a look.

Quoted 9 lines

kernel/unwind/sframe.c line 78

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

That's baked into sframe v2.

Quoted 2 lines

and also, does it mean that SFrame doesn't support executables with text bigger than 4GB?

Yes, but is that a realistic concern?

Quoted 7 lines
Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+       } else {
+               struct vm_area_struct *vma, *text_vma = NULL;
+               VMA_ITERATOR(vmi, mm, 0);
+
+               for_each_vma(vmi, vma) {
+                       if (vma->vm_file != sframe_vma->vm_file ||
+                           !(vma->vm_flags & VM_EXEC))
Quoted 8 lines

kernel/unwind/sframe.c line 312

Quoted 5 lines
kernel/unwind/sframe.c new file +5 -0
+                               continue;
+
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

It's a simplification I suppose.

Quoted 2 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

Signature · 1 line
Josh
#26 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 85 lines · Josh Poimboeuf
Quoted 4 lines · Andrii Nakryiko

It feels like this patch is trying to do too much. There is both new UAPI introduction, and SFrame format definition, and unwinder integration, etc, etc. Do you think it can be split further into more focused smaller patches?

True, let me see if I can split it up.

Quoted 5 lines
Quoted 5 lines
fs/binfmt_elf.c +5 -0
+
+                       if ((eppnt->p_flags & PF_X) && k < start_code)
+                               start_code = k;
+
+                       if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
Quoted 8 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 714

Quoted 5 lines
fs/binfmt_elf.c +5 -0
+                               end_code = k + eppnt->p_filesz;
+                       break;
+               }
+               case PT_GNU_SFRAME:
+                       sframe_phdr = eppnt;

if I understand correctly, there has to be only one sframe, is that right? Should we validate that?

Yes, there shouldn't be more than one PT_GNU_SFRAME for the executable itself. I can validate that.

Quoted 10 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 722

Quoted 8 lines
fs/binfmt_elf.c +5 -0
+                       break;
                }
        }
 
+       if (sframe_phdr)
+               sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

no error checking?

Good point. I remember discussing this with some people at Cauldon/LPC, I just forgot to do it!

Right now it does all the validation at unwind, which could really slow things down unnecessarily if the sframe isn't valid.

Quoted 9 lines

include/linux/sframe.h line 15

Quoted 7 lines
include/linux/sframe.h new file +7 -0
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+
+extern void sframe_free_mm(struct mm_struct *mm);
+
+/* text_start, text_end, file_name are optional */

what file_name? was that an extra argument that got removed?

Indeed, that was for some old code.

Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2791

Quoted 7 lines
        case PR_RISCV_SET_ICACHE_FLUSH_CTX:
                error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
                break;
kernel/sys.c +4 -0
+       case PR_ADD_SFRAME:
+               if (arg5)
+                       return -EINVAL;
+               error = sframe_add_section(arg2, arg3, arg4);
Quoted 8 lines

wouldn't it be better to make this interface extendable from the get go? Instead of passing 3 arguments with fixed meaning, why not pass a pointer to an extendable binary struct like seems to be the trend nowadays with nicely extensible APIs. See [0] for one such example (specifically, struct procmap_query). Seems more prudent, as we'll most probably will be adding flags, options, extra information, etc)

[0] https://lore.kernel.org/linux-mm/[email protected]/

This ioctl interface was admittedly hacked together. I was hoping somebody would suggest something better :-) I'll take a look.

Quoted 9 lines

kernel/unwind/sframe.c line 78

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

Quoted 1 line

That's baked into sframe v2.

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

Quoted 5 lines
Quoted 3 lines

and also, does it mean that SFrame doesn't support executables with text bigger than 4GB?

Yes, but is that a realistic concern?

See above, yes. You'd be surprised. As somewhat corroborating evidence, there were tons of problems and churn (within at least Meta) with DWARF not supporting more than 2GB sizes, so yes, this is not an abstract problem for sure. Modern production applications can be ridiculously big.

Quoted 17 lines
Quoted 16 lines

kernel/unwind/sframe.c line 312

Quoted 13 lines
kernel/unwind/sframe.c new file +12 -0
+       } else {
+               struct vm_area_struct *vma, *text_vma = NULL;
+               VMA_ITERATOR(vmi, mm, 0);
+
+               for_each_vma(vmi, vma) {
+                       if (vma->vm_file != sframe_vma->vm_file ||
+                           !(vma->vm_flags & VM_EXEC))
+                               continue;
+
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

Quoted 1 line

It's a simplification I suppose.

That's a rather random limitation, IMO... How hard would it be to not make that assumption?

Quoted 5 lines
Quoted 3 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The

I'm pretty sure you can do that with a custom linker script, at the very least. Normally this probably won't happen, but I don't think Linux dictates how many executable VMAs an application can have. And it probably just naturally happens for JIT-ted applications (Java, Go, etc).

Linux kernel itself has two executable segments, for instance (though kernel is special, of course, but still).

Quoted 2 lines

pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

This warn() will be logged across millions of machines in the fleet, triggering alarms, people looking at this, making custom internal patches to disable the known-to-happen warn. Why do we need all this? This is an issue that is trivial to trigger by user process that's not doing anything illegal. Why?

Quoted 3 lines

--
Josh

#27 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 16 lines · Andrii Nakryiko
Quoted 12 lines · Josh Poimboeuf
Quoted 9 lines · Andrii Nakryiko

kernel/unwind/sframe.c line 78

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

Quoted 1 line

That's baked into sframe v2.

Quoted 7 lines

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

FWIW, Google has such large binaries, too.
https://llvm.org/devmtg/2023-10/slides/quicktalks/Eubanks-CompromisesWithLargeX86-64Binaries.pdf

Signature · 2 lines
Thanks,
~Nick Desaulniers
#28 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Andrii Nakryiko
Quoted 88 lines · Andrii Nakryiko
Quoted 85 lines · Josh Poimboeuf
Quoted 4 lines · Andrii Nakryiko

It feels like this patch is trying to do too much. There is both new UAPI introduction, and SFrame format definition, and unwinder integration, etc, etc. Do you think it can be split further into more focused smaller patches?

True, let me see if I can split it up.

Quoted 5 lines
Quoted 5 lines
fs/binfmt_elf.c +5 -0
+
+                       if ((eppnt->p_flags & PF_X) && k < start_code)
+                               start_code = k;
+
+                       if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
Quoted 8 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 714

Quoted 5 lines
fs/binfmt_elf.c +5 -0
+                               end_code = k + eppnt->p_filesz;
+                       break;
+               }
+               case PT_GNU_SFRAME:
+                       sframe_phdr = eppnt;

if I understand correctly, there has to be only one sframe, is that right? Should we validate that?

Yes, there shouldn't be more than one PT_GNU_SFRAME for the executable itself. I can validate that.

Quoted 10 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 722

Quoted 8 lines
fs/binfmt_elf.c +5 -0
+                       break;
                 }
         }
 
+       if (sframe_phdr)
+               sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

no error checking?

Good point. I remember discussing this with some people at Cauldon/LPC, I just forgot to do it!

Right now it does all the validation at unwind, which could really slow things down unnecessarily if the sframe isn't valid.

Quoted 9 lines

include/linux/sframe.h line 15

Quoted 7 lines
include/linux/sframe.h new file +7 -0
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+
+extern void sframe_free_mm(struct mm_struct *mm);
+
+/* text_start, text_end, file_name are optional */

what file_name? was that an extra argument that got removed?

Indeed, that was for some old code.

Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2791

Quoted 7 lines
         case PR_RISCV_SET_ICACHE_FLUSH_CTX:
                 error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
                 break;
kernel/sys.c +4 -0
+       case PR_ADD_SFRAME:
+               if (arg5)
+                       return -EINVAL;
+               error = sframe_add_section(arg2, arg3, arg4);
Quoted 8 lines

wouldn't it be better to make this interface extendable from the get go? Instead of passing 3 arguments with fixed meaning, why not pass a pointer to an extendable binary struct like seems to be the trend nowadays with nicely extensible APIs. See [0] for one such example (specifically, struct procmap_query). Seems more prudent, as we'll most probably will be adding flags, options, extra information, etc)

   [0] https://lore.kernel.org/linux-mm/[email protected]/

This ioctl interface was admittedly hacked together. I was hoping somebody would suggest something better :-) I'll take a look.

Quoted 9 lines

kernel/unwind/sframe.c line 78

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

Quoted 1 line

That's baked into sframe v2.

Quoted 8 lines

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

SFrame V2 does have that limitation. We can try to have 64-bit representation for the 'ip' in the SFrame FDE and conditionalize it somehow (say, with a flag in the header) so as to not bloat the majority of applications.

Quoted 61 lines
Quoted 5 lines
Quoted 3 lines

and also, does it mean that SFrame doesn't support executables with text bigger than 4GB?

Yes, but is that a realistic concern?

See above, yes. You'd be surprised. As somewhat corroborating evidence, there were tons of problems and churn (within at least Meta) with DWARF not supporting more than 2GB sizes, so yes, this is not an abstract problem for sure. Modern production applications can be ridiculously big.

Quoted 17 lines
Quoted 16 lines

kernel/unwind/sframe.c line 312

Quoted 13 lines
kernel/unwind/sframe.c new file +12 -0
+       } else {
+               struct vm_area_struct *vma, *text_vma = NULL;
+               VMA_ITERATOR(vmi, mm, 0);
+
+               for_each_vma(vmi, vma) {
+                       if (vma->vm_file != sframe_vma->vm_file ||
+                           !(vma->vm_flags & VM_EXEC))
+                               continue;
+
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

Quoted 1 line

It's a simplification I suppose.

That's a rather random limitation, IMO... How hard would it be to not make that assumption?

Quoted 5 lines
Quoted 3 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The

I'm pretty sure you can do that with a custom linker script, at the very least. Normally this probably won't happen, but I don't think Linux dictates how many executable VMAs an application can have. And it probably just naturally happens for JIT-ted applications (Java, Go, etc).

Linux kernel itself has two executable segments, for instance (though kernel is special, of course, but still).

Quoted 2 lines

pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

This warn() will be logged across millions of machines in the fleet, triggering alarms, people looking at this, making custom internal patches to disable the known-to-happen warn. Why do we need all this? This is an issue that is trivial to trigger by user process that's not doing anything illegal. Why?

Quoted 3 lines

--
Josh

#29 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 99 lines · Indu Bhagat
Quoted 88 lines · Andrii Nakryiko
Quoted 85 lines · Josh Poimboeuf
Quoted 4 lines · Andrii Nakryiko

It feels like this patch is trying to do too much. There is both new UAPI introduction, and SFrame format definition, and unwinder integration, etc, etc. Do you think it can be split further into more focused smaller patches?

True, let me see if I can split it up.

Quoted 5 lines
Quoted 5 lines
fs/binfmt_elf.c +5 -0
+
+                       if ((eppnt->p_flags & PF_X) && k < start_code)
+                               start_code = k;
+
+                       if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
Quoted 8 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 714

Quoted 5 lines
fs/binfmt_elf.c +5 -0
+                               end_code = k + eppnt->p_filesz;
+                       break;
+               }
+               case PT_GNU_SFRAME:
+                       sframe_phdr = eppnt;

if I understand correctly, there has to be only one sframe, is that right? Should we validate that?

Yes, there shouldn't be more than one PT_GNU_SFRAME for the executable itself. I can validate that.

Quoted 10 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 722

Quoted 8 lines
fs/binfmt_elf.c +5 -0
+                       break;
                 }
         }
 
+       if (sframe_phdr)
+               sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

no error checking?

Good point. I remember discussing this with some people at Cauldon/LPC, I just forgot to do it!

Right now it does all the validation at unwind, which could really slow things down unnecessarily if the sframe isn't valid.

Quoted 9 lines

include/linux/sframe.h line 15

Quoted 7 lines
include/linux/sframe.h new file +7 -0
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+
+extern void sframe_free_mm(struct mm_struct *mm);
+
+/* text_start, text_end, file_name are optional */

what file_name? was that an extra argument that got removed?

Indeed, that was for some old code.

Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2791

Quoted 7 lines
         case PR_RISCV_SET_ICACHE_FLUSH_CTX:
                 error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
                 break;
kernel/sys.c +4 -0
+       case PR_ADD_SFRAME:
+               if (arg5)
+                       return -EINVAL;
+               error = sframe_add_section(arg2, arg3, arg4);
Quoted 8 lines

wouldn't it be better to make this interface extendable from the get go? Instead of passing 3 arguments with fixed meaning, why not pass a pointer to an extendable binary struct like seems to be the trend nowadays with nicely extensible APIs. See [0] for one such example (specifically, struct procmap_query). Seems more prudent, as we'll most probably will be adding flags, options, extra information, etc)

   [0] https://lore.kernel.org/linux-mm/[email protected]/

This ioctl interface was admittedly hacked together. I was hoping somebody would suggest something better :-) I'll take a look.

Quoted 9 lines

kernel/unwind/sframe.c line 78

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

Quoted 1 line

That's baked into sframe v2.

Quoted 8 lines

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

Quoted 4 lines

SFrame V2 does have that limitation. We can try to have 64-bit representation for the 'ip' in the SFrame FDE and conditionalize it somehow (say, with a flag in the header) so as to not bloat the majority of applications.

Hi Indu,

I think that's prudent if we believe that SFrame is the solution here. See my reply to Josh. Real-world already approach 4GB limits, and things are not going to shrink in the years to come. So yeah, probably we need some adjustments to the format to at least allow 64-bit offsets (though trying to stick to 32-bit as much as possible, of course, if they work).

I'm not really familiar with the nuances of the format just yet, so can't really provide anything more useful at this point. What would be the sort of gold reference for Sframe format to familiarize myself thoroughly?

BTW, I wanted to ask. Are there any plans to add SFrame support to Clang as well? It feels like without that there is no future for SFrame as a general-purpose solution for stack traces.

Quoted 63 lines
Quoted 62 lines
Quoted 6 lines
Quoted 4 lines

and also, does it mean that SFrame doesn't support executables with text bigger than 4GB?

Yes, but is that a realistic concern?

See above, yes. You'd be surprised. As somewhat corroborating evidence, there were tons of problems and churn (within at least Meta) with DWARF not supporting more than 2GB sizes, so yes, this is not an abstract problem for sure. Modern production applications can be ridiculously big.

Quoted 17 lines
Quoted 16 lines

kernel/unwind/sframe.c line 312

Quoted 13 lines
kernel/unwind/sframe.c new file +12 -0
+       } else {
+               struct vm_area_struct *vma, *text_vma = NULL;
+               VMA_ITERATOR(vmi, mm, 0);
+
+               for_each_vma(vmi, vma) {
+                       if (vma->vm_file != sframe_vma->vm_file ||
+                           !(vma->vm_flags & VM_EXEC))
+                               continue;
+
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

Quoted 1 line

It's a simplification I suppose.

That's a rather random limitation, IMO... How hard would it be to not make that assumption?

Quoted 5 lines
Quoted 3 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The

I'm pretty sure you can do that with a custom linker script, at the very least. Normally this probably won't happen, but I don't think Linux dictates how many executable VMAs an application can have. And it probably just naturally happens for JIT-ted applications (Java, Go, etc).

Linux kernel itself has two executable segments, for instance (though kernel is special, of course, but still).

Quoted 2 lines

pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

This warn() will be logged across millions of machines in the fleet, triggering alarms, people looking at this, making custom internal patches to disable the known-to-happen warn. Why do we need all this? This is an issue that is trivial to trigger by user process that's not doing anything illegal. Why?

Quoted 3 lines

--
Josh

maintainer
#30 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 3 lines · On Fri, 1 Nov 2024 11:38:47 -0700 Andrii Nakryiko <[email protected]>…

BTW, I wanted to ask. Are there any plans to add SFrame support to Clang as well? It feels like without that there is no future for SFrame as a general-purpose solution for stack traces.

We want to use SFrames inside Google, and having Clang support it is a requirement for that. I'm working on getting people to support it in Clang.

-- Steve

#31 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 11 lines · Steven Rostedt
Quoted 3 lines · On Fri, 1 Nov 2024 11:38:47 -0700 Andrii Nakryiko <[email protected]>…

BTW, I wanted to ask. Are there any plans to add SFrame support to Clang as well? It feels like without that there is no future for SFrame as a general-purpose solution for stack traces.

We want to use SFrames inside Google, and having Clang support it is a requirement for that. I'm working on getting people to support it in Clang.

Nice, good to hear!

Quoted 1 line

-- Steve

#32 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Andrii Nakryiko
Quoted 114 lines · Andrii Nakryiko
Quoted 99 lines · Indu Bhagat
Quoted 88 lines · Andrii Nakryiko
Quoted 85 lines · Josh Poimboeuf
Quoted 4 lines · Andrii Nakryiko

It feels like this patch is trying to do too much. There is both new UAPI introduction, and SFrame format definition, and unwinder integration, etc, etc. Do you think it can be split further into more focused smaller patches?

True, let me see if I can split it up.

Quoted 5 lines
Quoted 5 lines
fs/binfmt_elf.c +5 -0
+
+                       if ((eppnt->p_flags & PF_X) && k < start_code)
+                               start_code = k;
+
+                       if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
Quoted 8 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 714

Quoted 5 lines
fs/binfmt_elf.c +5 -0
+                               end_code = k + eppnt->p_filesz;
+                       break;
+               }
+               case PT_GNU_SFRAME:
+                       sframe_phdr = eppnt;

if I understand correctly, there has to be only one sframe, is that right? Should we validate that?

Yes, there shouldn't be more than one PT_GNU_SFRAME for the executable itself. I can validate that.

Quoted 10 lines

fs/binfmt_elf.c static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, · line 722

Quoted 8 lines
fs/binfmt_elf.c +5 -0
+                       break;
                  }
          }
 
+       if (sframe_phdr)
+               sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+                                  start_code, end_code);
+

no error checking?

Good point. I remember discussing this with some people at Cauldon/LPC, I just forgot to do it!

Right now it does all the validation at unwind, which could really slow things down unnecessarily if the sframe isn't valid.

Quoted 9 lines

include/linux/sframe.h line 15

Quoted 7 lines
include/linux/sframe.h new file +7 -0
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+
+extern void sframe_free_mm(struct mm_struct *mm);
+
+/* text_start, text_end, file_name are optional */

what file_name? was that an extra argument that got removed?

Indeed, that was for some old code.

Quoted 8 lines

kernel/sys.c SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, · line 2791

Quoted 7 lines
          case PR_RISCV_SET_ICACHE_FLUSH_CTX:
                  error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
                  break;
kernel/sys.c +4 -0
+       case PR_ADD_SFRAME:
+               if (arg5)
+                       return -EINVAL;
+               error = sframe_add_section(arg2, arg3, arg4);
Quoted 8 lines

wouldn't it be better to make this interface extendable from the get go? Instead of passing 3 arguments with fixed meaning, why not pass a pointer to an extendable binary struct like seems to be the trend nowadays with nicely extensible APIs. See [0] for one such example (specifically, struct procmap_query). Seems more prudent, as we'll most probably will be adding flags, options, extra information, etc)

    [0] https://lore.kernel.org/linux-mm/[email protected]/

This ioctl interface was admittedly hacked together. I was hoping somebody would suggest something better :-) I'll take a look.

Quoted 9 lines

kernel/unwind/sframe.c line 78

Quoted 7 lines
kernel/unwind/sframe.c new file +7 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+                   struct sframe_fde *fde)
+{
+       struct sframe_fde __user *first, *last, *found = NULL;
+       u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+       ip_off = ip - sec->sframe_addr;

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

Quoted 1 line

That's baked into sframe v2.

Quoted 8 lines

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

Quoted 4 lines

SFrame V2 does have that limitation. We can try to have 64-bit representation for the 'ip' in the SFrame FDE and conditionalize it somehow (say, with a flag in the header) so as to not bloat the majority of applications.

Hi Indu,

I think that's prudent if we believe that SFrame is the solution here. See my reply to Josh. Real-world already approach 4GB limits, and things are not going to shrink in the years to come. So yeah, probably we need some adjustments to the format to at least allow 64-bit offsets (though trying to stick to 32-bit as much as possible, of course, if they work).

Quoted 5 lines

I'm not really familiar with the nuances of the format just yet, so can't really provide anything more useful at this point. What would be the sort of gold reference for Sframe format to familiarize myself thoroughly?

There are some links on the SFrame wiki that can be helpful
https://sourceware.org/binutils/wiki/sframe

Quoted 67 lines

BTW, I wanted to ask. Are there any plans to add SFrame support to Clang as well? It feels like without that there is no future for SFrame as a general-purpose solution for stack traces.

Quoted 63 lines
Quoted 62 lines
Quoted 6 lines
Quoted 4 lines

and also, does it mean that SFrame doesn't support executables with text bigger than 4GB?

Yes, but is that a realistic concern?

See above, yes. You'd be surprised. As somewhat corroborating evidence, there were tons of problems and churn (within at least Meta) with DWARF not supporting more than 2GB sizes, so yes, this is not an abstract problem for sure. Modern production applications can be ridiculously big.

Quoted 17 lines
Quoted 16 lines

kernel/unwind/sframe.c line 312

Quoted 13 lines
kernel/unwind/sframe.c new file +12 -0
+       } else {
+               struct vm_area_struct *vma, *text_vma = NULL;
+               VMA_ITERATOR(vmi, mm, 0);
+
+               for_each_vma(vmi, vma) {
+                       if (vma->vm_file != sframe_vma->vm_file ||
+                           !(vma->vm_flags & VM_EXEC))
+                               continue;
+
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

Quoted 1 line

It's a simplification I suppose.

That's a rather random limitation, IMO... How hard would it be to not make that assumption?

Quoted 5 lines
Quoted 3 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The

I'm pretty sure you can do that with a custom linker script, at the very least. Normally this probably won't happen, but I don't think Linux dictates how many executable VMAs an application can have. And it probably just naturally happens for JIT-ted applications (Java, Go, etc).

Linux kernel itself has two executable segments, for instance (though kernel is special, of course, but still).

Quoted 2 lines

pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

This warn() will be logged across millions of machines in the fleet, triggering alarms, people looking at this, making custom internal patches to disable the known-to-happen warn. Why do we need all this? This is an issue that is trivial to trigger by user process that's not doing anything illegal. Why?

Quoted 3 lines

--
Josh

author maintainer
#33 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Andrii Nakryiko
Quoted 11 lines · Andrii Nakryiko
Quoted 3 lines
Quoted 1 line

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

That's baked into sframe v2.

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

Personally I find the idea of a single 4GB+ text segment pretty surprising as I've never seen anything even close to that.

Anyway it's iterative development and not everybody's requirements are clear from day 1. Which is why we're discussing it now. I think there are already plans to do an sframe v3.

Quoted 11 lines
Quoted 8 lines
Quoted 6 lines

kernel/unwind/sframe.c line 312

Quoted 3 lines
kernel/unwind/sframe.c new file +3 -0
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

It's a simplification I suppose.

That's a rather random limitation, IMO... How hard would it be to not make that assumption?

It's definitely not random, there's no need to complicate the code if this condition doesn't exist.

Quoted 10 lines
Quoted 4 lines
Quoted 2 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The

I'm pretty sure you can do that with a custom linker script, at the very least. Normally this probably won't happen, but I don't think Linux dictates how many executable VMAs an application can have. And it probably just naturally happens for JIT-ted applications (Java, Go, etc).

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

Quoted 8 lines
Quoted 2 lines

pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

This warn() will be logged across millions of machines in the fleet, triggering alarms, people looking at this, making custom internal patches to disable the known-to-happen warn. Why do we need all this? This is an issue that is trivial to trigger by user process that's not doing anything illegal. Why?

There's no point in adding complexity to support some hypothetical. I can remove the printk though.

Signature · 1 line
Josh
#34 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

+cc bpf@ where people care about stack traces and profiling as well (please cc [email protected] for next revisions as well, I'm sure a bunch of folks would appreciate it and have something useful to say)

Quoted 14 lines · Josh Poimboeuf
Quoted 11 lines · Andrii Nakryiko
Quoted 3 lines
Quoted 1 line

what if ip_off is larger than 4GB? ELF section can be bigger than 4GB, right?

That's baked into sframe v2.

I believe we do have large production binaries with more than 4GB of text, what are we going to do about them? It would be interesting to hear sframe people's opinion. Adding such a far-reaching new format in 2024 with these limitations is kind of sad. At the very least maybe we should allow some form of chaining sframe definitions to cover more than 4GB segments? Please CC relevant folks, I'm wondering what they're thinking about this.

Quoted 2 lines

Personally I find the idea of a single 4GB+ text segment pretty surprising as I've never seen anything even close to that.

I grabbed one of Meta production servers running one of the big-ish (I don't know if it's even the largest, most probably not) service. Here are first few sections from /proc/pid/maps belonging to the main binary:

00200000-170ad000 r--p 00000000 07:01 5
172ac000-498e7000 r-xp 16eac000 07:01 5
49ae7000-49b8b000 r--p 494e7000 07:01 5
49d8b000-4a228000 rw-p 4958b000 07:01 5
4a228000-4c677000 rw-p 00000000 00:00 0
4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

Few observations:

1) There are 4 executable segments in just the first 8 entries.
2) Their total size is already approaching 1.5GB:

Quoted 1 line
Quoted 1 line
Quoted 1 line

((0x170ad000 - 0x200000) + (0x5b270000 - 0x4f600000) + (0x498e7000 - 0x172ac000)) / 1024 / 1024

1361.34375

I don't know about you, but from my experience things like code size tend to just grow over time, rarely it shrinks (and even that usually requires tremendous and focused efforts).

Quoted 4 lines

Anyway it's iterative development and not everybody's requirements are clear from day 1. Which is why we're discussing it now. I think there are already plans to do an sframe v3.

Of course, which is why I'm providing this feedback. But it would be nice to avoid having to support a zoo of versions if we already know there are practical limitations that we are not that far from hitting.

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

kernel/unwind/sframe.c line 312

Quoted 4 lines
kernel/unwind/sframe.c new file +3 -0
+                       if (text_vma) {
+                               pr_warn_once("%s[%d]: multiple EXEC segments unsupported\n",
+                                            current->comm, current->pid);

is this just something that fundamentally can't be supported by SFrame format? Or just an implementation simplification?

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

It's a simplification I suppose.

That's a rather random limitation, IMO... How hard would it be to not make that assumption?

It's definitely not random, there's no need to complicate the code if this condition doesn't exist.

Sorry, I'm probably dense and missing something. But from the example process above, isn't this check violated already? Or it's two different things? Not sure, honestly.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
Quoted 3 lines

It's not illegal to have an executable with multiple VM_EXEC segments, no? Should this be a pr_warn_once() then?

I don't know, is it allowed? I've never seen it in practice. The

Quoted 8 lines
Quoted 5 lines

I'm pretty sure you can do that with a custom linker script, at the very least. Normally this probably won't happen, but I don't think Linux dictates how many executable VMAs an application can have. And it probably just naturally happens for JIT-ted applications (Java, Go, etc).

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

See above, very confused by such assumptions, but I'm hoping we are talking about two different things here.

Quoted 4 lines
Quoted 4 lines
Quoted 3 lines

pr_warn_once() is not reporting that it's illegal but rather that this corner case actually exists and maybe needs to be looked at.

Quoted 8 lines
Quoted 5 lines

This warn() will be logged across millions of machines in the fleet, triggering alarms, people looking at this, making custom internal patches to disable the known-to-happen warn. Why do we need all this? This is an issue that is trivial to trigger by user process that's not doing anything illegal. Why?

There's no point in adding complexity to support some hypothetical. I can remove the printk though.

We are talking about fundamental things like format for supporting frame pointer-less stack trace capture. It will take years to adopt SFrame everywhere, so I think it's prudent to think a bit ahead beyond just saying "no real application should need more than 4GB text", IMO.

Quoted 3 lines

--
Josh

author maintainer
#35 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 9 lines · Andrii Nakryiko

00200000-170ad000 r--p 00000000 07:01 5
172ac000-498e7000 r-xp 16eac000 07:01 5
49ae7000-49b8b000 r--p 494e7000 07:01 5
49d8b000-4a228000 rw-p 4958b000 07:01 5
4a228000-4c677000 rw-p 00000000 00:00 0
4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

Quoted 3 lines

Sorry, I'm probably dense and missing something. But from the example process above, isn't this check violated already? Or it's two different things? Not sure, honestly.

It's hard to tell exactly what's going on, did you strip the file names?

The sframe limitation is per file, not per address space. I assume these are one file:

Quoted 1 line

172ac000-498e7000 r-xp 16eac000 07:01 5

and these are another:

Quoted 3 lines

4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

Multiple mappings for a single file is fine, as long as they're contiguous.

Quoted 5 lines
Quoted 2 lines

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

See above, very confused by such assumptions, but I'm hoping we are talking about two different things here.

The "contiguous text" thing seems enforced by the kernel for executables. However it doesn't manage shared libraries, those are mapped by the loader, e.g. /lib64/ld-linux-x86-64.so.2.

At a quick glance I can't tell if /lib64/ld-linux-x86-64.so.2 enforces that.

Quoted 7 lines
Quoted 2 lines

There's no point in adding complexity to support some hypothetical. I can remove the printk though.

We are talking about fundamental things like format for supporting frame pointer-less stack trace capture. It will take years to adopt SFrame everywhere, so I think it's prudent to think a bit ahead beyond just saying "no real application should need more than 4GB text", IMO.

I don't think anybody is saying that...

Signature · 1 line
Josh
#36 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 11 lines · Josh Poimboeuf
Quoted 9 lines · Andrii Nakryiko

00200000-170ad000 r--p 00000000 07:01 5
172ac000-498e7000 r-xp 16eac000 07:01 5
49ae7000-49b8b000 r--p 494e7000 07:01 5
49d8b000-4a228000 rw-p 4958b000 07:01 5
4a228000-4c677000 rw-p 00000000 00:00 0
4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

Quoted 5 lines
Quoted 3 lines

Sorry, I'm probably dense and missing something. But from the example process above, isn't this check violated already? Or it's two different things? Not sure, honestly.

It's hard to tell exactly what's going on, did you strip the file names?

Yes, I did, of course. But as I said, they all belong to the same main binary of the process.

Quoted 6 lines

The sframe limitation is per file, not per address space. I assume these are one file:

Quoted 1 line

172ac000-498e7000 r-xp 16eac000 07:01 5

Quoted 8 lines

and these are another:

Quoted 3 lines

4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

Multiple mappings for a single file is fine, as long as they're contiguous.

No all of what I posted above belongs to the same file (except "4a228000-4c677000 rw-p 00000000 00:00 0" which doesn't have associated file, but I suspect it originally was part of this file, we do some tricks with re-mmap()'ing stuff due to huge pages usage).

Quoted 26 lines
Quoted 6 lines
Quoted 3 lines

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

See above, very confused by such assumptions, but I'm hoping we are talking about two different things here.

The "contiguous text" thing seems enforced by the kernel for executables. However it doesn't manage shared libraries, those are mapped by the loader, e.g. /lib64/ld-linux-x86-64.so.2.

At a quick glance I can't tell if /lib64/ld-linux-x86-64.so.2 enforces that.

Quoted 7 lines
Quoted 2 lines

There's no point in adding complexity to support some hypothetical. I can remove the printk though.

We are talking about fundamental things like format for supporting frame pointer-less stack trace capture. It will take years to adopt SFrame everywhere, so I think it's prudent to think a bit ahead beyond just saying "no real application should need more than 4GB text", IMO.

I don't think anybody is saying that...

--
Josh

#37 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 4 lines · Andrii Nakryiko
Quoted 2 lines · Josh Poimboeuf

On Fri, Nov 01, 2024 at 11:34:48AM -0700, Andrii Nakryiko wrote:

Quoted 9 lines
Quoted 9 lines
Quoted 9 lines

00200000-170ad000 r--p 00000000 07:01 5
172ac000-498e7000 r-xp 16eac000 07:01 5
49ae7000-49b8b000 r--p 494e7000 07:01 5
49d8b000-4a228000 rw-p 4958b000 07:01 5
4a228000-4c677000 rw-p 00000000 00:00 0
4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

I should have maybe posted this in this form:

00200000-170ad000 r--p 00000000 07:01 5  /packages/obfuscated_file
172ac000-498e7000 r-xp 16eac000 07:01 5  /packages/obfuscated_file
49ae7000-49b8b000 r--p 494e7000 07:01 5  /packages/obfuscated_file
49d8b000-4a228000 rw-p 4958b000 07:01 5  /packages/obfuscated_file
4a228000-4c677000 rw-p 00000000 00:00 0
4c800000-4ca00000 r-xp 49c00000 07:01 5  /packages/obfuscated_file
4ca00000-4f600000 r-xp 49e00000 07:01 5  /packages/obfuscated_file
4f600000-5b270000 r-xp 4ca00000 07:01 5  /packages/obfuscated_file

Those paths are pointing to the same binary.

Quoted 55 lines
Quoted 5 lines
Quoted 3 lines

Sorry, I'm probably dense and missing something. But from the example process above, isn't this check violated already? Or it's two different things? Not sure, honestly.

It's hard to tell exactly what's going on, did you strip the file names?

Yes, I did, of course. But as I said, they all belong to the same main binary of the process.

Quoted 6 lines

The sframe limitation is per file, not per address space. I assume these are one file:

Quoted 1 line

172ac000-498e7000 r-xp 16eac000 07:01 5

Quoted 8 lines

and these are another:

Quoted 3 lines

4c800000-4ca00000 r-xp 49c00000 07:01 5
4ca00000-4f600000 r-xp 49e00000 07:01 5
4f600000-5b270000 r-xp 4ca00000 07:01 5

Multiple mappings for a single file is fine, as long as they're contiguous.

No all of what I posted above belongs to the same file (except "4a228000-4c677000 rw-p 00000000 00:00 0" which doesn't have associated file, but I suspect it originally was part of this file, we do some tricks with re-mmap()'ing stuff due to huge pages usage).

Quoted 26 lines
Quoted 6 lines
Quoted 3 lines

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

See above, very confused by such assumptions, but I'm hoping we are talking about two different things here.

The "contiguous text" thing seems enforced by the kernel for executables. However it doesn't manage shared libraries, those are mapped by the loader, e.g. /lib64/ld-linux-x86-64.so.2.

At a quick glance I can't tell if /lib64/ld-linux-x86-64.so.2 enforces that.

Quoted 7 lines
Quoted 2 lines

There's no point in adding complexity to support some hypothetical. I can remove the printk though.

We are talking about fundamental things like format for supporting frame pointer-less stack trace capture. It will take years to adopt SFrame everywhere, so I think it's prudent to think a bit ahead beyond just saying "no real application should need more than 4GB text", IMO.

I don't think anybody is saying that...

--
Josh

author maintainer
#38 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 27 lines · Andrii Nakryiko
Quoted 4 lines · Andrii Nakryiko
Quoted 2 lines · Josh Poimboeuf

On Fri, Nov 01, 2024 at 11:34:48AM -0700, Andrii Nakryiko wrote:

Quoted 9 lines
Quoted 9 lines
Quoted 9 lines

00200000-170ad000 r--p 00000000 07:01 5 172ac000-498e7000 r-xp 16eac000 07:01 5 49ae7000-49b8b000 r--p 494e7000 07:01 5 49d8b000-4a228000 rw-p 4958b000 07:01 5 4a228000-4c677000 rw-p 00000000 00:00 0 4c800000-4ca00000 r-xp 49c00000 07:01 5 4ca00000-4f600000 r-xp 49e00000 07:01 5 4f600000-5b270000 r-xp 4ca00000 07:01 5

I should have maybe posted this in this form:

00200000-170ad000 r--p 00000000 07:01 5  /packages/obfuscated_file
172ac000-498e7000 r-xp 16eac000 07:01 5  /packages/obfuscated_file
49ae7000-49b8b000 r--p 494e7000 07:01 5  /packages/obfuscated_file
49d8b000-4a228000 rw-p 4958b000 07:01 5  /packages/obfuscated_file
4a228000-4c677000 rw-p 00000000 00:00 0
4c800000-4ca00000 r-xp 49c00000 07:01 5  /packages/obfuscated_file
4ca00000-4f600000 r-xp 49e00000 07:01 5  /packages/obfuscated_file
4f600000-5b270000 r-xp 4ca00000 07:01 5  /packages/obfuscated_file
Quoted 1 line

Those paths are pointing to the same binary.

Ok, thanks for sharing that. I'll add in support for noncontiguous text.

Signature · 1 line
Josh
#39 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf
Quoted 2 lines · Josh Poimboeuf

Personally I find the idea of a single 4GB+ text segment pretty surprising as I've never seen anything even close to that.

It is pretty common I'm afraid.

Quoted 2 lines

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

Huh? What makes you think that? There can be many executable PT_LOAD segments in each and every binary.

Segher

author maintainer
#40 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 6 lines · Segher Boessenkool
Quoted 2 lines · Josh Poimboeuf

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

Huh? What makes you think that? There can be many executable PT_LOAD segments in each and every binary.

Right, but for executables (not shared libraries) the kernel seems to assume they're contiguous? See the 'start_code' and 'end_code' variables in load_elf_binary() load_elf_interp().

Signature · 1 line
Josh
author maintainer
#41 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 11 lines · Josh Poimboeuf
Quoted 6 lines · Segher Boessenkool
Quoted 2 lines · Josh Poimboeuf

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

Huh? What makes you think that? There can be many executable PT_LOAD segments in each and every binary.

Right, but for executables (not shared libraries) the kernel seems to assume they're contiguous? See the 'start_code' and 'end_code' variables in load_elf_binary() load_elf_interp().

Typo, see load_elf_binary (not load_elf_interp).

Signature · 1 line
Josh
author maintainer
#42 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 6 lines · Josh Poimboeuf
Quoted 5 lines · Josh Poimboeuf
Quoted 4 lines · Segher Boessenkool
Quoted 2 lines · Josh Poimboeuf

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

Huh? What makes you think that? There can be many executable PT_LOAD segments in each and every binary.

Right, but for executables (not shared libraries) the kernel seems to assume they're contiguous? See the 'start_code' and 'end_code' variables in load_elf_binary() load_elf_interp().

Typo, see load_elf_binary (not load_elf_interp).

Hm, actually AFAICT that's only for reporting things in sysfs/proc. So maybe it's assumed but not really "enforced".

Signature · 1 line
Josh
#43 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Hi!

Quoted 10 lines · Josh Poimboeuf
Quoted 9 lines · Josh Poimboeuf
Quoted 8 lines · Josh Poimboeuf
Quoted 6 lines · Segher Boessenkool
Quoted 2 lines · Josh Poimboeuf

Actually I just double checked and even the kernel's ELF loader assumes that each executable has only a single text start+end address pair.

Huh? What makes you think that? There can be many executable PT_LOAD segments in each and every binary.

Quoted 8 lines
Quoted 5 lines
Quoted 3 lines

Right, but for executables (not shared libraries) the kernel seems to assume they're contiguous? See the 'start_code' and 'end_code' variables in load_elf_binary() load_elf_interp().

Typo, see load_elf_binary (not load_elf_interp).

Hm, actually AFAICT that's only for reporting things in sysfs/proc. So maybe it's assumed but not really "enforced".

Yes, this is copied to mm->start_code (etc.) This isn't used for anything very important it seems, it seems to be a leftover from when we only had really simple binfmts? For a.out it did actually make sense, for example :-)

Segher

maintainer
#44 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf
Quoted 81 lines · On Mon, 28 Oct 2024 14:47:56 -0700 Josh Poimboeuf <[email protected]> wrote:
fs/binfmt_elf.c +23 -4
index 06dc4a57ba78..434c548f0837 100644
@@ -47,6 +47,7 @@
 #include <linux/dax.h>
 #include <linux/uaccess.h>
 #include <linux/rseq.h>
+#include <linux/sframe.h>
 #include <asm/param.h>
 #include <asm/page.h>
 
@@ -633,11 +634,13 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 		unsigned long no_base, struct elf_phdr *interp_elf_phdata,
 		struct arch_elf_state *arch_state)
 {
-	struct elf_phdr *eppnt;
+	struct elf_phdr *eppnt, *sframe_phdr = NULL;
 	unsigned long load_addr = 0;
 	int load_addr_set = 0;
 	unsigned long error = ~0UL;
 	unsigned long total_size;
+	unsigned long start_code = ~0UL;
+	unsigned long end_code = 0;
 	int i;
 
 	/* First of all, some simple consistency checks */
@@ -659,7 +662,8 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 
 	eppnt = interp_elf_phdata;
 	for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
-		if (eppnt->p_type == PT_LOAD) {
+		switch (eppnt->p_type) {
+		case PT_LOAD: {
 			int elf_type = MAP_PRIVATE;
 			int elf_prot = make_prot(eppnt->p_flags, arch_state,
 						 true, true);
@@ -688,7 +692,7 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 			/*
 			 * Check to see if the section's size will overflow the
 			 * allowed task size. Note that p_filesz must always be
-			 * <= p_memsize so it's only necessary to check p_memsz.
+			 * <= p_memsz so it's only necessary to check p_memsz.
 			 */
 			k = load_addr + eppnt->p_vaddr;
 			if (BAD_ADDR(k) ||
@@ -698,9 +702,24 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 				error = -ENOMEM;
 				goto out;
 			}
+
+			if ((eppnt->p_flags & PF_X) && k < start_code)
+				start_code = k;
+
+			if ((eppnt->p_flags & PF_X) && k + eppnt->p_filesz > end_code)
+				end_code = k + eppnt->p_filesz;
+			break;
+		}
+		case PT_GNU_SFRAME:
+			sframe_phdr = eppnt;
+			break;
 		}
 	}
 
+	if (sframe_phdr)
+		sframe_add_section(load_addr + sframe_phdr->p_vaddr,
+				   start_code, end_code);
+
 	error = load_addr;
 out:
 	return error;
@@ -823,7 +842,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	int first_pt_load = 1;
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-	struct elf_phdr *elf_property_phdata = NULL;
+	struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
 	unsigned long elf_brk;
 	int retval, i;
 	unsigned long elf_entry;

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 954

Quoted 5 lines
fs/binfmt_elf.c +2 -0
@@ -931,6 +950,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
 				executable_stack = EXSTACK_DISABLE_X;
 			break;
 
+		case PT_GNU_SFRAME:
+			sframe_phdr = elf_ppnt;

You need to save the p_vaddr here and not the pointer.

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 1345

Quoted 8 lines
fs/binfmt_elf.c +2 -0
+			break;
+
 		case PT_LOPROC ... PT_HIPROC:
 			retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
 						  bprm->file, false,
@@ -1321,6 +1344,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
 					    task_pid_nr(current), retval);
 	}

Before this code we have:

	kfree(elf_phdata);

And I added:

	if (sframe_phdr)
		trace_printk("after sframe vaddr=%x\n", sframe_phdr->p_vaddr);
	kfree(elf_phdata);
	if (sframe_phdr)
		trace_printk("after sframe vaddr=%x\n", sframe_phdr->p_vaddr);

Which produced:

         scan-fs-940   [007] .....    16.091081: bprint:               load_elf_binary: after sframe vaddr=2298
         scan-fs-940   [007] .....    16.091083: bprint:               load_elf_binary: after sframe vaddr=0

I was wondering why it wasn't working.

-- Steve

Quoted 22 lines
fs/binfmt_elf.c +4 -0
+	if (sframe_phdr)
+		sframe_add_section(load_bias + sframe_phdr->p_vaddr,
+				   start_code, end_code);
+
 	regs = current_pt_regs();
 #ifdef ELF_PLAT_INIT
 	/*
include/linux/mm_types.h +3 -0
index 381d22eba088..6e7561c1a5fc 100644
@@ -1052,6 +1052,9 @@ struct mm_struct {
 #endif
 		} lru_gen;
 #endif /* CONFIG_LRU_GEN_WALKS_MMU */
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+		struct maple_tree sframe_mt;
+#endif
 	} __randomize_layout;
 
 	/*
maintainer
#45 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 2 lines · On Tue, 5 Nov 2024 12:40:53 -0500 Steven Rostedt <[email protected]> wrote:

On Mon, 28 Oct 2024 14:47:56 -0700
Josh Poimboeuf <[email protected]> wrote:

<[email protected]>: Host or domain name not found. Name
service error for name=vger.kerne.org type=AAAA: Host not found

Hmm, no wonder this didn't show up in patchwork :-/

Please fix on your next version.

Thanks,

-- Steve

#46 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf

On 28.10.2024 22:47, Josh Poimboeuf wrote:
...

Quoted 1 line
kernel/unwind/sframe.c +0 -0

...

Quoted 39 lines
kernel/unwind/sframe.c +39 -0
+#define __SFRAME_GET_USER(out, user_ptr, type)				\
+({									\
+	type __tmp;							\
+	if (get_user(__tmp, (type __user *)user_ptr))			\
+		return -EFAULT;						\
+	user_ptr += sizeof(__tmp);					\
+	out = __tmp;							\
+})
+
+#define SFRAME_GET_USER(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;
+}

...

Quoted 48 lines
kernel/unwind/sframe.c +48 -0
+static int find_fre(struct sframe_section *sec, struct sframe_fde *fde,
+		    unsigned long ip, struct unwind_user_frame *frame)
+{
+	unsigned char fde_type = SFRAME_FUNC_FDE_TYPE(fde->info);
+	unsigned char fre_type = SFRAME_FUNC_FRE_TYPE(fde->info);
+	unsigned char offset_count, offset_size;
+	s32 cfa_off, ra_off, fp_off, ip_off;
+	void __user *f, *last_f = NULL;
+	unsigned char addr_size;
+	u32 last_fre_ip_off = 0;
+	u8 fre_info = 0;
+	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 __user *)sec->fres_addr + fde->fres_off;
+
+	for (i = 0; i < fde->fres_num; i++) {
+		u32 fre_ip_off;
+
+		SFRAME_GET_USER(fre_ip_off, f, addr_size);
+
+		if (fre_ip_off < last_fre_ip_off)
+			return -EINVAL;
+
+		last_fre_ip_off = fre_ip_off;
+
+		if (fde_type == SFRAME_FDE_TYPE_PCINC) {
+			if (ip_off < fre_ip_off)
+				break;
+		} else {
+			/* SFRAME_FDE_TYPE_PCMASK */
+			if (ip_off % fde->rep_size < fre_ip_off)
+				break;
+		}
+
+		SFRAME_GET_USER(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;
+

kernel/unwind/sframe.c line 181

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+		last_f = f;
+		f += offset_count * offset_size;
+	}
+
+	if (!last_f)
+		return -EINVAL;
+
+	f = last_f;
+
+	SFRAME_GET_USER(cfa_off, f, offset_size);

SFRAME_GET_USER() does not work for the signed SFrame CFA offset.

kernel/unwind/sframe.c line 189

Quoted 8 lines
kernel/unwind/sframe.c +8 -0
+	offset_count--;
+
+	ra_off = sec->ra_off;
+	if (!ra_off) {
+		if (!offset_count--)
+			return -EINVAL;
+
+		SFRAME_GET_USER(ra_off, f, offset_size);

Likewise for the signed SFrame RA offset.

Excerpt from my added trace. Note the RA_off=65488 (unsigned) = 0xFFD0 = -48 (signed):

unwind_user_next: WARNING: RA could not be obtained from user space (IP=0x000003ffbb5f4218, CFA=0x000003ffc22f8f10, RA_off=65488)

Excerpt from perf script:

3ffbb5f4218 internal_fnwmatch+0x558 (/usr/lib64/libc.so.6)

Excerpts from objdump -wt --sframe:

00000000000f3cc0 l F .text 000000000000195c internal_fnwmatch

func idx [1715]: pc = 0xf3cc0, size = 6492 bytes
STARTPC CFA FP RA INFO

00000000000f3cc0  sp+160    u         u            (1*1B)
00000000000f3cc6  sp+160    c-72      c-48         (3*1B)
00000000000f3cd0  sp+4256   c-72      c-48         (3*2B)
00000000000f3cdc  sp+8352   c-72      c-48         (3*2B)
00000000000f3ce8  sp+10792  c-72      c-48         (3*2B)
00000000000f3f7e  sp+160    u         u            (1*1B)
00000000000f3f80  sp+10792  c-72      c-48         (3*2B)

kernel/unwind/sframe.c line 195

Quoted 6 lines
kernel/unwind/sframe.c +6 -0
+	}
+
+	fp_off = sec->fp_off;
+	if (!fp_off && offset_count) {
+		offset_count--;
+		SFRAME_GET_USER(fp_off, f, offset_size);

Likewise for the signed SFrame FP offset.

kernel/unwind/sframe.c line 207

Quoted 12 lines
kernel/unwind/sframe.c +12 -0
+	}
+
+	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;
+}

...

I have verified that reintroducing and using SFRAME_GET_USER_SIGNED() works correctly.

Regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
#47 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf
Quoted 1 line
kernel/unwind/sframe.c +0 -0

...

Quoted 8 lines
kernel/unwind/sframe.c +8 -0
+static int find_fre(struct sframe_section *sec, struct sframe_fde *fde,
+		    unsigned long ip, struct unwind_user_frame *frame)
+{
+	unsigned char fde_type = SFRAME_FUNC_FDE_TYPE(fde->info);
+	unsigned char fre_type = SFRAME_FUNC_FRE_TYPE(fde->info);
+	unsigned char offset_count, offset_size;
+	s32 cfa_off, ra_off, fp_off, ip_off;
+	void __user *f, *last_f = NULL;

kernel/unwind/sframe.c line 141

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+	unsigned char addr_size;
+	u32 last_fre_ip_off = 0;
+	u8 fre_info = 0;
+	int i;
+
+	addr_size = fre_type_to_size(fre_type);
+	if (!addr_size)
+		return -EINVAL;
+
+	ip_off = ip - (sec->sframe_addr + fde->start_addr);

nit: Since we already know wether the ip_off should mask or not, I think we don't have to check fde_type and mask the ip_off everytime.

	ip_off = (fde_type == SFRAME_FDE_TYPE_PCINC) ? ip_off : ip_off % fde->rep_size;
Quoted 66 lines
kernel/unwind/sframe.c +66 -0
+
+	f = (void __user *)sec->fres_addr + fde->fres_off;
+
+	for (i = 0; i < fde->fres_num; i++) {
+		u32 fre_ip_off;
+
+		SFRAME_GET_USER(fre_ip_off, f, addr_size);
+
+		if (fre_ip_off < last_fre_ip_off)
+			return -EINVAL;
+
+		last_fre_ip_off = fre_ip_off;
+
+		if (fde_type == SFRAME_FDE_TYPE_PCINC) {
+			if (ip_off < fre_ip_off)
+				break;
+		} else {
+			/* SFRAME_FDE_TYPE_PCMASK */
+			if (ip_off % fde->rep_size < fre_ip_off)
+				break;
+		}
+
+		SFRAME_GET_USER(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(cfa_off, f, offset_size);
+	offset_count--;
+
+	ra_off = sec->ra_off;
+	if (!ra_off) {
+		if (!offset_count--)
+			return -EINVAL;
+
+		SFRAME_GET_USER(ra_off, f, offset_size);
+	}
+
+	fp_off = sec->fp_off;
+	if (!fp_off && offset_count) {
+		offset_count--;
+		SFRAME_GET_USER(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;
+}
#48 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf

On 28.10.2024 22:47, Josh Poimboeuf wrote:
...

Quoted 1 line
kernel/unwind/sframe.c +0 -0

...

kernel/unwind/sframe.c line 81

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+		    struct sframe_fde *fde)
+{
+	struct sframe_fde __user *first, *last, *found = NULL;
+	u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+	ip_off = ip - sec->sframe_addr;
+
+	first = (void __user *)sec->fdes_addr;
+	last = first + sec->fdes_nr;

Could it be that this needs to be:

	last = first + sec->fdes_nr - 1;

kernel/unwind/sframe.c line 94

Quoted 13 lines
kernel/unwind/sframe.c +13 -0
+	while (first <= last) {
+		struct sframe_fde __user *mid;
+		u32 func_off;
+
+		mid = first + ((last - first) / 2);
+
+		if (get_user(func_off, (s32 __user *)mid))
+			return -EFAULT;
+
+		if (ip_off >= func_off) {
+			/* validate sort order */
+			if (func_off < func_off_low)
+				return -EINVAL;

Otherwise I run into this when the IP is within the function whose FDE is the last one in the .sframe section:

find_fde: IP=0x000000000110fbcc: ERROR: func_off < func_off_low (func_off=196608, func_off_low=4294224904)

110fbcc dump_sframe+0x2ec (/opt/binutils-sframe2/bin/objdump)

func idx [2275]: pc = 0x110f8e0, size = 3310 bytes <dump_sframe>
STARTPC CFA FP RA INFO

000000000110f8e0  sp+160    u         u            (1*1B)
000000000110f8e6  sp+160    c-72      c-48         (3*1B)
000000000110f8f6  sp+632    c-72      c-48         (3*1B)
000000000110fa82  sp+160    u         u            (1*1B)
000000000110fa88  sp+632    c-72      c-48         (3*1B)
0000000001110486  sp+160    u         u            (1*1B)
000000000111048c  sp+632    c-72      c-48         (3*1B)
0000000001110574  sp+160    u         u            (1*1B)
000000000111057a  sp+632    c-72      c-48         (3*1B)
Quoted 18 lines
kernel/unwind/sframe.c +18 -0
+
+			func_off_low = func_off;
+
+			found = mid;
+			first = mid + 1;
+		} else {
+			/* validate sort order */
+			if (func_off > func_off_high)
+				return -EINVAL;
+
+			func_off_high = func_off;
+
+			last = mid - 1;
+		}
+	}
+
+	if (!found)
+		return -EINVAL;

kernel/unwind/sframe.c line 122

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+
+	if (copy_from_user(fde, found, sizeof(*fde)))
+		return -EFAULT;
+
+	/* check for gaps */
+	if (ip_off < fde->start_addr || ip_off >= fde->start_addr + fde->size)
+		return -EINVAL;
+
+	return 0;
+}

Thanks and regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
maintainer
#49 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 17 lines · On Thu, 7 Nov 2024 17:59:08 +0100 Jens Remus <[email protected]> wrote:

On 28.10.2024 22:47, Josh Poimboeuf wrote:
...

Quoted 1 line
kernel/unwind/sframe.c +0 -0

...

kernel/unwind/sframe.c line 81

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+		    struct sframe_fde *fde)
+{
+	struct sframe_fde __user *first, *last, *found = NULL;
+	u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+	ip_off = ip - sec->sframe_addr;
+
+	first = (void __user *)sec->fdes_addr;
+	last = first + sec->fdes_nr;  

Could it be that this needs to be:

Quoted 1 line
	last = first + sec->fdes_nr - 1;

Yep, I discovered the same issue.

-- Steve

Quoted 5 lines
Quoted 5 lines
kernel/unwind/sframe.c +4 -0
+	while (first <= last) {
+		struct sframe_fde __user *mid;
+		u32 func_off;
+
author maintainer
#50 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 20 lines · Steven Rostedt
Quoted 17 lines · On Thu, 7 Nov 2024 17:59:08 +0100 Jens Remus <[email protected]> wrote:

On 28.10.2024 22:47, Josh Poimboeuf wrote:
...

Quoted 1 line
kernel/unwind/sframe.c +0 -0

...

kernel/unwind/sframe.c line 81

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+		    struct sframe_fde *fde)
+{
+	struct sframe_fde __user *first, *last, *found = NULL;
+	u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+	ip_off = ip - sec->sframe_addr;
+
+	first = (void __user *)sec->fdes_addr;
+	last = first + sec->fdes_nr;  

Could it be that this needs to be:

Quoted 3 lines
Quoted 1 line
	last = first + sec->fdes_nr - 1;

Yep, I discovered the same issue.

Indeed, thanks.

Signature · 1 line
Josh
maintainer
#51 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 21 lines · On Wed, 13 Nov 2024 13:15:35 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 20 lines · Steven Rostedt
Quoted 17 lines · On Thu, 7 Nov 2024 17:59:08 +0100 Jens Remus <[email protected]> wrote:

On 28.10.2024 22:47, Josh Poimboeuf wrote:
...

Quoted 1 line
kernel/unwind/sframe.c +0 -0

...

kernel/unwind/sframe.c

Quoted 10 lines
kernel/unwind/sframe.c +10 -0
+static int find_fde(struct sframe_section *sec, unsigned long ip,
+		    struct sframe_fde *fde)
+{
+	struct sframe_fde __user *first, *last, *found = NULL;
+	u32 ip_off, func_off_low = 0, func_off_high = -1;
+
+	ip_off = ip - sec->sframe_addr;
+
+	first = (void __user *)sec->fdes_addr;
+	last = first + sec->fdes_nr;    

Could it be that this needs to be:

Quoted 6 lines
Quoted 3 lines
Quoted 1 line
	last = first + sec->fdes_nr - 1;  

Yep, I discovered the same issue.

Indeed, thanks.

BTW, the following changes were needed to make it work for me:

-- Steve

fs/binfmt_elf.c +5 -4
index 434c548f0837..64cc3c1188ca 100644
@@ -842,7 +842,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	int first_pt_load = 1;
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-	struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
+	struct elf_phdr *elf_property_phdata = NULL;
+	unsigned long sframe_vaddr = 0;
 	unsigned long elf_brk;
 	int retval, i;
 	unsigned long elf_entry;
@@ -951,7 +952,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
 			break;
 
 		case PT_GNU_SFRAME:
-			sframe_phdr = elf_ppnt;
+			sframe_vaddr = elf_ppnt->p_vaddr;
 			break;
 
 		case PT_LOPROC ... PT_HIPROC:
@@ -1344,8 +1345,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 					    task_pid_nr(current), retval);
 	}
 
-	if (sframe_phdr)
-		sframe_add_section(load_bias + sframe_phdr->p_vaddr,
+	if (sframe_vaddr)
+		sframe_add_section(load_bias + sframe_vaddr,
 				   start_code, end_code);
 
 	regs = current_pt_regs();
kernel/unwind/sframe.c +3 -3
index 933e47696e29..ca4ef0b72772 100644
@@ -73,15 +73,15 @@ static int find_fde(struct sframe_section *sec, unsigned long ip,
 		    struct sframe_fde *fde)
 {
 	struct sframe_fde __user *first, *last, *found = NULL;
-	u32 ip_off, func_off_low = 0, func_off_high = -1;
+	s32 ip_off, func_off_low = INT_MIN, func_off_high = INT_MAX;
 
 	ip_off = ip - sec->sframe_addr;
 
 	first = (void __user *)sec->fdes_addr;
-	last = first + sec->fdes_nr;
+	last = first + sec->fdes_nr - 1;
 	while (first <= last) {
 		struct sframe_fde __user *mid;
-		u32 func_off;
+		s32 func_off;
 
 		mid = first + ((last - first) / 2);
 
kernel/unwind/user.c +2 -2
index 11aadfade005..d9cd820150c5 100644
@@ -97,7 +97,7 @@ int unwind_user_start(struct unwind_user_state *state)
 
 	if (current_has_sframe())
 		state->type = UNWIND_USER_TYPE_SFRAME;
-	else if (IS_ENABLED(CONFIG_UNWIND_USER_FP))
+	else if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))
 		state->type = UNWIND_USER_TYPE_FP;
 	else
 		state->type = UNWIND_USER_TYPE_NONE;
@@ -138,7 +138,7 @@ int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries)
 static u64 ctx_to_cookie(u64 cpu, u64 ctx)
 {
 	BUILD_BUG_ON(NR_CPUS > 65535);
-	return (ctx & ((1UL << 48) - 1)) | cpu;
+	return (ctx & ((1UL << 48) - 1)) | (cpu << 48);
 }
 
 /*
maintainer
#52 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

[ This reply fixes the linux-trace-kernel email :-p ]

Quoted 9 lines · On Wed, 13 Nov 2024 17:13:26 -0500 Steven Rostedt <[email protected]> wrote:

BTW, the following changes were needed to make it work for me:

-- Steve

fs/binfmt_elf.c +0 -0
index 434c548f0837..64cc3c1188ca 100644

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 846

Quoted 6 lines
fs/binfmt_elf.c +2 -1
@@ -842,7 +842,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	int first_pt_load = 1;
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-	struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
+	struct elf_phdr *elf_property_phdata = NULL;
+	unsigned long sframe_vaddr = 0;

Could not just save the pointer to the sframe phd, as it gets freed before we need it.

Quoted 28 lines
fs/binfmt_elf.c +3 -3
 	unsigned long elf_brk;
 	int retval, i;
 	unsigned long elf_entry;
@@ -951,7 +952,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
 			break;
 
 		case PT_GNU_SFRAME:
-			sframe_phdr = elf_ppnt;
+			sframe_vaddr = elf_ppnt->p_vaddr;
 			break;
 
 		case PT_LOPROC ... PT_HIPROC:
@@ -1344,8 +1345,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 					    task_pid_nr(current), retval);
 	}
 
-	if (sframe_phdr)
-		sframe_add_section(load_bias + sframe_phdr->p_vaddr,
+	if (sframe_vaddr)
+		sframe_add_section(load_bias + sframe_vaddr,
 				   start_code, end_code);
 
 	regs = current_pt_regs();
kernel/unwind/sframe.c +0 -0
index 933e47696e29..ca4ef0b72772 100644

kernel/unwind/sframe.c static int find_fde(struct sframe_section *sec, unsigned long ip, · line 76

Quoted 5 lines
kernel/unwind/sframe.c +1 -1
@@ -73,15 +73,15 @@ static int find_fde(struct sframe_section *sec, unsigned long ip,
 		    struct sframe_fde *fde)
 {
 	struct sframe_fde __user *first, *last, *found = NULL;
-	u32 ip_off, func_off_low = 0, func_off_high = -1;
+	s32 ip_off, func_off_low = INT_MIN, func_off_high = INT_MAX;

The ip_off is a signed it. I wrote a program to dump out the sframe section of files, and I had:

	ffffed88: (1020) size:      16 off:     146 num:       2 info: 1 rep:16
	ffffed98: (1030) size:     336 off:     154 num:       2 info:17 rep:16
	ffffefe1: (1279) size:     113 off:       0 num:       4 info: 0 rep: 0
	fffff052: (12ea) size:      54 off:      15 num:       3 info: 0 rep: 0
	fffff088: (1320) size:     167 off:      26 num:       3 info: 0 rep: 0
	fffff12f: (13c7) size:     167 off:      37 num:       4 info: 0 rep: 0
	fffff1d6: (146e) size:     167 off:      52 num:       4 info: 0 rep: 0
	fffff27d: (1515) size:      22 off:      67 num:       4 info: 0 rep: 0
	fffff293: (152b) size:     141 off:      82 num:       4 info: 0 rep: 0
	fffff320: (15b8) size:      81 off:      97 num:       4 info: 0 rep: 0
	fffff371: (1609) size:     671 off:     112 num:       4 info: 1 rep: 0
	fffff610: (18a8) size:     171 off:     131 num:       4 info: 0 rep: 0

The above turns was created by a loop of:

	fde = (void *)sframes + sizeof(*sframes) + sframes->sfh_fdeoff;
	for (s = 0; s < sframes->sfh_num_fdes; s++, fde++) {
		printf("\t%x: (%lx) size:%8u off:%8u num:%8u info:%2u rep:%2u\n",
		       fde->sfde_func_start_address,
		       fde->sfde_func_start_address + shdr->sh_offset,
		       fde->sfde_func_size,
		       fde->sfde_func_start_fre_off,
		       fde->sfde_func_num_fres,
		       fde->sfde_func_info,
		       fde->sfde_func_rep_size);
	}

As you can see, all the ip_off are negative.

kernel/unwind/sframe.c static int find_fde(struct sframe_section *sec, unsigned long ip, · line 81

Quoted 6 lines
kernel/unwind/sframe.c +1 -1
 	ip_off = ip - sec->sframe_addr;
 
 	first = (void __user *)sec->fdes_addr;
-	last = first + sec->fdes_nr;
+	last = first + sec->fdes_nr - 1;

The above was mentioned before.

Quoted 12 lines
kernel/unwind/sframe.c +1 -1
 	while (first <= last) {
 		struct sframe_fde __user *mid;
-		u32 func_off;
+		s32 func_off;
 
 		mid = first + ((last - first) / 2);
 
kernel/unwind/user.c +0 -0
index 11aadfade005..d9cd820150c5 100644

kernel/unwind/user.c int unwind_user_start(struct unwind_user_state *state) · line 100

Quoted 5 lines
kernel/unwind/user.c +1 -1
@@ -97,7 +97,7 @@ int unwind_user_start(struct unwind_user_state *state)
 
 	if (current_has_sframe())
 		state->type = UNWIND_USER_TYPE_SFRAME;
-	else if (IS_ENABLED(CONFIG_UNWIND_USER_FP))
+	else if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))

This was mentioned too.

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 141

Quoted 9 lines
kernel/unwind/user.c +1 -1
 		state->type = UNWIND_USER_TYPE_FP;
 	else
 		state->type = UNWIND_USER_TYPE_NONE;
@@ -138,7 +138,7 @@ int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries)
 static u64 ctx_to_cookie(u64 cpu, u64 ctx)
 {
 	BUILD_BUG_ON(NR_CPUS > 65535);
-	return (ctx & ((1UL << 48) - 1)) | cpu;
+	return (ctx & ((1UL << 48) - 1)) | (cpu << 48);

And so was this.

-- Steve

Quoted 3 lines
kernel/unwind/user.c +0 -0
 }
 
 /*
maintainer
#53 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 5 lines · On Wed, 13 Nov 2024 17:21:18 -0500 Steven Rostedt <[email protected]> wrote:
Quoted 5 lines
fs/binfmt_elf.c +0 -0
index 434c548f0837..64cc3c1188ca 100644
@@ -842,7 +842,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
Quoted 8 lines

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 846

Quoted 6 lines
fs/binfmt_elf.c +2 -1
 	int first_pt_load = 1;
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-	struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
+	struct elf_phdr *elf_property_phdata = NULL;
+	unsigned long sframe_vaddr = 0;  

Could not just save the pointer to the sframe phd, as it gets freed before we need it.

                                                ^^^
                                                phdr
Quoted 4 lines
Quoted 4 lines
kernel/unwind/sframe.c +0 -0
index 933e47696e29..ca4ef0b72772 100644
Quoted 8 lines

kernel/unwind/sframe.c static int find_fde(struct sframe_section *sec, unsigned long ip, · line 76

Quoted 6 lines
kernel/unwind/sframe.c +1 -1
@@ -73,15 +73,15 @@ static int find_fde(struct sframe_section *sec, unsigned long ip,
 		    struct sframe_fde *fde)
 {
 	struct sframe_fde __user *first, *last, *found = NULL;
-	u32 ip_off, func_off_low = 0, func_off_high = -1;
+	s32 ip_off, func_off_low = INT_MIN, func_off_high = INT_MAX;  

The ip_off is a signed it. I wrote a program to dump out the sframe section

                         ^^
                         int
Quoted 15 lines

of files, and I had:

	ffffed88: (1020) size:      16 off:     146 num:       2 info: 1 rep:16
	ffffed98: (1030) size:     336 off:     154 num:       2 info:17 rep:16
	ffffefe1: (1279) size:     113 off:       0 num:       4 info: 0 rep: 0
	fffff052: (12ea) size:      54 off:      15 num:       3 info: 0 rep: 0
	fffff088: (1320) size:     167 off:      26 num:       3 info: 0 rep: 0
	fffff12f: (13c7) size:     167 off:      37 num:       4 info: 0 rep: 0
	fffff1d6: (146e) size:     167 off:      52 num:       4 info: 0 rep: 0
	fffff27d: (1515) size:      22 off:      67 num:       4 info: 0 rep: 0
	fffff293: (152b) size:     141 off:      82 num:       4 info: 0 rep: 0
	fffff320: (15b8) size:      81 off:      97 num:       4 info: 0 rep: 0
	fffff371: (1609) size:     671 off:     112 num:       4 info: 1 rep: 0
	fffff610: (18a8) size:     171 off:     131 num:       4 info: 0 rep: 0
Quoted 1 line

The above turns was created by a loop of:

            ^^^^^^^^^
            items were

No idea why I typed that :-p

I can't blame jetlag anymore.

-- Steve

#54 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Steven Rostedt
Quoted 9 lines · Steven Rostedt

On Wed, 13 Nov 2024 13:15:35 -0800

BTW, the following changes were needed to make it work for me:

kernel/unwind/sframe.c +0 -0
index 933e47696e29..ca4ef0b72772 100644

kernel/unwind/sframe.c static int find_fde(struct sframe_section *sec, unsigned long ip, · line 76

Quoted 5 lines
kernel/unwind/sframe.c +1 -1
@@ -73,15 +73,15 @@ static int find_fde(struct sframe_section *sec, unsigned long ip,
  		    struct sframe_fde *fde)
  {
  	struct sframe_fde __user *first, *last, *found = NULL;
-	u32 ip_off, func_off_low = 0, func_off_high = -1;
+	s32 ip_off, func_off_low = INT_MIN, func_off_high = INT_MAX;

Coincidentally I was experimenting with exactly the same changes, except that I used S32_MIN and S32_MAX. Any preference?

kernel/unwind/sframe.c static int find_fde(struct sframe_section *sec, unsigned long ip, · line 87

Quoted 13 lines
kernel/unwind/sframe.c +2 -2
  	ip_off = ip - sec->sframe_addr;
 
  	first = (void __user *)sec->fdes_addr;
-	last = first + sec->fdes_nr;
+	last = first + sec->fdes_nr - 1;
  	while (first <= last) {
  		struct sframe_fde __user *mid;
-		u32 func_off;
+		s32 func_off;
 
  		mid = first + ((last - first) / 2);
 

Thanks and regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
#55 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

In reply to Josh Poimboeuf

kernel/unwind/user.c int unwind_user_start(struct unwind_user_state *state) · line 88

Quoted 10 lines · Josh Poimboeuf
kernel/unwind/user.c +3 -1
@@ -68,7 +83,12 @@ int unwind_user_start(struct unwind_user_state *state)
  		return -EINVAL;
  	}
 
-	state->type = UNWIND_USER_TYPE_FP;
+	if (current_has_sframe())
+		state->type = UNWIND_USER_TYPE_SFRAME;
+	else if (IS_ENABLED(CONFIG_UNWIND_USER_FP))

The test must be for CONFIG_HAVE_UNWIND_USER_FP. :-)

kernel/unwind/user.c int unwind_user_start(struct unwind_user_state *state) · line 94

Quoted 6 lines
kernel/unwind/user.c +3 -0
+		state->type = UNWIND_USER_TYPE_FP;
+	else
+		state->type = UNWIND_USER_TYPE_NONE;
 
  	state->sp = user_stack_pointer(regs);
  	state->ip = instruction_pointer(regs);

Regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
maintainer
#56 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 8 lines · On Wed, 13 Nov 2024 16:56:25 +0100 Jens Remus <[email protected]> wrote:
Quoted 6 lines · Josh Poimboeuf
kernel/unwind/user.c +0 -0
@@ -68,7 +83,12 @@ int unwind_user_start(struct unwind_user_state *state)
  		return -EINVAL;
  	}
 
Quoted 6 lines

kernel/unwind/user.c int unwind_user_start(struct unwind_user_state *state) · line 88

Quoted 4 lines
kernel/unwind/user.c +3 -1
-	state->type = UNWIND_USER_TYPE_FP;
+	if (current_has_sframe())
+		state->type = UNWIND_USER_TYPE_SFRAME;
+	else if (IS_ENABLED(CONFIG_UNWIND_USER_FP))  

The test must be for CONFIG_HAVE_UNWIND_USER_FP. :-)

Yep, that too.

Thanks,

-- Steve

Quoted 10 lines

kernel/unwind/user.c int unwind_user_start(struct unwind_user_state *state) · line 94

Quoted 7 lines
kernel/unwind/user.c +3 -0
+		state->type = UNWIND_USER_TYPE_FP;
+	else
+		state->type = UNWIND_USER_TYPE_NONE;
 
  	state->sp = user_stack_pointer(regs);
  	state->ip = instruction_pointer(regs);  

Regards,
Jens

author maintainer
#57 lore raw

Re: [PATCH v3 09/19] unwind: Introduce sframe user space unwinding

Quoted 11 lines · Steven Rostedt
Quoted 8 lines · On Wed, 13 Nov 2024 16:56:25 +0100 Jens Remus <[email protected]> wrote:
Quoted 6 lines · Josh Poimboeuf
kernel/unwind/user.c +0 -0
@@ -68,7 +83,12 @@ int unwind_user_start(struct unwind_user_state *state)
  		return -EINVAL;
  	}
 
Quoted 8 lines
Quoted 6 lines

kernel/unwind/user.c int unwind_user_start(struct unwind_user_state *state) · line 88

Quoted 4 lines
kernel/unwind/user.c +3 -1
-	state->type = UNWIND_USER_TYPE_FP;
+	if (current_has_sframe())
+		state->type = UNWIND_USER_TYPE_SFRAME;
+	else if (IS_ENABLED(CONFIG_UNWIND_USER_FP))  

The test must be for CONFIG_HAVE_UNWIND_USER_FP. :-)

Yep, that too.

I also found this one, so that makes three of us!

It's too bad IS_ENABLED() doesn't catch typos.

Signature · 1 line
Josh
author maintainer
#58 lore raw

PATCH v3 10/19 unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_SFRAME

In reply to Josh Poimboeuf

Binutils 2.41 supports generating sframe v2 for x86_64. It works well in testing so enable it.

---
 arch/x86/Kconfig | 1 +
 1 file changed, 1 insertion(+)
arch/x86/Kconfig +1 -0
index f91098d6f535..3e6f4c80c5b5 100644
@@ -290,6 +290,7 @@ config X86
 	select HAVE_UACCESS_VALIDATION		if HAVE_OBJTOOL
 	select HAVE_UNSTABLE_SCHED_CLOCK
 	select HAVE_UNWIND_USER_FP		if X86_64
+	select HAVE_UNWIND_USER_SFRAME		if X86_64
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_GENERIC_VDSO
 	select VDSO_GETRANDOM			if X86_64
Signature · 1 line
2.47.0
maintainer
#59 lore raw

Re: [PATCH v3 10/19] unwind/x86: Enable CONFIG_HAVE_UNWIND_USER_SFRAME

Quoted 2 lines · Josh Poimboeuf

Binutils 2.41 supports generating sframe v2 for x86_64. It works well in testing so enable it.

There is no sframe for 32bit?

author maintainer
#60 lore raw

PATCH v3 11/19 unwind: Add deferred user space unwinding API

In reply to Josh Poimboeuf

Add unwind_user_deferred() which allows callers to schedule task work to unwind the user space stack before returning to user space. This solves several problems for its callers:

- Ensure the unwind happens in task context even if the caller may running in interrupt context.

- Only do the unwind once, even if called multiple times either by the same caller or multiple callers.

- Create a "context context" cookie which allows trace post-processing to correlate kernel unwinds/traces with the user unwind.

---
 include/linux/entry-common.h |   3 +
 include/linux/sched.h        |   5 +
 include/linux/unwind_user.h  |  56 ++++++++++
 kernel/fork.c                |   4 +
 kernel/unwind/user.c         | 199 +++++++++++++++++++++++++++++++++++
 5 files changed, 267 insertions(+)
include/linux/entry-common.h +3 -0
index 1e50cdb83ae5..efbe8f964f31 100644
@@ -12,6 +12,7 @@
 #include <linux/resume_user_mode.h>
 #include <linux/tick.h>
 #include <linux/kmsan.h>
+#include <linux/unwind_user.h>
 
 #include <asm/entry-common.h>
 
@@ -111,6 +112,8 @@ static __always_inline void enter_from_user_mode(struct pt_regs *regs)
 	CT_WARN_ON(__ct_state() != CT_STATE_USER);
 	user_exit_irqoff();
 
+	unwind_enter_from_user_mode();
+
 	instrumentation_begin();
 	kmsan_unpoison_entry_regs(regs);
 	trace_hardirqs_off_finish();
include/linux/sched.h +5 -0
index 5007a8e2d640..31b6f1d763ef 100644
@@ -47,6 +47,7 @@
 #include <linux/livepatch_sched.h>
 #include <linux/uidgid_types.h>
 #include <asm/kmap_size.h>
+#include <linux/unwind_user.h>
 
 /* task_struct member predeclarations (sorted alphabetically): */
 struct audit_context;
@@ -1592,6 +1593,10 @@ struct task_struct {
 	struct user_event_mm		*user_event_mm;
 #endif
 
+#ifdef CONFIG_UNWIND_USER
+	struct unwind_task_info		unwind_task_info;
+#endif
+
 	/*
 	 * New fields for task_struct should be added above here, so that
 	 * they are included in the randomized portion of task_struct.
include/linux/unwind_user.h +56 -0

72 lines not on this page · raw message

kernel/fork.c +4 -0
index 60f14fbab956..d7580067853d 100644
@@ -105,6 +105,7 @@
 #include <linux/rseq.h>
 #include <uapi/linux/pidfd.h>
 #include <linux/pidfs.h>
+#include <linux/unwind_user.h>
 #include <linux/sframe.h>
 
 #include <asm/pgalloc.h>
@@ -972,6 +973,7 @@ void __put_task_struct(struct task_struct *tsk)
 	WARN_ON(refcount_read(&tsk->usage));
 	WARN_ON(tsk == current);
 
+	unwind_task_free(tsk);
 	sched_ext_free(tsk);
 	io_uring_free(tsk);
 	cgroup_free(tsk);
@@ -2348,6 +2350,8 @@ __latent_entropy struct task_struct *copy_process(
 	p->bpf_ctx = NULL;
 #endif
 
+	unwind_task_init(p);
+
 	/* Perform scheduler related setup. Assign this task to a CPU. */
 	retval = sched_fork(clone_flags, p);
 	if (retval)
kernel/unwind/user.c +199 -0

214 lines not on this page · raw message

Signature · 1 line
2.47.0
maintainer
#61 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 141

Quoted 10 lines · Josh Poimboeuf
kernel/unwind/user.c +10 -0
+/*
+ * The "context cookie" is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds.  It has the CPU id the highest
+ * 16 bits and a per-CPU entry counter in the lower 48 bits.
+ */
+static u64 ctx_to_cookie(u64 cpu, u64 ctx)
+{
+	BUILD_BUG_ON(NR_CPUS > 65535);
+	return (ctx & ((1UL << 48) - 1)) | cpu;
+}

Did you mean to: (cpu << 48) ?

author maintainer
#62 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 14 lines · Peter Zijlstra

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 141

Quoted 10 lines · Josh Poimboeuf
kernel/unwind/user.c +10 -0
+/*
+ * The "context cookie" is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds.  It has the CPU id the highest
+ * 16 bits and a per-CPU entry counter in the lower 48 bits.
+ */
+static u64 ctx_to_cookie(u64 cpu, u64 ctx)
+{
+	BUILD_BUG_ON(NR_CPUS > 65535);
+	return (ctx & ((1UL << 48) - 1)) | cpu;
+}

Did you mean to: (cpu << 48) ?

Indeed... that was the victim of a late refactoring.

Signature · 1 line
Josh
maintainer
#63 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

In reply to Josh Poimboeuf
Quoted 2 lines · Josh Poimboeuf
(quoted diff) +2 -0
+static void unwind_user_task_work(struct callback_head *head)
+{

...

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 264

Quoted 6 lines
kernel/unwind/user.c +6 -0
+	guard(rwsem_read)(&callbacks_rwsem);
+
+	for_each_set_bit(i, &pending, UNWIND_MAX_CALLBACKS) {
+		if (callbacks[i])
+			callbacks[i]->func(&trace, cookie, privs[i]);
+	}

I'm fairly sure people will come with pitchforks for that read-lock there. They scale like shit on big systems. Please use SRCU or somesuch.

Quoted 33 lines
kernel/unwind/user.c +33 -0
+}
+
+int unwind_user_register(struct unwind_callback *callback, unwind_callback_t func)
+{
+	scoped_guard(rwsem_write, &callbacks_rwsem) {
+		for (int i = 0; i < UNWIND_MAX_CALLBACKS; i++) {
+			if (!callbacks[i]) {
+				callback->func = func;
+				callback->idx = i;
+				callbacks[i] = callback;
+				return 0;
+			}
+		}
+	}
+
+	callback->func = NULL;
+	callback->idx = -1;
+	return -ENOSPC;
+}
+
+int unwind_user_unregister(struct unwind_callback *callback)
+{
+	if (callback->idx < 0)
+		return -EINVAL;
+
+	scoped_guard(rwsem_write, &callbacks_rwsem)
+		callbacks[callback->idx] = NULL;
+
+	callback->func = NULL;
+	callback->idx = -1;
+
+	return 0;
+}
author maintainer
#64 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
(quoted diff) +2 -0
+static void unwind_user_task_work(struct callback_head *head)
+{

...

Quoted 9 lines

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 264

Quoted 6 lines
kernel/unwind/user.c +6 -0
+	guard(rwsem_read)(&callbacks_rwsem);
+
+	for_each_set_bit(i, &pending, UNWIND_MAX_CALLBACKS) {
+		if (callbacks[i])
+			callbacks[i]->func(&trace, cookie, privs[i]);
+	}

I'm fairly sure people will come with pitchforks for that read-lock there. They scale like shit on big systems. Please use SRCU or somesuch.

I'd expect that unwind_user_{register,unregister}() would only be called once per tracing component during boot so there wouldn't be any contention.

But I think I can make SRCU work here.

Signature · 1 line
Josh
maintainer
#65 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 16 lines · Josh Poimboeuf
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
(quoted diff) +2 -0
+static void unwind_user_task_work(struct callback_head *head)
+{

...

Quoted 9 lines

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 264

Quoted 6 lines
kernel/unwind/user.c +6 -0
+	guard(rwsem_read)(&callbacks_rwsem);
+
+	for_each_set_bit(i, &pending, UNWIND_MAX_CALLBACKS) {
+		if (callbacks[i])
+			callbacks[i]->func(&trace, cookie, privs[i]);
+	}

I'm fairly sure people will come with pitchforks for that read-lock there. They scale like shit on big systems. Please use SRCU or somesuch.

Quoted 3 lines

I'd expect that unwind_user_{register,unregister}() would only be called once per tracing component during boot so there wouldn't be any contention.

The read-lock does an atomic op on the lock word, try and do that with 200+ CPUs and things get really sad.

Quoted 1 line

But I think I can make SRCU work here.

Thanks!

maintainer
#66 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

In reply to Josh Poimboeuf
Quoted 4 lines · Josh Poimboeuf

+ * The only exception is when the task has migrated to another CPU, *and* this
+ * is called while the task work is running (or has already run). Then a new
+ * cookie will be generated and the callback will be called again for the new
+ * cookie.

So that's a bit crap. The user stack won't change for having been migrated.

So perf can readily use the full u64 cookie value as a sequence number, since the whole perf record will already have the TID of the task in. Mixing in this CPU number for no good reason and causing trouble like this just doesn't make sense to me.

If ftrace needs brain damage like this, can't we push this to the user?

That is, do away with the per-cpu sequence crap, and add a per-task counter that is incremented for every return-to-userspace.

author maintainer
#67 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 15 lines · Peter Zijlstra
Quoted 4 lines · Josh Poimboeuf

+ * The only exception is when the task has migrated to another CPU, *and* this
+ * is called while the task work is running (or has already run). Then a new
+ * cookie will be generated and the callback will be called again for the new
+ * cookie.

So that's a bit crap. The user stack won't change for having been migrated.

So perf can readily use the full u64 cookie value as a sequence number, since the whole perf record will already have the TID of the task in. Mixing in this CPU number for no good reason and causing trouble like this just doesn't make sense to me.

Quoted 4 lines

If ftrace needs brain damage like this, can't we push this to the user?

That is, do away with the per-cpu sequence crap, and add a per-task counter that is incremented for every return-to-userspace.

That would definitely make things easier for me, though IIRC Steven and Mathieu had some concerns about TID wrapping over days/months/years.

With that mindset I suppose the per-CPU counter could also wrap, though that could be mitigated by making the cookie a struct with more bits.

Signature · 1 line
Josh
#68 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 18 lines · Josh Poimboeuf
Quoted 17 lines · Peter Zijlstra
Quoted 4 lines · Josh Poimboeuf

+ * The only exception is when the task has migrated to another CPU, *and* this
+ * is called while the task work is running (or has already run). Then a new
+ * cookie will be generated and the callback will be called again for the new
+ * cookie.

So that's a bit crap. The user stack won't change for having been migrated.

So perf can readily use the full u64 cookie value as a sequence number, since the whole perf record will already have the TID of the task in. Mixing in this CPU number for no good reason and causing trouble like this just doesn't make sense to me.

If ftrace needs brain damage like this, can't we push this to the user?

Quoted 9 lines
Quoted 2 lines

That is, do away with the per-cpu sequence crap, and add a per-task counter that is incremented for every return-to-userspace.

That would definitely make things easier for me, though IIRC Steven and Mathieu had some concerns about TID wrapping over days/months/years.

With that mindset I suppose the per-CPU counter could also wrap, though that could be mitigated by making the cookie a struct with more bits.

AFAIR, the scheme we discussed in Prague was different than the implementation here.

We discussed having a free-running counter per-cpu, and combining it with the cpu number as top (or low) bits, to effectively make a 64-bit value that is unique across the entire system, but without requiring a global counter with its associated cache line bouncing.

Here is part where the implementation here differs from our discussion:
I recall we discussed keeping a snapshot of the counter value within the task struct of the thread. So we only snapshot the per-cpu value on first use after entering the kernel, and after that we use the same per-cpu value snapshot (from task struct) up until return to userspace. We clear the task struct cookie snapshot on return to userspace.

This way, even if the thread is migrated during the system call, the cookie value does not change: it simply depends on the point where it was first snapshotted (either before or after migration). From that point until return to userspace, we just use the per-thread snapshot value.

This should allow us to keep a global cookie semantic (no need to tie this to tracer-specific knowledge about current TID), without the migration corner cases discussed in the comment above.

Thanks,

Mathieu

Signature · 3 lines
Mathieu Desnoyers
EfficiOS Inc.
https://www.efficios.com
maintainer
#69 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 50 lines · Mathieu Desnoyers
Quoted 18 lines · Josh Poimboeuf
Quoted 17 lines · Peter Zijlstra
Quoted 4 lines · Josh Poimboeuf

+ * The only exception is when the task has migrated to another CPU, *and* this
+ * is called while the task work is running (or has already run). Then a new
+ * cookie will be generated and the callback will be called again for the new
+ * cookie.

So that's a bit crap. The user stack won't change for having been migrated.

So perf can readily use the full u64 cookie value as a sequence number, since the whole perf record will already have the TID of the task in. Mixing in this CPU number for no good reason and causing trouble like this just doesn't make sense to me.

If ftrace needs brain damage like this, can't we push this to the user?

Quoted 9 lines
Quoted 2 lines

That is, do away with the per-cpu sequence crap, and add a per-task counter that is incremented for every return-to-userspace.

That would definitely make things easier for me, though IIRC Steven and Mathieu had some concerns about TID wrapping over days/months/years.

With that mindset I suppose the per-CPU counter could also wrap, though that could be mitigated by making the cookie a struct with more bits.

AFAIR, the scheme we discussed in Prague was different than the implementation here.

We discussed having a free-running counter per-cpu, and combining it with the cpu number as top (or low) bits, to effectively make a 64-bit value that is unique across the entire system, but without requiring a global counter with its associated cache line bouncing.

Here is part where the implementation here differs from our discussion:
I recall we discussed keeping a snapshot of the counter value within the task struct of the thread. So we only snapshot the per-cpu value on first use after entering the kernel, and after that we use the same per-cpu value snapshot (from task struct) up until return to userspace. We clear the task struct cookie snapshot on return to userspace.

This way, even if the thread is migrated during the system call, the cookie value does not change: it simply depends on the point where it was first snapshotted (either before or after migration). From that point until return to userspace, we just use the per-thread snapshot value.

Quoted 3 lines

This should allow us to keep a global cookie semantic (no need to tie this to tracer-specific knowledge about current TID), without the migration corner cases discussed in the comment above.

The 48:16 bit split gives you uniqueness for around 78 hours at 1GHz.

But seriously, perf doesn't need this. It really only needs a sequence number if you care to stitch over a LOST packet (and I can't say I care about that case much) -- and doing that right doesn't really take much at all.

maintainer
#70 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 1 line · On Tue, 29 Oct 2024 19:20:32 +0100 Peter Zijlstra <[email protected]> wrote:

The 48:16 bit split gives you uniqueness for around 78 hours at 1GHz.

Are you saying that there will be one system call per nanosecond? This number is incremented only when a task enters the kernel from user spaces *and* requests a stack trace. If that happens 1000 times a second, that would still be around 9000 years.

Quoted 5 lines

But seriously, perf doesn't need this. It really only needs a sequence number if you care to stitch over a LOST packet (and I can't say I care about that case much) -- and doing that right doesn't really take much at all.

Perf may not care because it has a unique descriptor per task, right? Where it can already know what events are associated to a task. But that's just a unique characteristic of perf. The unwinder should give a identifier for every user space stack trace that it will produce and pass that back to the tracer when it requests a stack trace but it cannot yet be performed. This identifier is what we are calling a context cookie. Then when it wants the stack trace, the unwinder will give the tracer the stack trace along with the identifier (context-cookie) that this stack trace was for in the past.

It definitely belongs with the undwinder logic.

-- Steve

maintainer
#71 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 9 lines · Steven Rostedt
Quoted 1 line · On Tue, 29 Oct 2024 19:20:32 +0100 Peter Zijlstra <[email protected]> wrote:

The 48:16 bit split gives you uniqueness for around 78 hours at 1GHz.

Are you saying that there will be one system call per nanosecond? This number is incremented only when a task enters the kernel from user spaces *and* requests a stack trace. If that happens 1000 times a second, that would still be around 9000 years.

We used to be able to do well over a million syscalls a second. I'm not exactly sure where we are now, the whole speculation shit-show hurt things quite badly.

Quoted 6 lines
Quoted 5 lines

But seriously, perf doesn't need this. It really only needs a sequence number if you care to stitch over a LOST packet (and I can't say I care about that case much) -- and doing that right doesn't really take much at all.

Quoted 9 lines

Perf may not care because it has a unique descriptor per task, right? Where it can already know what events are associated to a task. But that's just a unique characteristic of perf. The unwinder should give a identifier for every user space stack trace that it will produce and pass that back to the tracer when it requests a stack trace but it cannot yet be performed. This identifier is what we are calling a context cookie. Then when it wants the stack trace, the unwinder will give the tracer the stack trace along with the identifier (context-cookie) that this stack trace was for in the past.

You're designing things inside out again. You should add functionality by adding layers.

Pass a void * into the 'request-unwind' and have the 'do-unwind' callback get that same pointer. Then anybody that needs identifiers to figure out where things came from can stuff something in there.

maintainer
#72 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 6 lines · On Wed, 30 Oct 2024 15:03:24 +0100 Peter Zijlstra <[email protected]> wrote:

You're designing things inside out again. You should add functionality by adding layers.

Pass a void * into the 'request-unwind' and have the 'do-unwind' callback get that same pointer. Then anybody that needs identifiers to figure out where things came from can stuff something in there.

What the hell should I do with a void pointer? I want a stack trace, I ask for one, it gives me an identifier for it, then at the end when it does my callback it gives me the stack trace I asked for with the identifier that it belongs to.

The identifier should be part of the unwinder code as it has to do with the stack trace and has nothing to do with tracing.

-- Steve

author maintainer
#73 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 10 lines · Steven Rostedt
Quoted 6 lines · On Wed, 30 Oct 2024 15:03:24 +0100 Peter Zijlstra <[email protected]> wrote:

You're designing things inside out again. You should add functionality by adding layers.

Pass a void * into the 'request-unwind' and have the 'do-unwind' callback get that same pointer. Then anybody that needs identifiers to figure out where things came from can stuff something in there.

Quoted 7 lines

What the hell should I do with a void pointer? I want a stack trace, I ask for one, it gives me an identifier for it, then at the end when it does my callback it gives me the stack trace I asked for with the identifier that it belongs to.

The identifier should be part of the unwinder code as it has to do with the stack trace and has nothing to do with tracing.

If at least most of the users would find the cookie useful -- which it sounds like they would, maybe even including perf -- then it makes sense to integrate that into the unwinder. It also helps the unwinder disambiguate which callbacks have been called and whether the stack has already been unwound.

That said, I'm also implementing the void pointer thing, as AFAICT perf needs to pass the perf_event pointer to the callback. There's no reason we can't have both.

Signature · 1 line
Josh
author maintainer
#74 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

In reply to Mathieu Desnoyers
Quoted 8 lines · Mathieu Desnoyers
Quoted 8 lines
Quoted 4 lines

If ftrace needs brain damage like this, can't we push this to the user?

That is, do away with the per-cpu sequence crap, and add a per-task counter that is incremented for every return-to-userspace.

That would definitely make things easier for me, though IIRC Steven and Mathieu had some concerns about TID wrapping over days/months/years.

Quoted 6 lines
Quoted 3 lines

With that mindset I suppose the per-CPU counter could also wrap, though that could be mitigated by making the cookie a struct with more bits.

AFAIR, the scheme we discussed in Prague was different than the implementation here.

It does differ a bit. I'll explain why below.

Quoted 11 lines

We discussed having a free-running counter per-cpu, and combining it with the cpu number as top (or low) bits, to effectively make a 64-bit value that is unique across the entire system, but without requiring a global counter with its associated cache line bouncing.

Here is part where the implementation here differs from our discussion:
I recall we discussed keeping a snapshot of the counter value within the task struct of the thread. So we only snapshot the per-cpu value on first use after entering the kernel, and after that we use the same per-cpu value snapshot (from task struct) up until return to userspace. We clear the task struct cookie snapshot on return to userspace.

Right, so adding some details to this, what I remember specifically deciding on:

- In unwind_user_deferred(), if task cookie is 0, it snapshots the per-cpu counter, stores the old value in the task cookie, and increments the new value (with CPU # in top 48 bits).

- Future calls to unwind_user_deferred() see the task cookie is set and reuse the same cookie.

- Then the task work (which runs right before exiting the kernel) clears the task cookie (with interrupts disabled), does the unwind, and calls the callbacks. It clears the task cookie so that any future calls to unwind_user_deferred() (either before exiting the kernel or after next entry) are guaranteed to get an unwind.

That's what I had implemented originally. It had a major performance issue, particularly for long stacks (bash sometimes had 300+ stack frames in my testing).

The task work runs with interrupts enabled. So if another PMU interrupt and call to unwind_user_deferred() happens after the task work clears the task cookie but before kernel exit, a new cookie is generated and an additional task work is scheduled. For long stacks, performance gets really bad, dominated by all the extra unnecessary unwinding.

So I changed the design a bit:

- Increment a per-cpu counter at kernel entry before interrupts are enabled.

- In unwind_user_deferred(), if task cookie is 0, it sets the task cookie based on the per-cpu counter, like before. However if this cookie was the last one used by this callback+task, it skips the callback altogether.

So it eliminates duplicate unwinds except for the CPU migration case.

If I change the entry code to increment a per-task counter instead of a per-cpu counter then this problem goes away. I was just concerned about the performance impacts of doing that on every entry.

Signature · 1 line
Josh
#75 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 37 lines · Josh Poimboeuf
Quoted 8 lines · Mathieu Desnoyers
Quoted 8 lines
Quoted 4 lines

If ftrace needs brain damage like this, can't we push this to the user?

That is, do away with the per-cpu sequence crap, and add a per-task counter that is incremented for every return-to-userspace.

That would definitely make things easier for me, though IIRC Steven and Mathieu had some concerns about TID wrapping over days/months/years.

Quoted 6 lines
Quoted 3 lines

With that mindset I suppose the per-CPU counter could also wrap, though that could be mitigated by making the cookie a struct with more bits.

AFAIR, the scheme we discussed in Prague was different than the implementation here.

It does differ a bit. I'll explain why below.

Quoted 11 lines

We discussed having a free-running counter per-cpu, and combining it with the cpu number as top (or low) bits, to effectively make a 64-bit value that is unique across the entire system, but without requiring a global counter with its associated cache line bouncing.

Here is part where the implementation here differs from our discussion:
I recall we discussed keeping a snapshot of the counter value within the task struct of the thread. So we only snapshot the per-cpu value on first use after entering the kernel, and after that we use the same per-cpu value snapshot (from task struct) up until return to userspace. We clear the task struct cookie snapshot on return to userspace.

Right, so adding some details to this, what I remember specifically deciding on:

- In unwind_user_deferred(), if task cookie is 0, it snapshots the per-cpu counter, stores the old value in the task cookie, and increments the new value (with CPU # in top 48 bits).

Quoted 8 lines

- Future calls to unwind_user_deferred() see the task cookie is set and reuse the same cookie.

- Then the task work (which runs right before exiting the kernel) clears the task cookie (with interrupts disabled), does the unwind, and calls the callbacks. It clears the task cookie so that any future calls to unwind_user_deferred() (either before exiting the kernel or after next entry) are guaranteed to get an unwind.

This is where I think we should improve the logic.

If an unwind_user_deferred() is called right over/after the unwind, I don't think we want to issue another stack walk: it would be redundant with the one already in progress or completed.

What we'd want is to make sure that the cookie returned to that unwind_user_deferred() is the same as the cookie associated with the in-progress or completed stack unwind. This way, trace analysis can look at the surrounding events (before and after) the unwind request and find the associated call stack.

Quoted 4 lines

That's what I had implemented originally. It had a major performance issue, particularly for long stacks (bash sometimes had 300+ stack frames in my testing).

That's probably because long stacks require a lot of work (user pages accesses) to be done when stack walking, which increases the likeliness of re-issuing unwind_user_deferred() while the stack walk is being done.

That's basically a lack-of-progress issue: a sufficiently long stack walk with a sufficiently frequent unwind_user_deferred() trigger could make the system stall forever trying to service stalk walks again and again. That's bad.

Quoted 6 lines

The task work runs with interrupts enabled. So if another PMU interrupt and call to unwind_user_deferred() happens after the task work clears the task cookie but before kernel exit, a new cookie is generated and an additional task work is scheduled. For long stacks, performance gets really bad, dominated by all the extra unnecessary unwinding.

What you want here is to move the point where you clear the task cookie to _after_ completion of stack unwind. There are a few ways this can be done:

A) Clear the task cookie in the task_work() right after the unwind_user_deferred() is completed. Downside: if some long task work happen to be done after the stack walk, a new unwind_user_deferred() could be issued again and we may end up looping forever taking stack unwind and never actually making forward progress.

B) Clear the task cookie after the exit_to_user_mode_loop is done, before returning to user-space, while interrupts are disabled.

C) Clear the task cookie when entering kernel space again.

I think (B) and (C) could be interesting approaches, perhaps with
(B) slightly more interesting because it cleans up after itself before returning to userspace. Also (B) allows us to return to a purely lazy scheme where there is nothing to do when entering the kernel. This is therefore more efficient in terms of cache locality, because we can expect our per-task state to be cache hot when returning to userspace, but not necessarily when entering into the kernel.

Quoted 6 lines

So I changed the design a bit:

- Increment a per-cpu counter at kernel entry before interrupts are enabled.

Quoted 6 lines

- In unwind_user_deferred(), if task cookie is 0, it sets the task cookie based on the per-cpu counter, like before. However if this cookie was the last one used by this callback+task, it skips the callback altogether.

So it eliminates duplicate unwinds except for the CPU migration case.

This sounds complicated and fragile. And why would we care about duplicated unwinds on cpu migration ?

What prevents us from moving the per-task cookie clearing to after exit_to_user_mode_loop instead ? Then there is no need to do per-cpu counter increment on every kernel entry and we can go back to a lazy scheme.

Quoted 4 lines

If I change the entry code to increment a per-task counter instead of a per-cpu counter then this problem goes away. I was just concerned about the performance impacts of doing that on every entry.

Moving from per-cpu to per-task makes this cookie task-specific and not global anymore, I don't think we want this for a stack walking infrastructure meant to be used by various tracers. Also a global cookie is more robust and does not depend on guaranteeing that all the trace data is present to guarantee current thread ID accuracy and thus that cookies match between deferred unwind request and their fulfillment.

Thanks,

Mathieu

Signature · 3 lines
Mathieu Desnoyers
EfficiOS Inc.
https://www.efficios.com
author maintainer
#76 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 4 lines · Mathieu Desnoyers

What you want here is to move the point where you clear the task cookie to _after_ completion of stack unwind. There are a few ways this can be done:

Quoted 8 lines

A) Clear the task cookie in the task_work() right after the unwind_user_deferred() is completed. Downside: if some long task work happen to be done after the stack walk, a new unwind_user_deferred() could be issued again and we may end up looping forever taking stack unwind and never actually making forward progress.

B) Clear the task cookie after the exit_to_user_mode_loop is done, before returning to user-space, while interrupts are disabled.

Problem is, if another tracer calls unwind_user_deferred() for the first time, after the task work but before the task cookie gets cleared, it will see the cookie is non-zero and will fail to schedule another task work. So its callback never gets called.

Quoted 11 lines
Quoted 3 lines

If I change the entry code to increment a per-task counter instead of a per-cpu counter then this problem goes away. I was just concerned about the performance impacts of doing that on every entry.

Moving from per-cpu to per-task makes this cookie task-specific and not global anymore, I don't think we want this for a stack walking infrastructure meant to be used by various tracers. Also a global cookie is more robust and does not depend on guaranteeing that all the trace data is present to guarantee current thread ID accuracy and thus that cookies match between deferred unwind request and their fulfillment.

I don't disagree. What I meant was, on entry (or exit), increment the task cookie *with* the CPU bits included.

Or as you suggested previously, the "cookie" just be a struct with two
fields: CPU # and per-task entry counter.

Signature · 1 line
Josh
author maintainer
#77 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 8 lines · Josh Poimboeuf
Quoted 7 lines

Moving from per-cpu to per-task makes this cookie task-specific and not global anymore, I don't think we want this for a stack walking infrastructure meant to be used by various tracers. Also a global cookie is more robust and does not depend on guaranteeing that all the trace data is present to guarantee current thread ID accuracy and thus that cookies match between deferred unwind request and their fulfillment.

Quoted 5 lines

I don't disagree. What I meant was, on entry (or exit), increment the task cookie *with* the CPU bits included.

Or as you suggested previously, the "cookie" just be a struct with two
fields: CPU # and per-task entry counter.

Never mind, that wouldn't work... Two tasks could have identical per-task counters while being scheduled on the same CPU.

Signature · 1 line
Josh
author maintainer
#78 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

In reply to Josh Poimboeuf
Quoted 11 lines · Josh Poimboeuf
Quoted 10 lines · Mathieu Desnoyers

What you want here is to move the point where you clear the task cookie to _after_ completion of stack unwind. There are a few ways this can be done:

A) Clear the task cookie in the task_work() right after the unwind_user_deferred() is completed. Downside: if some long task work happen to be done after the stack walk, a new unwind_user_deferred() could be issued again and we may end up looping forever taking stack unwind and never actually making forward progress.

Quoted 7 lines
Quoted 2 lines

B) Clear the task cookie after the exit_to_user_mode_loop is done, before returning to user-space, while interrupts are disabled.

Problem is, if another tracer calls unwind_user_deferred() for the first time, after the task work but before the task cookie gets cleared, it will see the cookie is non-zero and will fail to schedule another task work. So its callback never gets called.

How about we:

- clear task cookie on entry or exit from user space

- use a different variable (rather than clearing of task cookie) to communicate whether task work is pending

- keep track of which callbacks have been called for this cookie

something like so?

int unwind_user_deferred(struct unwind_callback *callback, u64 *ctx_cookie, void *data)

{
	struct unwind_task_info *info = &current->unwind_task_info;
	unsigned int work_pending = work_pending;
	u64 cookie = info->ctx_cookie;
	int idx = callback->idx;

	if (WARN_ON_ONCE(in_nmi()))
		return -EINVAL;

	if (!current->mm)
		return -EINVAL;

	guard(irqsave)();

	if (cookie && (info->pending_callbacks & (1 << idx))) {
		/* callback already scheduled */
		goto done;
	}

	/*
	 * If this is the first call from any caller since the most recent
	 * entry from user space, initialize the task context cookie.
	 */
	if (!cookie) {
		u64 cpu = raw_smp_processor_id();
		u64 ctx_ctr;

		__this_cpu_inc(unwind_ctx_ctr);
		ctx_ctr = __this_cpu_read(unwind_ctx_ctr);

		cookie = ctx_to_cookie(cpu, ctx_ctr);
		info->ctx_cookie = cookie;

	} else {
		if (cookie == info->last_cookies[idx]) {
			/*
			 * The callback has already been called with this unwind,
			 * nothing to do.
			 */
			goto done;
		}

		if (info->pending_callbacks & (1 << idx)) {
			/* callback already scheduled */
			goto done;
		}
	}

	info->pending_callbacks |= (1 << idx);
	info->privs[idx] = data;
	info->last_cookies[idx] = cookie;

	if (!info->task_pending) {
		info->task_pending = 1; /* cleared by unwind_user_task_work() */
		task_work_add(current, &info->work, TWA_RESUME);
	}

done:

	if (ctx_cookie)
		*ctx_cookie = cookie;
	return 0;
}
Signature · 1 line
Josh
#79 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

In reply to Josh Poimboeuf
Quoted 57 lines · Josh Poimboeuf

Add unwind_user_deferred() which allows callers to schedule task work to unwind the user space stack before returning to user space. This solves several problems for its callers:

- Ensure the unwind happens in task context even if the caller may running in interrupt context.

- Only do the unwind once, even if called multiple times either by the same caller or multiple callers.

- Create a "context context" cookie which allows trace post-processing to correlate kernel unwinds/traces with the user unwind.

---
 include/linux/entry-common.h |   3 +
 include/linux/sched.h        |   5 +
 include/linux/unwind_user.h  |  56 ++++++++++
 kernel/fork.c                |   4 +
 kernel/unwind/user.c         | 199 +++++++++++++++++++++++++++++++++++
 5 files changed, 267 insertions(+)
include/linux/entry-common.h +3 -0
index 1e50cdb83ae5..efbe8f964f31 100644
@@ -12,6 +12,7 @@
 #include <linux/resume_user_mode.h>
 #include <linux/tick.h>
 #include <linux/kmsan.h>
+#include <linux/unwind_user.h>
 
 #include <asm/entry-common.h>
 
@@ -111,6 +112,8 @@ static __always_inline void enter_from_user_mode(struct pt_regs *regs)
        CT_WARN_ON(__ct_state() != CT_STATE_USER);
        user_exit_irqoff();
 
+       unwind_enter_from_user_mode();
+
        instrumentation_begin();
        kmsan_unpoison_entry_regs(regs);
        trace_hardirqs_off_finish();
include/linux/sched.h +1 -0
index 5007a8e2d640..31b6f1d763ef 100644
@@ -47,6 +47,7 @@
 #include <linux/livepatch_sched.h>
 #include <linux/uidgid_types.h>
 #include <asm/kmap_size.h>
+#include <linux/unwind_user.h>
 
 /* task_struct member predeclarations (sorted alphabetically): */
 struct audit_context;

include/linux/sched.h struct task_struct { · line 1597

Quoted 5 lines
include/linux/sched.h +2 -0
@@ -1592,6 +1593,10 @@ struct task_struct {
        struct user_event_mm            *user_event_mm;
 #endif
 
+#ifdef CONFIG_UNWIND_USER
+       struct unwind_task_info         unwind_task_info;

this is quite a lot of memory to pay on each task, a lot of which a) might not have sframe and b) might not need stack unwinding during their lifetime.

It can be a pointer and allocated in copy_process(), no? Though ideally this should be initialized lazily, if possible.

Quoted 5 lines
include/linux/sched.h +2 -0
+#endif
+
        /*
         * New fields for task_struct should be added above here, so that
         * they are included in the randomized portion of task_struct.

[...]

Quoted 22 lines
include/linux/sched.h +22 -0
+static void unwind_user_task_work(struct callback_head *head)
+{
+       struct unwind_task_info *info = container_of(head, struct unwind_task_info, work);
+       struct task_struct *task = container_of(info, struct task_struct, unwind_task_info);
+       void *privs[UNWIND_MAX_CALLBACKS];
+       struct unwind_stacktrace trace;
+       unsigned long pending;
+       u64 cookie = 0;
+       int i;
+
+       BUILD_BUG_ON(UNWIND_MAX_CALLBACKS > 32);
+
+       if (WARN_ON_ONCE(task != current))
+               return;
+
+       if (WARN_ON_ONCE(!info->ctx_cookie || !info->pending_callbacks))
+               return;
+
+       scoped_guard(irqsave) {
+               pending = info->pending_callbacks;
+               cookie = info->ctx_cookie;
+

include/linux/sched.h

Quoted 10 lines
include/linux/sched.h +10 -0
+               info->pending_callbacks = 0;
+               info->ctx_cookie = 0;
+               memcpy(privs, info->privs, sizeof(void *) * UNWIND_MAX_CALLBACKS);
+       }
+
+       if (!info->entries) {
+               info->entries = kmalloc(UNWIND_MAX_ENTRIES * sizeof(long),
+                                       GFP_KERNEL);
+               if (!info->entries)
+                       return;

uhm... can we notify callbacks that stack capture failed? otherwise we'd need some extra timeouts and other complications if we are *waiting* for this callback to be called

Quoted 6 lines
include/linux/sched.h +6 -0
+       }
+
+       trace.entries = info->entries;
+       trace.nr = 0;
+       unwind_user(&trace, UNWIND_MAX_ENTRIES);
+

[...]

author maintainer
#80 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 10 lines · Andrii Nakryiko

include/linux/sched.h struct task_struct { · line 1597

Quoted 7 lines

struct audit_context;

include/linux/sched.h +2 -0
@@ -1592,6 +1593,10 @@ struct task_struct {
        struct user_event_mm            *user_event_mm;
 #endif
 
+#ifdef CONFIG_UNWIND_USER
+       struct unwind_task_info         unwind_task_info;

this is quite a lot of memory to pay on each task, a lot of which a) might not have sframe

Frame pointers are also supported.

Quoted 1 line

and b) might not need stack unwinding during their lifetime.

Right, I'm not super happy about that.

Quoted 2 lines

It can be a pointer and allocated in copy_process(), no? Though ideally this should be initialized lazily, if possible.

Problem is, the unwinder doesn't know in advance which tasks will be unwound.

Its first clue is unwind_user_register(), would it make sense for the caller to clarify whether all tasks need to be unwound or only a specific subset?

Its second clue is unwind_user_deferred(), which is called for the task itself. But by then it's too late because it needs to access the per-task data from (potentially) irq context so it can't do a lazy allocation.

I'm definitely open to ideas...

Quoted 9 lines

kernel/unwind/user.c int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries) · line 252

Quoted 5 lines
kernel/unwind/user.c +5 -0
+       if (!info->entries) {
+               info->entries = kmalloc(UNWIND_MAX_ENTRIES * sizeof(long),
+                                       GFP_KERNEL);
+               if (!info->entries)
+                       return;

uhm... can we notify callbacks that stack capture failed? otherwise we'd need some extra timeouts and other complications if we are *waiting* for this callback to be called

Hm, do you actually plan to wait for the callback?

I assume this is BPF, can you give some high-level detail about how it will use these interfaces?

Signature · 1 line
Josh
#81 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 29 lines · Josh Poimboeuf
Quoted 10 lines · Andrii Nakryiko

include/linux/sched.h struct task_struct { · line 1597

Quoted 7 lines

struct audit_context;

include/linux/sched.h +2 -0
@@ -1592,6 +1593,10 @@ struct task_struct {
        struct user_event_mm            *user_event_mm;
 #endif
 
+#ifdef CONFIG_UNWIND_USER
+       struct unwind_task_info         unwind_task_info;

this is quite a lot of memory to pay on each task, a lot of which a) might not have sframe

Frame pointers are also supported.

Quoted 1 line

and b) might not need stack unwinding during their lifetime.

Right, I'm not super happy about that.

Quoted 2 lines

It can be a pointer and allocated in copy_process(), no? Though ideally this should be initialized lazily, if possible.

Problem is, the unwinder doesn't know in advance which tasks will be unwound.

Its first clue is unwind_user_register(), would it make sense for the caller to clarify whether all tasks need to be unwound or only a specific subset?

Quoted 6 lines

Its second clue is unwind_user_deferred(), which is called for the task itself. But by then it's too late because it needs to access the per-task data from (potentially) irq context so it can't do a lazy allocation.

I'm definitely open to ideas...

The laziest thing would be to perform GFP_ATOMIC allocation, and if that fails, oops, too bad, no stack trace for you (but, generally speaking, no big deal). Advantages are clear, though, right? Single pointer in task_struct, which most of the time will be NULL, so no unnecessary overheads.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
kernel/unwind/user.c +5 -0
+       if (!info->entries) {
+               info->entries = kmalloc(UNWIND_MAX_ENTRIES * sizeof(long),
+                                       GFP_KERNEL);
+               if (!info->entries)
+                       return;
Quoted 8 lines
Quoted 3 lines

uhm... can we notify callbacks that stack capture failed? otherwise we'd need some extra timeouts and other complications if we are *waiting* for this callback to be called

Hm, do you actually plan to wait for the callback?

I assume this is BPF, can you give some high-level detail about how it will use these interfaces?

So I have a presentation about integrating async stack trace capture into BPF at last LSF/MM/BPF, LWN.net has an article ([0]), which you might want to skim through, not sure if that will be helpful.

But very briefly, the way I see it, we'll have a workflow similar to the following:

1. BPF program will call some new API to request stack trace capture:
bpf_get_stack_async(...)
- it will pass a reference to BPF ringbuf into which captured stack
trace should go
- this API will return unique ID (similar to your cookie, but I'll
need a unique cookie for each call to bpf_get_stack_async() call, even if all of them capture the same stack trace; so what Mathie is proposing with coalescing all requests and triggering one callback isn't very convenient in this case, but we can probably work around that in BPF-specific way)
2. bpf_get_stack_async() calls unwind_user_deferred() and it expects one of two outcomes:
- either successful capture, at which point we'll submit data into
BPF ringbuf with that unique ID for user to correlate whatever data was captured synchronously and stack trace that arrived later
- OR we failed to get a stack trace, and we'll still put a small
piece of information for user to know that stack trace is never coming.

It's the last point that's important to make usability so much simpler, avoiding unnecessary custom timeouts and stuff like that. Regardless whether stack trace capture is success or not, user is guaranteed to get a "notification" about the outcome.

Hope this helps.

But basically, if I I called unwind_user_deferred(), I expect to get some callback, guaranteed, with the result or failure. The only thing that's not guaranteed (and which makes timeouts bad) is *when* this will happen. Because stack trace capture can be arbitrarily delayed and stuff. That's fine, but that also shows why timeout is tricky and necessarily fragile.

[0] https://lwn.net/Articles/978736/

Quoted 3 lines

--
Josh

author maintainer
#82 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 12 lines · Andrii Nakryiko
Quoted 12 lines

Problem is, the unwinder doesn't know in advance which tasks will be unwound.

Its first clue is unwind_user_register(), would it make sense for the caller to clarify whether all tasks need to be unwound or only a specific subset?

Its second clue is unwind_user_deferred(), which is called for the task itself. But by then it's too late because it needs to access the per-task data from (potentially) irq context so it can't do a lazy allocation.

Quoted 7 lines
Quoted 1 line

I'm definitely open to ideas...

The laziest thing would be to perform GFP_ATOMIC allocation, and if that fails, oops, too bad, no stack trace for you (but, generally speaking, no big deal). Advantages are clear, though, right? Single pointer in task_struct, which most of the time will be NULL, so no unnecessary overheads.

GFP_ATOMIC is limited, I don't think we want the unwinder to trigger OOM.

Quoted 5 lines

It's the last point that's important to make usability so much simpler, avoiding unnecessary custom timeouts and stuff like that. Regardless whether stack trace capture is success or not, user is guaranteed to get a "notification" about the outcome.

Quoted 8 lines

Hope this helps.

But basically, if I I called unwind_user_deferred(), I expect to get some callback, guaranteed, with the result or failure. The only thing that's not guaranteed (and which makes timeouts bad) is *when* this will happen. Because stack trace capture can be arbitrarily delayed and stuff. That's fine, but that also shows why timeout is tricky and necessarily fragile.

That sounds reasonable. In the OOM error case I can just pass a small (stack allocated) one-entry trace with only regs->ip.

Signature · 1 line
Josh
#83 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 16 lines · Josh Poimboeuf
Quoted 14 lines · Andrii Nakryiko
Quoted 13 lines

Problem is, the unwinder doesn't know in advance which tasks will be unwound.

Its first clue is unwind_user_register(), would it make sense for the caller to clarify whether all tasks need to be unwound or only a specific subset?

Its second clue is unwind_user_deferred(), which is called for the task itself. But by then it's too late because it needs to access the per-task data from (potentially) irq context so it can't do a lazy allocation.

I'm definitely open to ideas...

Quoted 9 lines
Quoted 5 lines

The laziest thing would be to perform GFP_ATOMIC allocation, and if that fails, oops, too bad, no stack trace for you (but, generally speaking, no big deal). Advantages are clear, though, right? Single pointer in task_struct, which most of the time will be NULL, so no unnecessary overheads.

GFP_ATOMIC is limited, I don't think we want the unwinder to trigger OOM.

So all task_structs on the system using 104 bytes more, *permanently* and *unconditionally*, is not a concern, but lazy GFP_ATOMIC allocation when you actually need it is?

Quoted 14 lines
Quoted 5 lines

It's the last point that's important to make usability so much simpler, avoiding unnecessary custom timeouts and stuff like that. Regardless whether stack trace capture is success or not, user is guaranteed to get a "notification" about the outcome.

Quoted 8 lines

Hope this helps.

But basically, if I I called unwind_user_deferred(), I expect to get some callback, guaranteed, with the result or failure. The only thing that's not guaranteed (and which makes timeouts bad) is *when* this will happen. Because stack trace capture can be arbitrarily delayed and stuff. That's fine, but that also shows why timeout is tricky and necessarily fragile.

Quoted 3 lines

That sounds reasonable. In the OOM error case I can just pass a small (stack allocated) one-entry trace with only regs->ip.

SGTM

Quoted 3 lines

--
Josh

author maintainer
#84 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 1 line · Andrii Nakryiko

So all task_structs on the system using 104 bytes more, *permanently*

Either way it's permanent, we don't know when to free it until the task struct is freed...

Quoted 1 line

and *unconditionally*, is not a concern

Of course it's a concern, that's why we're looking for something better...

Quoted 1 line

but lazy GFP_ATOMIC allocation when you actually need it is?

We don't want to dip into the GFP_ATOMIC emergency reserves, those are kept for more important things.

Actually, I think I can just use GFP_NOWAIT here.

Signature · 1 line
Josh
#85 lore raw

Re: [PATCH v3 11/19] unwind: Add deferred user space unwinding API

Quoted 7 lines · Josh Poimboeuf
Quoted 1 line · Andrii Nakryiko

So all task_structs on the system using 104 bytes more, *permanently*

Either way it's permanent, we don't know when to free it until the task struct is freed...

I'm not sure if we are arguing for the sake of arguing at this point :) Yes, for *those tasks* for which we at least once requested stack trace, that memory will stay, sure. But there are normally tons of threads that are almost completely idle and/or use so little CPU, that they won't ever be caught in the profiler, so their stack trace will never be requested.

Sure, you can come up with a use case where you'll just go over each task and ask for stack trace for each of them, but that's not a common case.

So, sorry, but no, I don't agree that these are equivalent things. Lazy memory allocation is a must, IMO.

Quoted 11 lines
Quoted 1 line

and *unconditionally*, is not a concern

Of course it's a concern, that's why we're looking for something better...

Quoted 1 line

but lazy GFP_ATOMIC allocation when you actually need it is?

We don't want to dip into the GFP_ATOMIC emergency reserves, those are kept for more important things.

Actually, I think I can just use GFP_NOWAIT here.

Whatever semantics works for being called from NMI (even if it can fail).

Quoted 3 lines

--
Josh

author maintainer
#86 lore raw

PATCH v3 12/19 perf: Remove get_perf_callchain() 'init_nr' argument

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

---
 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 fb908843f209..1e956ff9acd3 100644
@@ -1568,7 +1568,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 3615c06b7dfa..ec3a57a5fba1 100644
@@ -314,7 +314,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))
@@ -451,7 +451,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,
 					   crosstask, false);
 
 	if (unlikely(!trace) || trace->nr < skip) {
kernel/events/callchain.c +6 -6
index 8a47e52a454f..83834203e144 100644
@@ -216,7 +216,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entr
 }
 
 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;
@@ -227,11 +227,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 df27d08a7232..1654d6e7c148 100644
@@ -7800,7 +7800,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.47.0
author maintainer
#87 lore raw

PATCH v3 13/19 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.

---
 include/linux/perf_event.h |  2 +-
 kernel/bpf/stackmap.c      | 12 ++++--------
 kernel/events/callchain.c  |  6 +-----
 kernel/events/core.c       |  9 +++++----
 4 files changed, 11 insertions(+), 18 deletions(-)
include/linux/perf_event.h +1 -1
index 1e956ff9acd3..788f6971d32d 100644
@@ -1569,7 +1569,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 +4 -8
index ec3a57a5fba1..ee9701337912 100644
@@ -314,8 +314,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 */
@@ -430,10 +429,8 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
 	if (task && user && !user_mode(regs))
 		goto err_fault;
 
-	/* get_perf_callchain does not support crosstask user stack walking
-	 * but returns an empty stack instead of NULL.
-	 */
-	if (crosstask && user) {
+	/* get_perf_callchain() does not support crosstask stack walking */
+	if (crosstask) {
 		err = -EOPNOTSUPP;
 		goto clear;
 	}
@@ -451,8 +448,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, kernel, user, max_depth,
-					   crosstask, false);
+		trace = get_perf_callchain(regs, kernel, user, max_depth,false);
 
 	if (unlikely(!trace) || trace->nr < skip) {
 		if (may_fault)
kernel/events/callchain.c +1 -5
index 83834203e144..655fb25a725b 100644
@@ -217,7 +217,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entr
 
 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;
@@ -248,9 +248,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);
 
@@ -260,7 +257,6 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user,
 		}
 	}
 
-exit_put:
 	put_callchain_entry(rctx);
 
 	return entry;
kernel/events/core.c +5 -4
index 1654d6e7c148..ebf143aa427b 100644
@@ -7792,16 +7792,17 @@ 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;
 
 	if (!kernel && !user)
 		return &__empty_callchain;
 
-	callchain = get_perf_callchain(regs, kernel, user,
-				       max_stack, crosstask, true);
+	/* Disallow cross-task callchains. */
+	if (event->ctx->task && event->ctx->task != current)
+		return &__empty_callchain;
+
+	callchain = get_perf_callchain(regs, kernel, user, max_stack, true);
 	return callchain ?: &__empty_callchain;
 }
 
Signature · 1 line
2.47.0
author maintainer
#88 lore raw

PATCH v3 14/19 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 | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)
kernel/events/callchain.c +9 -11
index 655fb25a725b..2278402b7ac9 100644
@@ -241,22 +241,20 @@ 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);
 
-			start_entry_idx = entry->nr;
-			perf_callchain_user(&ctx, regs);
-			fixup_uretprobe_trampoline_entries(entry, start_entry_idx);
-		}
+		start_entry_idx = entry->nr;
+		perf_callchain_user(&ctx, regs);
+		fixup_uretprobe_trampoline_entries(entry, start_entry_idx);
 	}
 
+exit_put:
 	put_callchain_entry(rctx);
 
 	return entry;
Signature · 1 line
2.47.0
author maintainer
#89 lore raw

PATCH v3 15/19 perf: Add 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            | 10 ++++-
 include/uapi/linux/perf_event.h       | 22 +++++++++-
 kernel/bpf/stackmap.c                 |  6 +--
 kernel/events/callchain.c             | 11 ++++-
 kernel/events/core.c                  | 63 ++++++++++++++++++++++++++-
 tools/include/uapi/linux/perf_event.h | 22 +++++++++-
 7 files changed, 129 insertions(+), 8 deletions(-)
arch/Kconfig +3 -0
index e769c39dd221..33449485eafd 100644
@@ -446,6 +446,9 @@ config HAVE_UNWIND_USER_SFRAME
 	bool
 	select UNWIND_USER
 
+config HAVE_PERF_CALLCHAIN_DEFERRED
+	bool
+
 config HAVE_PERF_REGS
 	bool
 	help
include/linux/perf_event.h +9 -1
index 788f6971d32d..2193b3d16820 100644
@@ -808,9 +808,11 @@ struct perf_event {
 	unsigned long			pending_addr;	/* SIGTRAP */
 	struct irq_work			pending_irq;
 	struct irq_work			pending_disable_irq;
+	struct irq_work			pending_unwind_irq;
 	struct callback_head		pending_task;
 	unsigned int			pending_work;
 	struct rcuwait			pending_work_wait;
+	unsigned int			pending_unwind;
 
 	atomic_t			event_limit;
 
@@ -1569,12 +1571,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 +21 -1
index 4842c36fdf80..6d0524b7d082 100644
@@ -460,7 +460,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 PERF_RECORD_CALLCHAIN_DEFERRED records */
+				__reserved_1   : 25;
 
 	union {
 		__u32		wakeup_events;	  /* wakeup every n events */
@@ -1217,6 +1218,24 @@ enum perf_event_type {
 	 */
 	PERF_RECORD_AUX_OUTPUT_HW_ID		= 21,
 
+	/*
+	 * This user callchain capture was deferred until shortly before
+	 * returning to user space.  Previous samples would have kernel
+	 * callchains only and they need to be stitched with this to make full
+	 * callchains.
+	 *
+	 * TODO: do PERF_SAMPLE_{REGS,STACK}_USER also need deferral?
+	 *
+	 * struct {
+	 *	struct perf_event_header	header;
+	 *	u64				ctx_cookie;
+	 *	u64				nr;
+	 *	u64				ips[nr];
+	 *	struct sample_id		sample_id;
+	 * };
+	 */
+	PERF_RECORD_CALLCHAIN_DEFERRED		= 22,
+
 	PERF_RECORD_MAX,			/* non-ABI */
 };
 
@@ -1247,6 +1266,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 +3 -3
index ee9701337912..f073ebaf9c30 100644
@@ -314,8 +314,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;
@@ -448,7 +447,8 @@ 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, kernel, user, max_depth,false);
+		trace = get_perf_callchain(regs, kernel, user, max_depth,
+					   false, false);
 
 	if (unlikely(!trace) || trace->nr < skip) {
 		if (may_fault)
kernel/events/callchain.c +10 -1
index 2278402b7ac9..eeb15ba0137f 100644
@@ -217,7 +217,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entr
 
 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;
@@ -246,6 +246,15 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user,
 			regs = task_pt_regs(current);
 		}
 
+		if (defer_user) {
+			/*
+			 * Foretell the coming of PERF_RECORD_CALLCHAIN_DEFERRED
+			 * which can be stitched to this one.
+			 */
+			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 +62 -1

104 lines not on this page · raw message

tools/include/uapi/linux/perf_event.h +21 -1
index 4842c36fdf80..6d0524b7d082 100644
@@ -460,7 +460,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 PERF_RECORD_CALLCHAIN_DEFERRED records */
+				__reserved_1   : 25;
 
 	union {
 		__u32		wakeup_events;	  /* wakeup every n events */
@@ -1217,6 +1218,24 @@ enum perf_event_type {
 	 */
 	PERF_RECORD_AUX_OUTPUT_HW_ID		= 21,
 
+	/*
+	 * This user callchain capture was deferred until shortly before
+	 * returning to user space.  Previous samples would have kernel
+	 * callchains only and they need to be stitched with this to make full
+	 * callchains.
+	 *
+	 * TODO: do PERF_SAMPLE_{REGS,STACK}_USER also need deferral?
+	 *
+	 * struct {
+	 *	struct perf_event_header	header;
+	 *	u64				ctx_cookie;
+	 *	u64				nr;
+	 *	u64				ips[nr];
+	 *	struct sample_id		sample_id;
+	 * };
+	 */
+	PERF_RECORD_CALLCHAIN_DEFERRED		= 22,
+
 	PERF_RECORD_MAX,			/* non-ABI */
 };
 
@@ -1247,6 +1266,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,
Signature · 1 line
2.47.0
maintainer
#90 lore raw

Re: [PATCH v3 15/19] perf: Add deferred user callchains

Quoted 18 lines · Josh Poimboeuf
kernel/events/callchain.c +2 -1
index 2278402b7ac9..eeb15ba0137f 100644
@@ -217,7 +217,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entr
 
 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;
@@ -246,6 +246,15 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user,
 			regs = task_pt_regs(current);
 		}
 
+		if (defer_user) {

kernel/events/callchain.c get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, · line 259

Quoted 10 lines
kernel/events/callchain.c +8 -0
+			/*
+			 * Foretell the coming of PERF_RECORD_CALLCHAIN_DEFERRED
+			 * which can be stitched to this one.
+			 */
+			perf_callchain_store_context(&ctx, PERF_CONTEXT_USER_DEFERRED);
+			goto exit_put;
+		}
+
 		if (add_mark)
 			perf_callchain_store_context(&ctx, PERF_CONTEXT_USER);

Given that the whole deferred thing doesn't handle COMPAT, this will break thing, no?

#91 lore raw

Re: [PATCH v3 15/19] perf: Add deferred user callchains

In reply to Josh Poimboeuf

On 28.10.2024 22:48, Josh Poimboeuf wrote:
...

Quoted 4 lines
kernel/events/core.c +0 -0
index ebf143aa427b..bf97b2fa8a9c 100644

...

Quoted 7 lines
kernel/events/core.c +3 -0
@@ -6955,6 +6958,53 @@ static void perf_pending_irq(struct irq_work *entry)
  		perf_swevent_put_recursion_context(rctx);
  }
 
+static void perf_pending_unwind_irq(struct irq_work *entry)
+{
+	struct perf_event *event = container_of(entry, struct perf_event, pending_unwind_irq);

kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6973

Quoted 10 lines
kernel/events/core.c +10 -0
+
+	if (event->pending_unwind) {
+		unwind_user_deferred(&perf_unwind_callback_cb, NULL, event);
+		event->pending_unwind = 0;
+	}
+}
+
+struct perf_callchain_deferred_event {
+	struct perf_event_header	header;
+	u64				ctx_cookie;

This introduces ctx_cookie in the struct used to produce deferred events but misses to do so in the struct used to consume them. This causes the ctx_cookie value to erroneously get interpreted as nr (number of IPs) in perf:

Core was generated by `perf record -F 99 --call-graph fp /opt/binutils-sframe2/bin/objdump --sframe /opt/binutils-sframe2/bin/objdump'. Program terminated with signal SIGSEGV, Segmentation fault.
#0 __GI_memcpy () at ../sysdeps/s390/memcpy-z900.S:209
209 mvc 0(256,%r1),0(%r3)
[Current thread is 1 (Thread 0x3ff8bb5fe80 (LWP 16554))]
(gdb) bt
#0 __GI_memcpy () at ../sysdeps/s390/memcpy-z900.S:209
#1 0x00000000012ad0ca in sample__merge_deferred_callchain (sample_orig=0x3ffd2ff53c8, sample_callchain=0x3ffd2ff5b18) at util/callchain.c:1853
...
(gdb) p/x sample_callchain->callchain->nr
$2 = 0x489cb

With debug output from perf_event_callchain_deferred() (see below):

DEBUG: perf_event_callchain_deferred: ctx_cookie=0x00000000000489cb, nr=2

Quoted 12 lines
kernel/events/core.c +12 -0
+	u64				nr;
+	u64				ips[];
+};
+
+static void perf_event_callchain_deferred(struct unwind_stacktrace *trace,
+					  u64 ctx_cookie, void *_data)
+{
+	struct perf_callchain_deferred_event deferred_event;
+	u64 callchain_context = PERF_CONTEXT_USER;
+	struct perf_output_handle handle;
+	struct perf_event *event = _data;
+	struct perf_sample_data data;

kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 6995

Quoted 10 lines
kernel/events/core.c +10 -0
+	u64 nr = trace->nr + 1 /* callchain_context */;
+
+	deferred_event.header.type = PERF_RECORD_CALLCHAIN_DEFERRED;
+	deferred_event.header.misc = PERF_RECORD_MISC_USER;
+	deferred_event.header.size = sizeof(deferred_event) + (nr * sizeof(u64));
+
+	deferred_event.ctx_cookie = ctx_cookie;
+	deferred_event.nr = nr;
+
+	perf_event_header__init_id(&deferred_event.header, &data, event);
	pr_info_ratelimited("DEBUG: perf_event_callchain_deferred: ctx_cookie=0x%016llx, nr=%llu\n",
		deferred_event.ctx_cookie, deferred_event.nr);
Quoted 5 lines
kernel/events/core.c +5 -0
+
+	if (perf_output_begin(&handle, &data, event, deferred_event.header.size))
+		return;
+
+	perf_output_put(&handle, deferred_event);

kernel/events/core.c static void perf_pending_irq(struct irq_work *entry) · line 7010

Quoted 10 lines
kernel/events/core.c +7 -0
+	perf_output_put(&handle, callchain_context);
+	perf_output_copy(&handle, trace->entries, trace->nr * sizeof(u64));
+	perf_event__output_id_sample(event, &handle, &data);
+
+	perf_output_end(&handle);
+}
+
  static void perf_pending_task(struct callback_head *head)
  {
  	struct perf_event *event = container_of(head, struct perf_event, pending_task);

...

Regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303) and z/VSE Support
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
author maintainer
#92 lore raw

PATCH v3 16/19 perf tools: Minimal CALLCHAIN_DEFERRED support

In reply to Josh Poimboeuf

From: Namhyung Kim <[email protected]>

Add a new event type for deferred callchains and a new callback for the struct perf_tool. For now it doesn't actually handle the deferred callchains but it just marks the sample if it has the PERF_CONTEXT_ USER_DEFFERED in the callchain array.

At least, perf report can dump the raw data with this change. Actually this requires the next commit to enable attr.defer_callchain, but if you already have a data file, it'll show the following result.

  $ perf report -D
  ...
  [email protected] [0x40]: event: 22
  .
  . ... raw event: size 64 bytes
  .  0000:  16 00 00 00 02 00 40 00 02 00 00 00 00 00 00 00  ......@.........
  .  0010:  00 fe ff ff ff ff ff ff 4b d3 3f 25 45 7f 00 00  ........K.?%E...
  .  0020:  21 03 00 00 21 03 00 00 43 02 12 ab 05 00 00 00  !...!...C.......
  .  0030:  00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00  ................

0 24344920643 0x5fe0 [0x40]: PERF_RECORD_CALLCHAIN_DEFERRED(IP, 0x2): 801/801: 0 ... FP chain: nr:2
..... 0: fffffffffffffe00
..... 1: 00007f45253fd34b
: unhandled!

---
 tools/lib/perf/include/perf/event.h       |  7 +++++++
 tools/perf/util/event.c                   |  1 +
 tools/perf/util/evsel.c                   | 15 +++++++++++++++
 tools/perf/util/machine.c                 |  1 +
 tools/perf/util/perf_event_attr_fprintf.c |  1 +
 tools/perf/util/sample.h                  |  3 ++-
 tools/perf/util/session.c                 | 17 +++++++++++++++++
 tools/perf/util/tool.c                    |  1 +
 tools/perf/util/tool.h                    |  3 ++-
 9 files changed, 47 insertions(+), 2 deletions(-)
tools/lib/perf/include/perf/event.h +7 -0
index 37bb7771d914..f643a6a2b9fc 100644
@@ -151,6 +151,12 @@ struct perf_record_switch {
 	__u32			 next_prev_tid;
 };
 
+struct perf_record_callchain_deferred {
+	struct perf_event_header header;
+	__u64			 nr;
+	__u64			 ips[];
+};
+
 struct perf_record_header_attr {
 	struct perf_event_header header;
 	struct perf_event_attr	 attr;
@@ -494,6 +500,7 @@ union perf_event {
 	struct perf_record_read			read;
 	struct perf_record_throttle		throttle;
 	struct perf_record_sample		sample;
+	struct perf_record_callchain_deferred	callchain_deferred;
 	struct perf_record_bpf_event		bpf;
 	struct perf_record_ksymbol		ksymbol;
 	struct perf_record_text_poke_event	text_poke;
tools/perf/util/event.c +1 -0
index aac96d5d1917..8cdec373db44 100644
@@ -58,6 +58,7 @@ static const char *perf_event__names[] = {
 	[PERF_RECORD_CGROUP]			= "CGROUP",
 	[PERF_RECORD_TEXT_POKE]			= "TEXT_POKE",
 	[PERF_RECORD_AUX_OUTPUT_HW_ID]		= "AUX_OUTPUT_HW_ID",
+	[PERF_RECORD_CALLCHAIN_DEFERRED]	= "CALLCHAIN_DEFERRED",
 	[PERF_RECORD_HEADER_ATTR]		= "ATTR",
 	[PERF_RECORD_HEADER_EVENT_TYPE]		= "EVENT_TYPE",
 	[PERF_RECORD_HEADER_TRACING_DATA]	= "TRACING_DATA",
tools/perf/util/evsel.c +15 -0
index dbf9c8cee3c5..701092d6b1b6 100644
@@ -2676,6 +2676,18 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
 	data->data_src = PERF_MEM_DATA_SRC_NONE;
 	data->vcpu = -1;
 
+	if (event->header.type == PERF_RECORD_CALLCHAIN_DEFERRED) {
+		const u64 max_callchain_nr = UINT64_MAX / sizeof(u64);
+
+		data->callchain = (struct ip_callchain *)&event->callchain_deferred.nr;
+		if (data->callchain->nr > max_callchain_nr)
+			return -EFAULT;
+
+		if (evsel->core.attr.sample_id_all)
+			perf_evsel__parse_id_sample(evsel, event, data);
+		return 0;
+	}
+
 	if (event->header.type != PERF_RECORD_SAMPLE) {
 		if (!evsel->core.attr.sample_id_all)
 			return 0;
@@ -2806,6 +2818,9 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
 		if (data->callchain->nr > max_callchain_nr)
 			return -EFAULT;
 		sz = data->callchain->nr * sizeof(u64);
+		if (evsel->core.attr.defer_callchain && data->callchain->nr >= 1 &&
+		    data->callchain->ips[data->callchain->nr - 1] == PERF_CONTEXT_USER_DEFERRED)
+			data->deferred_callchain = true;
 		OVERFLOW_CHECK(array, sz, max_size);
 		array = (void *)array + sz;
 	}
tools/perf/util/machine.c +1 -0
index fad227b625d1..f367577c91ff 100644
@@ -2085,6 +2085,7 @@ static int add_callchain_ip(struct thread *thread,
 				*cpumode = PERF_RECORD_MISC_KERNEL;
 				break;
 			case PERF_CONTEXT_USER:
+			case PERF_CONTEXT_USER_DEFERRED:
 				*cpumode = PERF_RECORD_MISC_USER;
 				break;
 			default:
tools/perf/util/perf_event_attr_fprintf.c +1 -0
index 59fbbba79697..113845b35110 100644
@@ -321,6 +321,7 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr,
 	PRINT_ATTRf(inherit_thread, p_unsigned);
 	PRINT_ATTRf(remove_on_exec, p_unsigned);
 	PRINT_ATTRf(sigtrap, p_unsigned);
+	PRINT_ATTRf(defer_callchain, p_unsigned);
 
 	PRINT_ATTRn("{ wakeup_events, wakeup_watermark }", wakeup_events, p_unsigned, false);
 	PRINT_ATTRf(bp_type, p_unsigned);
tools/perf/util/sample.h +2 -1
index 70b2c3135555..010659dc80f8 100644
@@ -108,7 +108,8 @@ struct perf_sample {
 		u16 p_stage_cyc;
 		u16 retire_lat;
 	};
-	bool no_hw_idx;		/* No hw_idx collected in branch_stack */
+	bool no_hw_idx;			/* No hw_idx collected in branch_stack */
+	bool deferred_callchain;	/* Has deferred user callchains */
 	char insn[MAX_INSN];
 	void *raw_data;
 	struct ip_callchain *callchain;
tools/perf/util/session.c +17 -0
index dbaf07bf6c5f..1248a0317a2f 100644
@@ -714,6 +714,7 @@ static perf_event__swap_op perf_event__swap_ops[] = {
 	[PERF_RECORD_CGROUP]		  = perf_event__cgroup_swap,
 	[PERF_RECORD_TEXT_POKE]		  = perf_event__text_poke_swap,
 	[PERF_RECORD_AUX_OUTPUT_HW_ID]	  = perf_event__all64_swap,
+	[PERF_RECORD_CALLCHAIN_DEFERRED]  = perf_event__all64_swap,
 	[PERF_RECORD_HEADER_ATTR]	  = perf_event__hdr_attr_swap,
 	[PERF_RECORD_HEADER_EVENT_TYPE]	  = perf_event__event_type_swap,
 	[PERF_RECORD_HEADER_TRACING_DATA] = perf_event__tracing_data_swap,
@@ -1107,6 +1108,19 @@ static void dump_sample(struct evsel *evsel, union perf_event *event,
 		sample_read__printf(sample, evsel->core.attr.read_format);
 }
 
+static void dump_deferred_callchain(struct evsel *evsel, union perf_event *event,
+				    struct perf_sample *sample)
+{
+	if (!dump_trace)
+		return;
+
+	printf("(IP, 0x%x): %d/%d: %#" PRIx64 "\n",
+	       event->header.misc, sample->pid, sample->tid, sample->ip);
+
+	if (evsel__has_callchain(evsel))
+		callchain__printf(evsel, sample);
+}
+
 static void dump_read(struct evsel *evsel, union perf_event *event)
 {
 	struct perf_record_read *read_event = &event->read;
@@ -1327,6 +1341,9 @@ static int machines__deliver_event(struct machines *machines,
 		return tool->text_poke(tool, event, sample, machine);
 	case PERF_RECORD_AUX_OUTPUT_HW_ID:
 		return tool->aux_output_hw_id(tool, event, sample, machine);
+	case PERF_RECORD_CALLCHAIN_DEFERRED:
+		dump_deferred_callchain(evsel, event, sample);
+		return tool->callchain_deferred(tool, event, sample, evsel, machine);
 	default:
 		++evlist->stats.nr_unknown_events;
 		return -1;
tools/perf/util/tool.c +1 -0
index 3b7f390f26eb..e78f16de912e 100644
@@ -259,6 +259,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events)
 	tool->read = process_event_sample_stub;
 	tool->throttle = process_event_stub;
 	tool->unthrottle = process_event_stub;
+	tool->callchain_deferred = process_event_sample_stub;
 	tool->attr = process_event_synth_attr_stub;
 	tool->event_update = process_event_synth_event_update_stub;
 	tool->tracing_data = process_event_synth_tracing_data_stub;
tools/perf/util/tool.h +2 -1
index db1c7642b0d1..9987bbde6d5e 100644
@@ -42,7 +42,8 @@ enum show_feature_header {
 
 struct perf_tool {
 	event_sample	sample,
-			read;
+			read,
+			callchain_deferred;
 	event_op	mmap,
 			mmap2,
 			comm,
Signature · 1 line
2.47.0
author maintainer
#93 lore raw

PATCH v3 17/19 perf record: Enable defer_callchain for user callchains

In reply to Josh Poimboeuf

From: Namhyung Kim <[email protected]>

And add the missing feature detection logic to clear the flag on old kernels.

  $ perf record -g -vv true
  ...
  ------------------------------------------------------------
  perf_event_attr:
    type                             0 (PERF_TYPE_HARDWARE)
    size                             136
    config                           0 (PERF_COUNT_HW_CPU_CYCLES)
    { sample_period, sample_freq }   4000
    sample_type                      IP|TID|TIME|CALLCHAIN|PERIOD
    read_format                      ID|LOST
    disabled                         1
    inherit                          1
    mmap                             1
    comm                             1
    freq                             1
    enable_on_exec                   1
    task                             1
    sample_id_all                    1
    mmap2                            1
    comm_exec                        1
    ksymbol                          1
    bpf_event                        1
    defer_callchain                  1
  ------------------------------------------------------------
  sys_perf_event_open: pid 162755  cpu 0  group_fd -1  flags 0x8
  sys_perf_event_open failed, error -22
  switching off deferred callchain support
---
 tools/perf/util/evsel.c | 17 ++++++++++++++++-
 tools/perf/util/evsel.h |  1 +
 2 files changed, 17 insertions(+), 1 deletion(-)
tools/perf/util/evsel.c +16 -1
index 701092d6b1b6..ad89644b32f2 100644
@@ -912,6 +912,14 @@ static void __evsel__config_callchain(struct evsel *evsel, struct record_opts *o
 		}
 	}
 
+	if (param->record_mode == CALLCHAIN_FP && !attr->exclude_callchain_user) {
+		/*
+		 * Enable deferred callchains optimistically.  It'll be switched
+		 * off later if the kernel doesn't support it.
+		 */
+		attr->defer_callchain = 1;
+	}
+
 	if (function) {
 		pr_info("Disabling user space callchains for function trace event.\n");
 		attr->exclude_callchain_user = 1;
@@ -2089,6 +2097,8 @@ static int __evsel__prepare_open(struct evsel *evsel, struct perf_cpu_map *cpus,
 
 static void evsel__disable_missing_features(struct evsel *evsel)
 {
+	if (perf_missing_features.defer_callchain)
+		evsel->core.attr.defer_callchain = 0;
 	if (perf_missing_features.branch_counters)
 		evsel->core.attr.branch_sample_type &= ~PERF_SAMPLE_BRANCH_COUNTERS;
 	if (perf_missing_features.read_lost)
@@ -2144,7 +2154,12 @@ bool evsel__detect_missing_features(struct evsel *evsel)
 	 * Must probe features in the order they were added to the
 	 * perf_event_attr interface.
 	 */
-	if (!perf_missing_features.branch_counters &&
+	if (!perf_missing_features.defer_callchain &&
+	    evsel->core.attr.defer_callchain) {
+		perf_missing_features.defer_callchain = true;
+		pr_debug2("switching off deferred callchain support\n");
+		return true;
+	} else if (!perf_missing_features.branch_counters &&
 	    (evsel->core.attr.branch_sample_type & PERF_SAMPLE_BRANCH_COUNTERS)) {
 		perf_missing_features.branch_counters = true;
 		pr_debug2("switching off branch counters support\n");
tools/perf/util/evsel.h +1 -0
index 15e745a9a798..f0a1e1d78942 100644
@@ -221,6 +221,7 @@ struct perf_missing_features {
 	bool weight_struct;
 	bool read_lost;
 	bool branch_counters;
+	bool defer_callchain;
 };
 
 extern struct perf_missing_features perf_missing_features;
Signature · 1 line
2.47.0
author maintainer
#94 lore raw

PATCH v3 18/19 perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED

In reply to Josh Poimboeuf

From: Namhyung Kim <[email protected]>

Handle the deferred callchains in the script output.

  $ perf script
  perf     801 [000]    18.031793:          1 cycles:P:
          ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms])
          ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms])
          ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms])
          ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms])
          ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms])
          ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms])
          ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms])
          ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms])
          ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms])
          ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms])
          ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms])
          ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms])
          ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])

perf 801 [000] 18.031814: DEFERRED CALLCHAIN

              7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)
---
 tools/perf/builtin-script.c | 89 +++++++++++++++++++++++++++++++++++++
 1 file changed, 89 insertions(+)
tools/perf/builtin-script.c +89 -0
index a644787fa9e1..311580e25f5b 100644
@@ -2540,6 +2540,93 @@ static int process_sample_event(const struct perf_tool *tool,
 	return ret;
 }
 
+static int process_deferred_sample_event(const struct perf_tool *tool,
+					 union perf_event *event,
+					 struct perf_sample *sample,
+					 struct evsel *evsel,
+					 struct machine *machine)
+{
+	struct perf_script *scr = container_of(tool, struct perf_script, tool);
+	struct perf_event_attr *attr = &evsel->core.attr;
+	struct evsel_script *es = evsel->priv;
+	unsigned int type = output_type(attr->type);
+	struct addr_location al;
+	FILE *fp = es->fp;
+	int ret = 0;
+
+	if (output[type].fields == 0)
+		return 0;
+
+	/* Set thread to NULL to indicate addr_al and al are not initialized */
+	addr_location__init(&al);
+
+	if (perf_time__ranges_skip_sample(scr->ptime_range, scr->range_num,
+					  sample->time)) {
+		goto out_put;
+	}
+
+	if (debug_mode) {
+		if (sample->time < last_timestamp) {
+			pr_err("Samples misordered, previous: %" PRIu64
+				" this: %" PRIu64 "\n", last_timestamp,
+				sample->time);
+			nr_unordered++;
+		}
+		last_timestamp = sample->time;
+		goto out_put;
+	}
+
+	if (filter_cpu(sample))
+		goto out_put;
+
+	if (machine__resolve(machine, &al, sample) < 0) {
+		pr_err("problem processing %d event, skipping it.\n",
+		       event->header.type);
+		ret = -1;
+		goto out_put;
+	}
+
+	if (al.filtered)
+		goto out_put;
+
+	if (!show_event(sample, evsel, al.thread, &al, NULL))
+		goto out_put;
+
+	if (evswitch__discard(&scr->evswitch, evsel))
+		goto out_put;
+
+	perf_sample__fprintf_start(scr, sample, al.thread, evsel,
+				   PERF_RECORD_CALLCHAIN_DEFERRED, fp);
+	fprintf(fp, "DEFERRED CALLCHAIN");
+
+	if (PRINT_FIELD(IP)) {
+		struct callchain_cursor *cursor = NULL;
+
+		if (symbol_conf.use_callchain && sample->callchain) {
+			cursor = get_tls_callchain_cursor();
+			if (thread__resolve_callchain(al.thread, cursor, evsel,
+						      sample, NULL, NULL,
+						      scripting_max_stack)) {
+				pr_info("cannot resolve deferred callchains\n");
+				cursor = NULL;
+			}
+		}
+
+		fputc(cursor ? '\n' : ' ', fp);
+		sample__fprintf_sym(sample, &al, 0, output[type].print_ip_opts,
+				    cursor, symbol_conf.bt_stop_list, fp);
+	}
+
+	fprintf(fp, "\n");
+
+	if (verbose > 0)
+		fflush(fp);
+
+out_put:
+	addr_location__exit(&al);
+	return ret;
+}
+
 // Used when scr->per_event_dump is not set
 static struct evsel_script es_stdout;
 
@@ -4325,6 +4412,7 @@ int cmd_script(int argc, const char **argv)
 
 	perf_tool__init(&script.tool, !unsorted_dump);
 	script.tool.sample		 = process_sample_event;
+	script.tool.callchain_deferred	 = process_deferred_sample_event;
 	script.tool.mmap		 = perf_event__process_mmap;
 	script.tool.mmap2		 = perf_event__process_mmap2;
 	script.tool.comm		 = perf_event__process_comm;
@@ -4351,6 +4439,7 @@ int cmd_script(int argc, const char **argv)
 	script.tool.throttle		 = process_throttle_event;
 	script.tool.unthrottle		 = process_throttle_event;
 	script.tool.ordering_requires_timestamps = true;
+	script.tool.merge_deferred_callchains = false;
 	session = perf_session__new(&data, &script.tool);
 	if (IS_ERR(session))
 		return PTR_ERR(session);
Signature · 1 line
2.47.0
author maintainer
#95 lore raw

PATCH v3 19/19 perf tools: Merge deferred user callchains

In reply to Josh Poimboeuf

From: Namhyung Kim <[email protected]>

Save samples with deferred callchains in a separate list and deliver them after merging the user callchains. If users don't want to merge they can set tool->merge_deferred_callchains to false to prevent the behavior.

With previous result, now perf script will show the merged callchains.

  $ perf script
  perf     801 [000]    18.031793:          1 cycles:P:
          ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms])
          ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms])
          ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms])
          ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms])
          ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms])
          ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms])
          ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms])
          ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms])
          ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms])
          ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms])
          ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms])
          ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms])
          ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])
              7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)
  ...

The old output can be get using --no-merge-callchain option. Also perf report can get the user callchain entry at the end.

  $ perf report --no-children --percent-limit=0 --stdio -q -S __intel_pmu_enable_all.isra.0
  # symbol: __intel_pmu_enable_all.isra.0
       0.00%  perf     [kernel.kallsyms]
              |
              ---__intel_pmu_enable_all.isra.0
                 perf_ctx_enable
                 event_function
                 remote_function
                 generic_exec_single
                 smp_call_function_single
                 event_function_call
                 perf_event_for_each_child
                 _perf_ioctl
                 perf_ioctl
                 __x64_sys_ioctl
                 do_syscall_64
                 entry_SYSCALL_64
                 __GI___ioctl
---
 tools/perf/Documentation/perf-script.txt |  5 ++
 tools/perf/builtin-script.c              |  5 +-
 tools/perf/util/callchain.c              | 24 +++++++++
 tools/perf/util/callchain.h              |  3 ++
 tools/perf/util/evlist.c                 |  1 +
 tools/perf/util/evlist.h                 |  1 +
 tools/perf/util/session.c                | 63 +++++++++++++++++++++++-
 tools/perf/util/tool.c                   |  1 +
 tools/perf/util/tool.h                   |  1 +
 9 files changed, 102 insertions(+), 2 deletions(-)
tools/perf/Documentation/perf-script.txt +5 -0
index b72866ef270b..69f018b3d199 100644
@@ -518,6 +518,11 @@ include::itrace.txt[]
 	The known limitations include exception handing such as
 	setjmp/longjmp will have calls/returns not match.
 
+--merge-callchains::
+	Enable merging deferred user callchains if available.  This is the
+	default behavior.  If you want to see separate CALLCHAIN_DEFERRED
+	records for some reason, use --no-merge-callchains explicitly.
+
 :GMEXAMPLECMD: script
 :GMEXAMPLESUBCMD:
 include::guest-files.txt[]
tools/perf/builtin-script.c +4 -1
index 311580e25f5b..e3acf4979c36 100644
@@ -4031,6 +4031,7 @@ int cmd_script(int argc, const char **argv)
 	bool header_only = false;
 	bool script_started = false;
 	bool unsorted_dump = false;
+	bool merge_deferred_callchains = true;
 	char *rec_script_path = NULL;
 	char *rep_script_path = NULL;
 	struct perf_session *session;
@@ -4184,6 +4185,8 @@ int cmd_script(int argc, const char **argv)
 		    "Guest code can be found in hypervisor process"),
 	OPT_BOOLEAN('\0', "stitch-lbr", &script.stitch_lbr,
 		    "Enable LBR callgraph stitching approach"),
+	OPT_BOOLEAN('\0', "merge-callchains", &merge_deferred_callchains,
+		    "Enable merge deferred user callchains"),
 	OPTS_EVSWITCH(&script.evswitch),
 	OPT_END()
 	};
@@ -4439,7 +4442,7 @@ int cmd_script(int argc, const char **argv)
 	script.tool.throttle		 = process_throttle_event;
 	script.tool.unthrottle		 = process_throttle_event;
 	script.tool.ordering_requires_timestamps = true;
-	script.tool.merge_deferred_callchains = false;
+	script.tool.merge_deferred_callchains = merge_deferred_callchains;
 	session = perf_session__new(&data, &script.tool);
 	if (IS_ERR(session))
 		return PTR_ERR(session);
tools/perf/util/callchain.c +24 -0
index 0c7564747a14..d1114491c3da 100644
@@ -1832,3 +1832,27 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel,
 	}
 	return 0;
 }
+
+int sample__merge_deferred_callchain(struct perf_sample *sample_orig,
+				     struct perf_sample *sample_callchain)
+{
+	u64 nr_orig = sample_orig->callchain->nr - 1;
+	u64 nr_deferred = sample_callchain->callchain->nr;
+	struct ip_callchain *callchain;
+
+	callchain = calloc(1 + nr_orig + nr_deferred, sizeof(u64));
+	if (callchain == NULL) {
+		sample_orig->deferred_callchain = false;
+		return -ENOMEM;
+	}
+
+	callchain->nr = nr_orig + nr_deferred;
+	/* copy except for the last PERF_CONTEXT_USER_DEFERRED */
+	memcpy(callchain->ips, sample_orig->callchain->ips, nr_orig * sizeof(u64));
+	/* copy deferred use callchains */
+	memcpy(&callchain->ips[nr_orig], sample_callchain->callchain->ips,
+	       nr_deferred * sizeof(u64));
+
+	sample_orig->callchain = callchain;
+	return 0;
+}
tools/perf/util/callchain.h +3 -0
index 86ed9e4d04f9..89785125ed25 100644
@@ -317,4 +317,7 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel,
 				    struct perf_sample *sample, int max_stack,
 				    bool symbols, callchain_iter_fn cb, void *data);
 
+int sample__merge_deferred_callchain(struct perf_sample *sample_orig,
+				     struct perf_sample *sample_callchain);
+
 #endif	/* __PERF_CALLCHAIN_H */
tools/perf/util/evlist.c +1 -0
index f14b7e6ff1dc..f27d8c4a22aa 100644
@@ -81,6 +81,7 @@ void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
 	evlist->ctl_fd.ack = -1;
 	evlist->ctl_fd.pos = -1;
 	evlist->nr_br_cntr = -1;
+	INIT_LIST_HEAD(&evlist->deferred_samples);
 }
 
 struct evlist *evlist__new(void)
tools/perf/util/evlist.h +1 -0
index bcc1c6984bb5..c26379366554 100644
@@ -84,6 +84,7 @@ struct evlist {
 		int	pos;	/* index at evlist core object to check signals */
 	} ctl_fd;
 	struct event_enable_timer *eet;
+	struct list_head deferred_samples;
 };
 
 struct evsel_str_handler {
tools/perf/util/session.c +62 -1

81 lines not on this page · raw message

tools/perf/util/tool.c +1 -0
index e78f16de912e..385043e06627 100644
@@ -238,6 +238,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events)
 	tool->cgroup_events = false;
 	tool->no_warn = false;
 	tool->show_feat_hdr = SHOW_FEAT_NO_HEADER;
+	tool->merge_deferred_callchains = true;
 
 	tool->sample = process_event_sample_stub;
 	tool->mmap = process_event_stub;
tools/perf/util/tool.h +1 -0
index 9987bbde6d5e..d06580478ab1 100644
@@ -87,6 +87,7 @@ struct perf_tool {
 	bool		cgroup_events;
 	bool		no_warn;
 	bool		dont_split_sample_group;
+	bool		merge_deferred_callchains;
 	enum show_feature_header show_feat_hdr;
 };
 
Signature · 1 line
2.47.0
author maintainer
#96 lore raw
In reply to Josh Poimboeuf
Quoted 1 line · Josh Poimboeuf

It still needs some binutils and glibc patches

binutils+glibc patches attached.

Signature · 1 line
Josh

From: Indu Bhagat <[email protected]>
Date: Fri, 25 Oct 2024 14:33:19 -0700
To: [email protected]
Subject: [PATCH 1/3] ld: fix PR/32297
X-Mailer: git-send-email 2.43.0
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=utf-8

From: Josh Poimboeuf <[email protected]>

When _creating_ SFrame information for the linker created .plt.sec, the code correctly checks for presence of .plt.sec. When _writing_ the SFrame section for the corresponding .plt.sec, however, the conditionals were wrongly checking for splt. This was causing an assertion at link time.

This issue has been known to affect glibc build with SFrame enabled.

No testcase is added just yet. A later commit ensures correct SFrame stack trace information is created for .plt.got. A test case (where only .plt and .plt.got are created) is added then.

PR/32297 sframe: bfd assertion with empty main on IBT enabled system

ChangeLog:
PR/32297
* bfd/elfxx-x86.c (_bfd_x86_elf_late_size_sections): Check for plt_second member not for splt.

---
 bfd/elfxx-x86.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
bfd/elfxx-x86.c +2 -2
index dd951b91f50..0d83ce57d4f 100644
@@ -2680,8 +2680,8 @@ _bfd_x86_elf_late_size_sections (bfd *output_bfd,
 	_bfd_x86_elf_write_sframe_plt (output_bfd, info, SFRAME_PLT);
 
       if (htab->plt_second_sframe != NULL
-	  && htab->elf.splt != NULL
-	  && htab->elf.splt->size != 0
+	  && htab->plt_second != NULL
+	  && htab->plt_second->size != 0
 	  && htab->plt_second_sframe->contents == NULL)
 	_bfd_x86_elf_write_sframe_plt (output_bfd, info, SFRAME_PLT_SEC);
     }
Signature · 1 line
2.43.0

From: Indu Bhagat <[email protected]>
Date: Fri, 25 Oct 2024 14:33:20 -0700
To: [email protected]
Subject: [PATCH 2/3] ld: fix wrong SFrame info for lazy IBT PLT
X-Mailer: git-send-email 2.43.0
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=utf-8

From: Josh Poimboeuf <[email protected]>

Fix PR/32296 sframe: wrong SFrame info for pltN and .plt.sec for -z ibtplt

The x86 psABI defines a 2-PLT scheme for IBT which uses .plt and .plt.sec entries. It was observed that SFrame information for .plt.sec section was incorrect. The erroneous assumption was that SFrame stack trace information for .plt.sec with lazy binding is the same as SFrame stack trace information for .plt with lazy binding. This is corrected now by initializing a new SFrame PLT helper object elf_x86_64_sframe_ibt_plt for lazy PLT with IBT.

Add a testcase where linking with -z ibtplt generates .plt.sec entries and ensure correct SFrame information for it.

ChangeLog:
PR/32296
* bfd/elf64-x86-64.c (elf_x86_64_sframe_ibt_pltn_fre2): New definition elf_x86_64_sframe_ibt_plt. Use it in elf_x86_64_sframe_plt. (elf_x86_64_link_setup_gnu_properties): Lazy IBT PLT entries are different from lazy PLT.
* bfd/elfxx-x86.c (_bfd_x86_elf_create_sframe_plt): Adjust for SFrame for IBT PLT.
* ld/testsuite/ld-x86-64/x86-64.exp: Add new test.
* ld/testsuite/ld-x86-64/sframe-ibt-plt-1.d: New test.

---
 bfd/elf64-x86-64.c                        | 34 +++++++++++++++++++----
 bfd/elfxx-x86.c                           | 29 ++++++++++---------
 ld/testsuite/ld-x86-64/sframe-ibt-plt-1.d | 33 ++++++++++++++++++++++
 ld/testsuite/ld-x86-64/x86-64.exp         |  1 +
 4 files changed, 79 insertions(+), 18 deletions(-)
 create mode 100644 ld/testsuite/ld-x86-64/sframe-ibt-plt-1.d
bfd/elf64-x86-64.c +29 -5
index 4330bbd1648..abbebbe92b4 100644
@@ -906,6 +906,14 @@ static const sframe_frame_row_entry elf_x86_64_sframe_pltn_fre2 =
   SFRAME_V1_FRE_INFO (SFRAME_BASE_REG_SP, 1, SFRAME_FRE_OFFSET_1B) /* FRE info.  */
 };
 
+/* .sframe FRE covering the .plt section entry for IBT.  */
+static const sframe_frame_row_entry elf_x86_64_sframe_ibt_pltn_fre2 =
+{
+  9, /* SFrame FRE start address.  */
+  {16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 12 bytes.  */
+  SFRAME_V1_FRE_INFO (SFRAME_BASE_REG_SP, 1, SFRAME_FRE_OFFSET_1B) /* FRE info.  */
+};
+
 /* .sframe FRE covering the second .plt section entry.  */
 static const sframe_frame_row_entry elf_x86_64_sframe_sec_pltn_fre1 =
 {
@@ -930,7 +938,7 @@ static const struct elf_x86_sframe_plt elf_x86_64_sframe_non_lazy_plt =
   { &elf_x86_64_sframe_null_fre }
 };
 
-/* SFrame helper object for lazy PLT.  Also used for IBT enabled PLT.  */
+/* SFrame helper object for lazy PLT. */
 static const struct elf_x86_sframe_plt elf_x86_64_sframe_plt =
 {
   LAZY_PLT_ENTRY_SIZE,
@@ -942,9 +950,25 @@ static const struct elf_x86_sframe_plt elf_x86_64_sframe_plt =
   /* Array of SFrame FREs for plt.  */
   { &elf_x86_64_sframe_pltn_fre1, &elf_x86_64_sframe_pltn_fre2 },
   NON_LAZY_PLT_ENTRY_SIZE,
-  1, /* Number of FREs for PLTn for second PLT.  */
-  /* FREs for second plt (stack trace info for .plt.got is
-     identical).  Used when IBT or non-lazy PLT is in effect.  */
+  1, /* Number of FREs for second PLT.  */
+  /* Array of SFrame FREs for second PLT.  */
+  { &elf_x86_64_sframe_sec_pltn_fre1 }
+};
+
+/* SFrame helper object for lazy PLT with IBT. */
+static const struct elf_x86_sframe_plt elf_x86_64_sframe_ibt_plt =
+{
+  LAZY_PLT_ENTRY_SIZE,
+  2, /* Number of FREs for PLT0.  */
+  /* Array of SFrame FREs for plt0.  */
+  { &elf_x86_64_sframe_plt0_fre1, &elf_x86_64_sframe_plt0_fre2 },
+  LAZY_PLT_ENTRY_SIZE,
+  2, /* Number of FREs for PLTn.  */
+  /* Array of SFrame FREs for plt.  */
+  { &elf_x86_64_sframe_pltn_fre1, &elf_x86_64_sframe_ibt_pltn_fre2 },
+  LAZY_PLT_ENTRY_SIZE,
+  1, /* Number of FREs for second PLT.  */
+  /* Array of SFrame FREs for second plt.  */
   { &elf_x86_64_sframe_sec_pltn_fre1 }
 };
 
@@ -5678,7 +5702,7 @@ elf_x86_64_link_setup_gnu_properties (struct bfd_link_info *info)
     {
       init_table.sframe_lazy_plt = &elf_x86_64_sframe_plt;
       init_table.sframe_non_lazy_plt = &elf_x86_64_sframe_non_lazy_plt;
-      init_table.sframe_lazy_ibt_plt = &elf_x86_64_sframe_plt;
+      init_table.sframe_lazy_ibt_plt = &elf_x86_64_sframe_ibt_plt;
       init_table.sframe_non_lazy_ibt_plt = &elf_x86_64_sframe_non_lazy_plt;
     }
   else
bfd/elfxx-x86.c +16 -13
index 0d83ce57d4f..cb90f688b6f 100644
@@ -1831,7 +1831,6 @@ _bfd_x86_elf_create_sframe_plt (bfd *output_bfd,
   struct elf_x86_link_hash_table *htab;
   const struct elf_backend_data *bed;
 
-  bool plt0_generated_p;
   unsigned int plt0_entry_size;
   unsigned char func_info;
   uint32_t fre_type;
@@ -1845,14 +1844,11 @@ _bfd_x86_elf_create_sframe_plt (bfd *output_bfd,
   unsigned plt_entry_size = 0;
   unsigned int num_pltn_fres = 0;
   unsigned int num_pltn_entries = 0;
+  const sframe_frame_row_entry * const *pltn_fres;
 
   bed = get_elf_backend_data (output_bfd);
   htab = elf_x86_hash_table (info, bed->target_id);
   /* Whether SFrame stack trace info for plt0 is to be generated.  */
-  plt0_generated_p = htab->plt.has_plt0;
-  plt0_entry_size
-    = (plt0_generated_p) ? htab->sframe_plt->plt0_entry_size : 0;
-
   switch (plt_sec_type)
     {
     case SFRAME_PLT:
@@ -1860,7 +1856,10 @@ _bfd_x86_elf_create_sframe_plt (bfd *output_bfd,
 	  ectx = &htab->plt_cfe_ctx;
 	  dpltsec = htab->elf.splt;
 
-	  plt_entry_size = htab->plt.plt_entry_size;
+	  plt0_entry_size
+	    = htab->plt.has_plt0 ? htab->sframe_plt->plt0_entry_size : 0;
+	  plt_entry_size = htab->sframe_plt->pltn_entry_size;
+	  pltn_fres = htab->sframe_plt->pltn_fres;
 	  num_pltn_fres = htab->sframe_plt->pltn_num_fres;
 	  num_pltn_entries
 	    = (dpltsec->size - plt0_entry_size) / plt_entry_size;
@@ -1870,12 +1869,15 @@ _bfd_x86_elf_create_sframe_plt (bfd *output_bfd,
     case SFRAME_PLT_SEC:
 	{
 	  ectx = &htab->plt_second_cfe_ctx;
-	  /* FIXME - this or htab->plt_second_sframe ?  */
-	  dpltsec = htab->plt_second_eh_frame;
+	  dpltsec = htab->plt_second;
+
+	  plt0_entry_size = 0;
 
 	  plt_entry_size = htab->sframe_plt->sec_pltn_entry_size;
+	  pltn_fres = htab->sframe_plt->sec_pltn_fres;
 	  num_pltn_fres = htab->sframe_plt->sec_pltn_num_fres;
 	  num_pltn_entries = dpltsec->size / plt_entry_size;
+
 	  break;
 	}
     default:
@@ -1897,7 +1899,7 @@ _bfd_x86_elf_create_sframe_plt (bfd *output_bfd,
 
   /* Add SFrame FDE and the associated FREs for plt0 if plt0 has been
      generated.  */
-  if (plt0_generated_p)
+  if (plt0_entry_size)
     {
       /* Add SFrame FDE for plt0, the function start address is updated later
 	 at _bfd_elf_merge_section_sframe time.  */
@@ -1934,16 +1936,17 @@ _bfd_x86_elf_create_sframe_plt (bfd *output_bfd,
 				      plt0_entry_size, /* func start addr.  */
 				      dpltsec->size - plt0_entry_size,
 				      func_info,
-				      16,
+				      plt_entry_size,
 				      0 /* Num FREs.  */);
 
       sframe_frame_row_entry pltn_fre;
-      /* Now add the FREs for pltn.  Simply adding the two FREs suffices due
+      /* Now add the FREs for pltn.  Simply adding the FREs suffices due
 	 to the usage of SFRAME_FDE_TYPE_PCMASK above.  */
       for (unsigned int j = 0; j < num_pltn_fres; j++)
 	{
-	  pltn_fre = *(htab->sframe_plt->pltn_fres[j]);
-	  sframe_encoder_add_fre (*ectx, 1, &pltn_fre);
+	  unsigned int func_idx = plt0_entry_size ? 1 : 0;
+	  pltn_fre = *(pltn_fres[j]);
+	  sframe_encoder_add_fre (*ectx, func_idx, &pltn_fre);
 	}
     }
 
ld/testsuite/ld-x86-64/sframe-ibt-plt-1.d new file +33 -0
new file mode 100644
index 00000000000..26be4dfc6a0
@@ -0,0 +1,33 @@
+#as: --gsframe
+#source: ibt-plt-3.s
+#objdump: --sframe=.sframe
+#ld: -shared -z ibtplt --no-rosegment
+#name: SFrame for IBT PLT .plt.sec
+
+.*: +file format .*
+
+Contents of the SFrame section .sframe:
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_SORTED
+    CFA fixed RA offset: \-8
+#...
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x1000, size = 16 bytes
+    STARTPC +CFA +FP +RA +
+    0+1000 +sp\+16 +u +f +
+    0+1006 +sp\+24 +u +f +
+
+    func idx \[1\]: pc = 0x1010, size = 32 bytes
+    STARTPC\[m\] +CFA +FP +RA +
+    0+0000 +sp\+8 +u +f +
+    0+0009 +sp\+16 +u +f +
+
+    func idx \[2\]: pc = 0x1030, size = 32 bytes
+    STARTPC\[m\] +CFA +FP +RA +
+    0+0000 +sp\+8 +u +f +
+
+#...
ld/testsuite/ld-x86-64/x86-64.exp +1 -0
index bd7574d6965..0632397bccc 100644
@@ -547,6 +547,7 @@ run_dump_test "pr32191-x32"
 if { ![skip_sframe_tests] } {
     run_dump_test "sframe-simple-1"
     run_dump_test "sframe-plt-1"
+    run_dump_test "sframe-ibt-plt-1"
 }
 
 if ![istarget "x86_64-*-linux*"] {
Signature · 1 line
2.43.0

From: Indu Bhagat <[email protected]>
Date: Fri, 25 Oct 2024 14:33:21 -0700
To: [email protected]
Cc: Indu Bhagat <[email protected]>
Subject: [PATCH 3/3] ld: generate SFrame stack trace info for .plt.got
X-Mailer: git-send-email 2.43.0
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=utf-8

PR/32298 sframe: no SFrame stack trace info generated for .plt.got

Add support to generate SFrame stack trace info for .plt.got section. Enhance the current definition of struct elf_x86_sframe_plt to include initialized SFrame FDE/FREs applicable for .plt.got section. There are two variants of .plt.got entries: 16 byte and 8 byte.

8 byte:

    ff 25 00 00 00 00     jmpq  *name@GOTPCREL(%rip)
    66 90                 xchg  %ax,%ax

16 byte:

    f3 0f 1e fa           endbr64
    ff 25 66 2f 00 00     jmpq  *name@GOTPCREL(%rip)
    66 0f 1f 44 00 00     nopw   0x0(%rax,%rax,1)

For the testcase, define some application symbols such that their PLT entry is placed in .plt.got and ensure SFrame information is generated with and without -z ibtplt.

ChangeLog:
PR/32298
* bfd/elf64-x86-64.c (elf_x86_64_link_setup_gnu_properties):
PLT GOT entry size is different for IBT vs non IBT PLTs.
* bfd/elfxx-x86.c (enum dynobj_sframe_plt_type): New enum for SFRAME_PLT_GOT.
(_bfd_x86_elf_create_sframe_plt): Handle SFRAME_PLT_GOT. (_bfd_x86_elf_write_sframe_plt): Likewise. (_bfd_x86_elf_late_size_sections): Likewise. (_bfd_x86_elf_finish_dynamic_sections): Likewise.
* bfd/elfxx-x86.h (struct elf_x86_sframe_plt): Add new members to keep information about PLT GOT entries.
(struct elf_x86_link_hash_table): Add support for creating SFrame section for .plt.got.
* ld/testsuite/ld-x86-64/x86-64.exp: Add new tests.
* ld/testsuite/ld-x86-64/sframe-pltgot-1.d: New test.
* ld/testsuite/ld-x86-64/sframe-pltgot-1.s: New test.
* ld/testsuite/ld-x86-64/sframe-pltgot-2.d: New test.

---
 bfd/elf64-x86-64.c                       | 42 ++++++++++--
 bfd/elfxx-x86.c                          | 86 ++++++++++++++++++++++--
 bfd/elfxx-x86.h                          |  6 ++
 ld/testsuite/ld-x86-64/sframe-pltgot-1.d | 28 ++++++++
 ld/testsuite/ld-x86-64/sframe-pltgot-1.s | 15 +++++
 ld/testsuite/ld-x86-64/sframe-pltgot-2.d | 28 ++++++++
 ld/testsuite/ld-x86-64/x86-64.exp        |  2 +
 7 files changed, 198 insertions(+), 9 deletions(-)
 create mode 100644 ld/testsuite/ld-x86-64/sframe-pltgot-1.d
 create mode 100644 ld/testsuite/ld-x86-64/sframe-pltgot-1.s
 create mode 100644 ld/testsuite/ld-x86-64/sframe-pltgot-2.d
bfd/elf64-x86-64.c +37 -5

72 lines not on this page · raw message

bfd/elfxx-x86.c +82 -4

140 lines not on this page · raw message

bfd/elfxx-x86.h +6 -0
index b042d45c282..cd26e8fc6f2 100644
@@ -401,6 +401,10 @@ struct elf_x86_sframe_plt
   unsigned int sec_pltn_entry_size;
   unsigned int sec_pltn_num_fres;
   const sframe_frame_row_entry *sec_pltn_fres[SFRAME_PLTN_MAX_NUM_FRES];
+
+  unsigned int plt_got_entry_size;
+  unsigned int plt_got_num_fres;
+  const sframe_frame_row_entry *plt_got_fres[SFRAME_PLTN_MAX_NUM_FRES];
 };
 
 struct elf_x86_lazy_plt_layout
@@ -606,6 +610,8 @@ struct elf_x86_link_hash_table
   asection *plt_sframe;
   sframe_encoder_ctx *plt_second_cfe_ctx;
   asection *plt_second_sframe;
+  sframe_encoder_ctx *plt_got_cfe_ctx;
+  asection *plt_got_sframe;
 
   /* Parameters describing PLT generation, lazy or non-lazy.  */
   struct elf_x86_plt_layout plt;
ld/testsuite/ld-x86-64/sframe-pltgot-1.d new file +28 -0
new file mode 100644
index 00000000000..e26a741b635
@@ -0,0 +1,28 @@
+#as: --gsframe
+#source: sframe-pltgot-1.s
+#objdump: --sframe=.sframe
+#ld: -shared -z ibtplt --no-rosegment
+#name: SFrame for .plt.got
+
+.*: +file format .*
+
+Contents of the SFrame section .sframe:
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_SORTED
+    CFA fixed RA offset: \-8
+#...
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x1000, size = 16 bytes
+    STARTPC +CFA +FP +RA +
+    0+1000 +sp\+16 +u +f +
+    0+1006 +sp\+24 +u +f +
+
+    func idx \[1\]: pc = 0x1010, size = 64 bytes
+    STARTPC\[m\] +CFA +FP +RA +
+    0+0000 +sp\+16 +u +f +
+
+#...
ld/testsuite/ld-x86-64/sframe-pltgot-1.s new file +15 -0
new file mode 100644
index 00000000000..e596e846439
@@ -0,0 +1,15 @@
+        .text
+        .globl foo
+        .type foo, @function
+foo:
+        .cfi_startproc
+        call    func1@plt
+        movq    func1@GOTPCREL(%rip), %rax
+        call    func2@plt
+        movq    func2@GOTPCREL(%rip), %rax
+        call    func3@plt
+        movq    func3@GOTPCREL(%rip), %rax
+        call    func4@plt
+        movq    func4@GOTPCREL(%rip), %rax
+        .cfi_endproc
+	.section	.note.GNU-stack,"",@progbits
ld/testsuite/ld-x86-64/sframe-pltgot-2.d new file +28 -0
new file mode 100644
index 00000000000..7a99d12faa8
@@ -0,0 +1,28 @@
+#as: --gsframe
+#source: sframe-pltgot-1.s
+#objdump: --sframe=.sframe
+#ld: -shared --no-rosegment
+#name: SFrame for .plt.got
+
+.*: +file format .*
+
+Contents of the SFrame section .sframe:
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_SORTED
+    CFA fixed RA offset: \-8
+#...
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x1000, size = 16 bytes
+    STARTPC +CFA +FP +RA +
+    0+1000 +sp\+16 +u +f +
+    0+1006 +sp\+24 +u +f +
+
+    func idx \[1\]: pc = 0x1010, size = 32 bytes
+    STARTPC\[m\] +CFA +FP +RA +
+    0+0000 +sp\+16 +u +f +
+
+#...
ld/testsuite/ld-x86-64/x86-64.exp +2 -0
index 0632397bccc..a66d28eb0d1 100644
@@ -548,6 +548,8 @@ if { ![skip_sframe_tests] } {
     run_dump_test "sframe-simple-1"
     run_dump_test "sframe-plt-1"
     run_dump_test "sframe-ibt-plt-1"
+    run_dump_test "sframe-pltgot-1"
+    run_dump_test "sframe-pltgot-2"
 }
 
 if ![istarget "x86_64-*-linux*"] {
Signature · 1 line
2.43.0
elf/dl-load.c +16 -0
index 2923b1141d..ffaf722f5f 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))
@@ -1376,6 +1385,13 @@ cannot enable executable stack as shared object requires");
 	break;
       }
 
+#define PR_ADD_SFRAME 74
+  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))
     {
elf/dl-unmap-segments.h +6 -0
index 1ec507e887..c4a7e9e0ed 100644
@@ -21,14 +21,20 @@
 
 #include <link.h>
 #include <sys/mman.h>
+#include <sys/prctl.h>
 
 /* _dl_map_segments ensures that any whole pages in gaps between segments
    are filled in with PROT_NONE mappings.  So we can just unmap the whole
    range in one fell swoop.  */
 
+#define PR_REMOVE_SFRAME 75
+
 static __always_inline void
 _dl_unmap_segments (struct link_map *l)
 {
+  if (l->l_sframe_addr != 0)
+    __prctl(PR_REMOVE_SFRAME, l->l_sframe_addr, NULL, NULL, NULL);
+
   __munmap ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
 }
 
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;
   };
 
author maintainer
#97 lore raw
In reply to Josh Poimboeuf
Quoted 22 lines · Josh Poimboeuf

This has all the changes discussed in v2, plus VDSO sframe support and Namhyung's perf tool patches (see detailed changelog below).

I did quite a bit of testing, it seems to work well. It still needs some binutils and glibc patches which I'll send in a reply.

Questions for perf experts:

- Is the perf_event lifetime managed correctly or do we need to do something to ensure it exists in unwind_user_task_work()?

Or alternatively is the original perf_event even needed in unwind_user_task_work() or can a new one be created on demand?

- Is --call-graph=sframe needed for consistency?

- Should perf use the context cookie? Note that because the callback is usually only called once for multiple NMIs in the same entry context, it's possible for the PERF_RECORD_CALLCHAIN_DEFERRED event to arrive *before* some of the corresponding kernel events. The context cookie disambiguates the corner cases.

Quoted 5 lines

Based on tip/master.

Also at:

git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git sframe-v3

Argh, apparently it's a bad idea to pass "*.patch" twice on the git-send-email cmdline ;-) Sorry for sending it twice!

Signature · 1 line
Josh
maintainer
#98 lore raw
In reply to Josh Poimboeuf
Quoted 2 lines · Josh Poimboeuf

- Is the perf_event lifetime managed correctly or do we need to do something to ensure it exists in unwind_user_task_work()?

You probably want a sync/cancel somewhere near the start of _free_event().