unwind, perf: sframe user space unwinding
Subsystems (18)
Josh Poimboeuf
maintainerv4 cover unwind, perf: sframe user space unwinding
This took a bit longer than expected. I fell into some rabbit holes chasing a number of subtle bugs. I ended up rewriting the deferral code several times. But I think the end result is much better.
The deferral request has a new interface, which helps make the implementation MUCH simpler and less fragile. As a bonus it's now possible for the request implementation to be NMI-safe.
The interface is similar to {task,irq}_work. The caller owns an unwind_work struct:
struct unwind_work {
struct callback_head work;
unwind_callback_t func;
int pending;};
For perf, struct unwind_work is embedded in struct perf_event. For ftrace maybe it would live in task_struct?
The unwind_work can be passed to the following functions:
void unwind_deferred_init(struct unwind_work *work, unwind_callback_t func); int unwind_deferred_request(struct unwind_work *work, u64 *cookie); bool unwind_deferred_cancel(struct task_struct *task, struct unwind_work *work);
If unwind_deferred_request() returns success, the callback is guaranteed. If the callback is already pending, it returns an error, but the returned *cookie is still valid if it's nonzero.
Questions:
- Peter, I'm not sure how well this works with Intel PEBS? This just uses the original task regs, is that a problem?
- Namhyung, I rebased your perf tool patches on the new missing feature validation code, do the patches still look sane?
For testing with user space, here are the latest binutils fixes:
1785837a2570 ("ld: fix PR/32297")
938fb512184d ("ld: fix wrong SFrame info for lazy IBT PLT") 47c88752f9ad ("ld: generate SFrame stack trace info for .plt.got")
An out-of-tree glibc patch is also needed -- will attach in a reply.
Code also available at
git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git sframe-v4
v4:
- split up patches better [Andrii]
- add callback guarantee [Andrii]
- support multiple non-contiguous elf text segments [Andrii]
- sframe section validation [Andrii]
- x86 compat mode support [Peter]
- implement guard(mmap_read_lock) [Peter]
- synchronize callback with perf event lifetime [Peter]
- detect toolchain sframe support with CONFIG_SFRAME_AS [Jens]
- get vdso working (with updated glibc patches) [Jens]
- rebase perf tool on new missing feature validation code
- brand new deferred interface and implementation
- make unwind_deferred_request() NMI-safe
- sframe debugging infrastructure
- fix some task_work bugs
- enclose multiple user copies in single STAC/CLAC pair for performance
- much banging head on wall, refactoring, simplification
- fix a lot of bugs
Previous revisions
------------------
v3:
https://lore.kernel.org/[email protected]
- 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]
Original description
--------------------
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 (35):
task_work: Fix TWA_NMI_CURRENT error handling
task_work: Fix TWA_NMI_CURRENT race with __schedule()
mm: Add guard for mmap_read_lock
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
x86/uaccess: Add unsafe_copy_from_user() implementation
unwind_user: Add user space unwinding API
unwind_user: Add frame pointer support
unwind_user/x86: Enable frame pointer unwinding on x86 perf/x86: Rename get_segment_base() and make it global
unwind_user: Add compat mode frame pointer support unwind_user/x86: Enable compat mode frame pointer unwinding on x86 unwind_user/sframe: Add support for reading .sframe headers unwind_user/sframe: Store sframe section data in per-mm maple tree unwind_user/sframe: Add support for reading .sframe contents unwind_user/sframe: Detect .sframe sections in executables unwind_user/sframe: Add prctl() interface for registering .sframe
sections
unwind_user/sframe: Wire up unwind_user to sframe unwind_user/sframe/x86: Enable sframe unwinding on x86 unwind_user/sframe: Remove .sframe section on detected corruption unwind_user/sframe: Show file name in debug output unwind_user/sframe: Enable debugging in uaccess regions unwind_user/sframe: Add .sframe validation option unwind_user/deferred: Add deferred unwinding interface unwind_user/deferred: Add unwind cache unwind_user/deferred: Make unwind deferral requests NMI-safe
perf: Remove get_perf_callchain() 'init_nr' argument
perf: Remove get_perf_callchain() 'crosstask' argument
perf: Simplify get_perf_callchain() user logic
perf: Skip user unwind if !current->mm
perf: Support 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 | 40 ++ arch/x86/Kconfig | 3 + arch/x86/entry/vdso/Makefile | 10 +- 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/events/core.c | 10 +- arch/x86/include/asm/dwarf2.h | 54 +- arch/x86/include/asm/linkage.h | 29 +- arch/x86/include/asm/mmu.h | 2 +- arch/x86/include/asm/perf_event.h | 2 + arch/x86/include/asm/uaccess.h | 39 +- arch/x86/include/asm/unwind_user.h | 61 +++ arch/x86/include/asm/unwind_user_types.h | 17 + arch/x86/include/asm/vdso.h | 1 - fs/binfmt_elf.c | 49 +- include/asm-generic/Kbuild | 2 + include/asm-generic/unwind_user.h | 24 + include/asm-generic/unwind_user_types.h | 9 + include/linux/entry-common.h | 3 + include/linux/mm_types.h | 3 + include/linux/mmap_lock.h | 2 + include/linux/perf_event.h | 15 +- include/linux/sched.h | 5 + include/linux/sframe.h | 56 ++ include/linux/unwind_deferred.h | 52 ++ include/linux/unwind_deferred_types.h | 17 + include/linux/unwind_user.h | 15 + include/linux/unwind_user_types.h | 36 ++ include/uapi/linux/elf.h | 1 + include/uapi/linux/perf_event.h | 19 +- include/uapi/linux/prctl.h | 5 +- kernel/Makefile | 1 + kernel/bpf/stackmap.c | 14 +- kernel/events/callchain.c | 47 +- kernel/events/core.c | 112 +++- kernel/fork.c | 14 + kernel/sys.c | 9 + kernel/task_work.c | 67 ++- kernel/unwind/Makefile | 2 + kernel/unwind/deferred.c | 266 ++++++++++ kernel/unwind/sframe.c | 595 ++++++++++++++++++++++ kernel/unwind/sframe.h | 71 +++ kernel/unwind/sframe_debug.h | 95 ++++ kernel/unwind/user.c | 146 ++++++ mm/init-mm.c | 2 + tools/include/uapi/linux/perf_event.h | 19 +- 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 | 39 ++ 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 +- 64 files changed, 2208 insertions(+), 133 deletions(-) create mode 100644 arch/x86/include/asm/unwind_user.h create mode 100644 arch/x86/include/asm/unwind_user_types.h create mode 100644 include/asm-generic/unwind_user.h create mode 100644 include/asm-generic/unwind_user_types.h create mode 100644 include/linux/sframe.h create mode 100644 include/linux/unwind_deferred.h create mode 100644 include/linux/unwind_deferred_types.h create mode 100644 include/linux/unwind_user.h create mode 100644 include/linux/unwind_user_types.h create mode 100644 kernel/unwind/Makefile create mode 100644 kernel/unwind/deferred.c create mode 100644 kernel/unwind/sframe.c create mode 100644 kernel/unwind/sframe.h create mode 100644 kernel/unwind/sframe_debug.h create mode 100644 kernel/unwind/user.c
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 1/39 task_work: Fix TWA_NMI_CURRENT error handling
It's possible for irq_work_queue() to fail if the work has already been claimed. That can happen if a TWA_NMI_CURRENT task work is requested before a previous TWA_NMI_CURRENT IRQ work on the same CPU has gotten a chance to run.
The error has to be checked before the write to task->task_works. Also the try_cmpxchg() loop isn't needed in NMI context. The TWA_NMI_CURRENT case really is special, keep things simple by keeping its code all together in one place.
--- kernel/task_work.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-)
kernel/task_work.c +26 -13
@@ -58,25 +58,38 @@ int task_work_add(struct task_struct *task, struct callback_head *work,int flags = notify &TWA_FLAGS ; notify &= ~TWA_FLAGS ; +if (notify ==TWA_NMI_CURRENT ) { -if (WARN_ON_ONCE (task != current)) +if (WARN_ON_ONCE (!in_nmi () || task != current))return -EINVAL ;if (!IS_ENABLED (CONFIG_IRQ_WORK ))return -EINVAL ; - }else { -/* -* Record the work call stack in order to print it in KASAN -* reports. -* -* Note that stack allocation can fail if TWAF_NO_ALLOC flag -* is set and new page is needed to expand the stack buffer. -*/ -if (flags &TWAF_NO_ALLOC ) -kasan_record_aux_stack_noalloc (work); -else -kasan_record_aux_stack (work); +#ifdef CONFIG_IRQ_WORK + head = task->task_works; +if (unlikely (head == &work_exited)) +return -ESRCH ; + +if (!irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume))) +return -EBUSY ; + + work->next = head; + task->task_works = work; +#endif +return 0 ; } +/* +* Record the work call stack in order to print it in KASAN +* reports. +* +* Note that stack allocation can fail if TWAF_NO_ALLOC flag +* is set and new page is needed to expand the stack buffer. +*/ +if (flags &TWAF_NO_ALLOC ) +kasan_record_aux_stack_noalloc (work); +else +kasan_record_aux_stack (work); + head =READ_ONCE (task->task_works); do { if (unlikely(head == &work_exited))
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 01/39] task_work: Fix TWA_NMI_CURRENT error handling
Quoted 4 lines · Josh Poimboeuf
It's possible for
irq_work_queue()to fail if the work has already been claimed. That can happen if aTWA_NMI_CURRENTtask work is requested before a previousTWA_NMI_CURRENTIRQ work on the same CPU has gotten a chance to run.
I'm confused, if it fails then it's already pending, and we'll get the notification already. You can still add the work.
Quoted 4 lines
The error has to be checked before the write to
task->task_works. Also thetry_cmpxchg()loop isn't needed in NMI context. TheTWA_NMI_CURRENTcase really is special, keep things simple by keeping its code all together in one place.
NMIs can nest, consider #DB (which is NMI like) doing task_work_add() and getting interrupted with NMI doing the same.
Might all this be fallout from trying to fix that schedule() bug from the next patch, because as is, I don't see it.
Josh Poimboeuf
maintainerRe: [PATCH v4 01/39] task_work: Fix TWA_NMI_CURRENT error handling
Quoted 8 lines · Peter Zijlstra
Quoted 4 lines · Josh Poimboeuf
It's possible for
irq_work_queue()to fail if the work has already been claimed. That can happen if aTWA_NMI_CURRENTtask work is requested before a previousTWA_NMI_CURRENTIRQ work on the same CPU has gotten a chance to run.I'm confused, if it fails then it's already pending, and we'll get the notification already. You can still add the work.
Yeah, I suppose that makes sense. If the pending irq_work is already going to set TIF_NOTIFY_RESUME anyway, there's no need to do that again.
Quoted 6 lines
Quoted 4 lines
The error has to be checked before the write to
task->task_works. Also thetry_cmpxchg()loop isn't needed in NMI context. TheTWA_NMI_CURRENTcase really is special, keep things simple by keeping its code all together in one place.NMIs can nest,
Just for my understanding: for nested NMIs, the entry code basically queues up the next NMI, so the C handler (exc_nmi) can't nest. Right?
Quoted 1 line
consider #DB (which is NMI like)
What exactly do you mean by "NMI like"? Is it because a #DB might be basically running in NMI context, if the NMI hit a breakpoint?
Quoted 1 line
doing
task_work_add()and getting interrupted with NMI doing the same.
How exactly would that work? At least with my patch the #DB wouldn't be able to use TWA_NMI_CURRENT unless in_nmi() were true due to NMI hitting a breakpoint. In which case a nested NMI wouldn't actually nest, it would get "queued" by the entry code.
But yeah, I do see how the reverse can be true: somebody sets a breakpoint in task_work, right where it's fiddling with the list head. NMI calls task_work_add(TWA_NMI_CURRENT), triggering the #DB, which also calls task_work_add().
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 01/39] task_work: Fix TWA_NMI_CURRENT error handling
Quoted 20 lines · Josh Poimboeuf
Quoted 8 lines · Peter Zijlstra
Quoted 4 lines · Josh Poimboeuf
It's possible for
irq_work_queue()to fail if the work has already been claimed. That can happen if aTWA_NMI_CURRENTtask work is requested before a previousTWA_NMI_CURRENTIRQ work on the same CPU has gotten a chance to run.I'm confused, if it fails then it's already pending, and we'll get the notification already. You can still add the work.
Yeah, I suppose that makes sense. If the pending
irq_workis already going to setTIF_NOTIFY_RESUMEanyway, there's no need to do that again.Quoted 6 lines
Quoted 4 lines
The error has to be checked before the write to
task->task_works. Also thetry_cmpxchg()loop isn't needed in NMI context. TheTWA_NMI_CURRENTcase really is special, keep things simple by keeping its code all together in one place.NMIs can nest,
Quoted 7 lines
Just for my understanding: for nested NMIs, the entry code basically queues up the next NMI, so the C handler (
exc_nmi) can't nest. Right?Quoted 1 line
consider #DB (which is NMI like)
What exactly do you mean by "NMI like"? Is it because a #DB might be basically running in NMI context, if the NMI hit a breakpoint?
No, #DB, #BP and such are considered NMI (and will have in_nmi() true) because they can trigger anywhere, including sections where IRQs are disabled.
Quoted 4 lines
Quoted 1 line
doing
task_work_add()and getting interrupted with NMI doing the same.How exactly would that work? At least with my patch the #DB wouldn't be able to use
TWA_NMI_CURRENTunlessin_nmi()were true
It is, see exc_debug_kernel() doing irqentry_nmi_enter().
Josh Poimboeuf
maintainerRe: [PATCH v4 01/39] task_work: Fix TWA_NMI_CURRENT error handling
Quoted 7 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
What exactly do you mean by "NMI like"? Is it because a #DB might be basically running in NMI context, if the NMI hit a breakpoint?
No, #DB, #BP and such are considered NMI (and will have
in_nmi()true) because they can trigger anywhere, including sections where IRQs are disabled.
So:
- while exceptions are technically not NMI, they're "NMI" because they can occur in NMI or IRQ-disabled regions
- such "NMI" exceptions can be preempted by NMIs and "NMIs"
- NMIs can be preempted by "NMIs" but not NMIs (except in entry code!)
... did I get all that right? Not subtle at all!
I feel like in_nmi() needs a comment explaining all that nonobviousness.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 01/39] task_work: Fix TWA_NMI_CURRENT error handling
Quoted 11 lines · Josh Poimboeuf
Quoted 7 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
What exactly do you mean by "NMI like"? Is it because a #DB might be basically running in NMI context, if the NMI hit a breakpoint?
No, #DB, #BP and such are considered NMI (and will have
in_nmi()true) because they can trigger anywhere, including sections where IRQs are disabled.So:
Quoted 8 lines
- while exceptions are technically not NMI, they're "NMI" because they can occur in NMI or IRQ-disabled regions
- such "NMI" exceptions can be preempted by NMIs and "NMIs"
- NMIs can be preempted by "NMIs" but not NMIs (except in entry code!)
... did I get all that right? Not subtle at all!
Yeah, sounds about right :-)
Steven Rostedt
maintainerRe: [PATCH v4 01/39] task_work: Fix TWA_NMI_CURRENT error handling
In reply to Peter Zijlstra/me is resurrecting the sframe work
Quoted 8 lines · On Wed, 22 Jan 2025 13:28:21 +0100 Peter Zijlstra <[email protected]> wrote:
Quoted 4 lines
The error has to be checked before the write to
task->task_works. Also thetry_cmpxchg()loop isn't needed in NMI context. TheTWA_NMI_CURRENTcase really is special, keep things simple by keeping its code all together in one place.NMIs can nest, consider #DB (which is NMI like) doing
task_work_add()and getting interrupted with NMI doing the same.
I was looking at this patch and was thinking that the trycmpxchg() is still for the race against other CPUs.
Another CPU can add a task_work for this task, right?
If so, then even though the NMI adding the task work can't be interrupted (considering there's no #DB and such), it still can clobber an update done for this tasks task_work from another CPU.
Is this patch still even needed?
-- Steve
Josh Poimboeuf
maintainerPATCH v4 2/39 task_work: Fix TWA_NMI_CURRENT race with __schedule()
In reply to Josh PoimboeufIf TWA_NMI_CURRENT task work is queued from an NMI triggered while running in __schedule() with IRQs disabled, task_work_set_notify_irq() ends up inadvertently running on the next scheduled task. So the original task doesn't get its TIF_NOTIFY_RESUME flag set and the task work may get delayed indefinitely, or may not get to run at all.
__schedule() // disable irqs <NMI> task_work_add(current, work, TWA_NMI_CURRENT); </NMI> // current = next; // enable irqs <IRQ> task_work_set_notify_irq() test_and_set_tsk_thread_flag(current, TIF_NOTIFY_RESUME); // wrong task! </IRQ> // original task skips task work on its next return to user (or exit!)
Fix it by storing the task pointer along with the irq_work struct and passing that task to set_notify_resume().
--- kernel/task_work.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-)
kernel/task_work.c +21 -9
@@ -7,12 +7,23 @@static struct callback_head work_exited;/* all we need is ->next == NULL */ #ifdef CONFIG_IRQ_WORK + +struct nmi_irq_work { +struct irq_work work; +struct task_struct *task; +}; +static void task_work_set_notify_irq (struct irq_work *entry) { -test_and_set_tsk_thread_flag (current,TIF_NOTIFY_RESUME ); + struct nmi_irq_work *work = container_of(entry, struct nmi_irq_work, work); + +set_notify_resume (work->task); } -static DEFINE_PER_CPU(struct irq_work, irq_work_NMI_resume) = -IRQ_WORK_INIT_HARD (task_work_set_notify_irq); + +static DEFINE_PER_CPU(struct nmi_irq_work, nmi_irq_work) = { + .work =IRQ_WORK_INIT_HARD (task_work_set_notify_irq), +}; +#endif /**
@@ -65,15 +76,21 @@ int task_work_add(struct task_struct *task, struct callback_head *work,if (!IS_ENABLED (CONFIG_IRQ_WORK ))return -EINVAL ;#ifdef CONFIG_IRQ_WORK +{ +struct nmi_irq_work *irq_work =this_cpu_ptr (&nmi_irq_work); + head = task->task_works;if (unlikely (head == &work_exited))return -ESRCH ; -if (!irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume))) +if (!irq_work_queue (&irq_work->work))return -EBUSY ; + irq_work->task = current; + work->next = head; task->task_works = work; +}#endif return 0 ; }
@@ -109,11 +126,6 @@ int task_work_add(struct task_struct *task, struct callback_head *work,case TWA_SIGNAL_NO_IPI :__set_notify_signal (task);break ; -#ifdef CONFIG_IRQ_WORK -case TWA_NMI_CURRENT : -irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume)); -break ; -#endif default :WARN_ON_ONCE (1 );break ;
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
kernel/task_work.c int task_work_add(struct task_struct *task, struct callback_head *work, · line 131
Quoted 12 lines · Josh Poimboeuf
kernel/task_work.c +0
-5@@ -109,11 +126,6 @@ int task_work_add(struct task_struct *task, struct callback_head *work,case TWA_SIGNAL_NO_IPI :__set_notify_signal (task);break ; -#ifdef CONFIG_IRQ_WORK -case TWA_NMI_CURRENT : -irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume)); -break ; -#endif default :WARN_ON_ONCE (1 );break ;
Shouldn't this go to in the previous patch?
Peter Zijlstra
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
In reply to Josh PoimboeufQuoted 19 lines · Josh Poimboeuf
If
TWA_NMI_CURRENTtask work is queued from an NMI triggered while running in__schedule()with IRQs disabled,task_work_set_notify_irq()ends up inadvertently running on the next scheduled task. So the original task doesn't get itsTIF_NOTIFY_RESUMEflag set and the task work may get delayed indefinitely, or may not get to run at all.__schedule() // disable irqs <NMI> task_work_add(current, work, TWA_NMI_CURRENT); </NMI> // current = next; // enable irqs <IRQ> task_work_set_notify_irq() test_and_set_tsk_thread_flag(current, TIF_NOTIFY_RESUME); // wrong task! </IRQ> // original task skips task work on its next return to user (or exit!)
Quoted 2 lines
Fix it by storing the task pointer along with the
irq_workstruct and passing that task toset_notify_resume().
So I'm a little confused, isn't something like this sufficient?
If we hit before schedule(), all just works as expected, if we hit after schedule(), the task will already have the TIF flag set, and we'll hit the return to user path once it gets scheduled again.
---
kernel/task_work.c +7 -1
@@ -9,7 +9,12 @@ static struct callback_head work_exited; /* all we need is ->next == NULL */#ifdef CONFIG_IRQ_WORK static void task_work_set_notify_irq (struct irq_work *entry) { -test_and_set_tsk_thread_flag (current,TIF_NOTIFY_RESUME ); +/* +* no-op IPI +* +* TWA_NMI_CURRENT will already have set the TIF flag, all +* this interrupt does it tickle the return-to-user path. +*/ } static DEFINE_PER_CPU(struct irq_work, irq_work_NMI_resume) =IRQ_WORK_INIT_HARD (task_work_set_notify_irq);
@@ -98,6 +103,7 @@ int task_work_add(struct task_struct *task, struct callback_head *work,break ;#ifdef CONFIG_IRQ_WORK case TWA_NMI_CURRENT : +set_tsk_thread_flag (current,TIF_NOTIFY_RESUME );irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume));break ;#endif
Josh Poimboeuf
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
Quoted 26 lines · Peter Zijlstra
So I'm a little confused, isn't something like this sufficient?
If we hit before
schedule(), all just works as expected, if we hit afterschedule(), the task will already have the TIF flag set, and we'll hit the return to user path once it gets scheduled again.---
kernel/task_work.c +6
-1@@ -9,7 +9,12 @@ static struct callback_head work_exited; /* all we need is ->next == NULL */#ifdef CONFIG_IRQ_WORK static void task_work_set_notify_irq (struct irq_work *entry) { -test_and_set_tsk_thread_flag (current,TIF_NOTIFY_RESUME ); +/* +* no-op IPI +* +* TWA_NMI_CURRENT will already have set the TIF flag, all +* this interrupt does it tickle the return-to-user path. +*/ } static DEFINE_PER_CPU(struct irq_work, irq_work_NMI_resume) =IRQ_WORK_INIT_HARD (task_work_set_notify_irq);
kernel/task_work.c int task_work_add(struct task_struct *task, struct callback_head *work, · line 109
Quoted 7 lines
kernel/task_work.c +1
-0@@ -98,6 +103,7 @@ int task_work_add(struct task_struct *task, struct callback_head *work,break ;#ifdef CONFIG_IRQ_WORK case TWA_NMI_CURRENT : +set_tsk_thread_flag (current,TIF_NOTIFY_RESUME );irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume));break ;#endif
Yeah, that looks so much better...
The self-IPI is only needed when the NMI happened in user space, right? Would it make sense to have an optimized version of that?
Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
Quoted 2 lines · Josh Poimboeuf
The self-IPI is only needed when the NMI happened in user space, right? Would it make sense to have an optimized version of that?
Actually, maybe not, that could be tricky if the NMI hits in the kernel after task work runs.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
Quoted 6 lines · Josh Poimboeuf
Quoted 2 lines · Josh Poimboeuf
The self-IPI is only needed when the NMI happened in user space, right? Would it make sense to have an optimized version of that?
Actually, maybe not, that could be tricky if the NMI hits in the kernel after task work runs.
Right, I was going to say, lets keep this as simple as possible :-)
Steven Rostedt
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
In reply to Peter ZijlstraQuoted 24 lines · On Wed, 22 Jan 2025 13:42:28 +0100 Peter Zijlstra <[email protected]> wrote:
If we hit before
schedule(), all just works as expected, if we hit afterschedule(), the task will already have the TIF flag set, and we'll hit the return to user path once it gets scheduled again.---
kernel/task_work.c +6
-1@@ -9,7 +9,12 @@ static struct callback_head work_exited; /* all we need is ->next == NULL */#ifdef CONFIG_IRQ_WORK static void task_work_set_notify_irq (struct irq_work *entry) { -test_and_set_tsk_thread_flag (current,TIF_NOTIFY_RESUME ); +/* +* no-op IPI +* +* TWA_NMI_CURRENT will already have set the TIF flag, all +* this interrupt does it tickle the return-to-user path. +*/ } static DEFINE_PER_CPU(struct irq_work, irq_work_NMI_resume) =IRQ_WORK_INIT_HARD (task_work_set_notify_irq);
kernel/task_work.c int task_work_add(struct task_struct *task, struct callback_head *work, · line 109
Quoted 7 lines
kernel/task_work.c +1
-0@@ -98,6 +103,7 @@ int task_work_add(struct task_struct *task, struct callback_head *work,break ;#ifdef CONFIG_IRQ_WORK case TWA_NMI_CURRENT : +set_tsk_thread_flag (current,TIF_NOTIFY_RESUME );irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume));break ;#endif
Does this patch replace patches 1 and 2?
If so, Peter, can you give me a Signed-off-by?
-- Steve
Josh Poimboeuf
maintainerRe: [PATCH v4 02/39] task_work: Fix TWA_NMI_CURRENT race with __schedule()
Quoted 28 lines · Steven Rostedt
Quoted 25 lines · On Wed, 22 Jan 2025 13:42:28 +0100 Peter Zijlstra <[email protected]> wrote:
If we hit before
schedule(), all just works as expected, if we hit afterschedule(), the task will already have the TIF flag set, and we'll hit the return to user path once it gets scheduled again.---
kernel/task_work.c +6
-1@@ -9,7 +9,12 @@ static struct callback_head work_exited; /* all we need is ->next == NULL */#ifdef CONFIG_IRQ_WORK static void task_work_set_notify_irq (struct irq_work *entry) { -test_and_set_tsk_thread_flag (current,TIF_NOTIFY_RESUME ); +/* +* no-op IPI +* +* TWA_NMI_CURRENT will already have set the TIF flag, all +* this interrupt does it tickle the return-to-user path. +*/ } static DEFINE_PER_CPU(struct irq_work, irq_work_NMI_resume) =IRQ_WORK_INIT_HARD (task_work_set_notify_irq);@@ -98,6 +103,7 @@ int task_work_add(struct task_struct *task, struct callback_head *work,break ;
Quoted 8 lines
kernel/task_work.c int task_work_add(struct task_struct *task, struct callback_head *work, · line 109
Quoted 6 lines
kernel/task_work.c +1
-0#ifdef CONFIG_IRQ_WORK case TWA_NMI_CURRENT : +set_tsk_thread_flag (current,TIF_NOTIFY_RESUME );irq_work_queue (this_cpu_ptr (&irq_work_NMI_resume));break ;#endif Does this patch replace patches 1 and 2?
Indeed it does.
Quoted 3 lines
If so, Peter, can you give me a Signed-off-by?
-- Steve
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 3/39 mm: Add guard for mmap_read_lock
In reply to Josh PoimboeufThis is the new way of doing things. Converting all existing mmap_read_lock users is an exercise left for the reader ;-)
--- include/linux/mmap_lock.h | 2 ++ 1 file changed, 2 insertions(+)
include/linux/mmap_lock.h +2 -0
@@ -182,4 +182,6 @@ static inline int mmap_lock_is_contended(struct mm_struct *mm)return rwsem_is_contended (&mm->mmap_lock); } +DEFINE_GUARD(mmap_read_lock, struct mm_struct *, mmap_read_lock(_T), mmap_read_unlock(_T)) +#endif /* _LINUX_MMAP_LOCK_H */
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 4/39 x86/vdso: Fix DWARF generation for getrandom()
In reply to Josh PoimboeufAdd CFI annotations to the VDSO implementation of getrandom() so it will have valid DWARF unwinding metadata.
--- arch/x86/entry/vdso/vgetrandom-chacha.S | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
arch/x86/entry/vdso/vgetrandom-chacha.S +3 -2
@@ -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.48.1
Josh Poimboeuf
maintainerPATCH v4 5/39 x86/asm: Avoid emitting DWARF CFI for non-VDSO
In reply to Josh PoimboeufIt was decided years ago that .cfi_* annotations aren't maintainable in the kernel. They were replaced by objtool unwind hints. For the kernel proper, ensure the CFI_* macros don't do anything.
On the other hand the VDSO library *does* use them, so user space can unwind through it.
Make sure these macros only work for VDSO. They aren't actually being used outside of VDSO anyway, so there's no functional change.
--- arch/x86/include/asm/dwarf2.h | 51 ++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-)
arch/x86/include/asm/dwarf2.h +35 -16
@@ -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,21 +30,31 @@#define CFI_UNDEFINED .cfi_undefined#define CFI_ESCAPE .cfi_escape -#ifndef 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 -* unwinding at runtime. So only the offline DWARF information is -* 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 +#else /* !BUILD_VDSO */ + +/* +* On x86, these macros aren't used outside VDSO. As well they shouldn't be: +* they're fragile and very difficult to maintain. +*/ + +.macro nocfi args:vararg +.endm + +#define CFI_STARTPROC nocfi +#define CFI_ENDPROC nocfi +#define CFI_DEF_CFA nocfi +#define CFI_DEF_CFA_REGISTER nocfi +#define CFI_DEF_CFA_OFFSET nocfi +#define CFI_ADJUST_CFA_OFFSET nocfi +#define CFI_OFFSET nocfi +#define CFI_REL_OFFSET nocfi +#define CFI_REGISTER nocfi +#define CFI_RESTORE nocfi +#define CFI_REMEMBER_STATE nocfi +#define CFI_RESTORE_STATE nocfi +#define CFI_UNDEFINED nocfi +#define CFI_ESCAPE nocfi + +#endif /* !BUILD_VDSO */ #endif /* _ASM_X86_DWARF2_H */
Signature · 1 line
2.48.1
Re: [PATCH v4 05/39] x86/asm: Avoid emitting DWARF CFI for non-VDSO
Quoted 22 lines · Josh Poimboeuf
It was decided years ago that .cfi_* annotations aren't maintainable in the kernel. They were replaced by objtool unwind hints. For the kernel proper, ensure the CFI_* macros don't do anything.
On the other hand the VDSO library *does* use them, so user space can unwind through it.
Make sure these macros only work for VDSO. They aren't actually being used outside of VDSO anyway, so there's no functional change.
Signed-off-by Josh Poimboeuf <[email protected]>arch/x86/include/asm/dwarf2.h +0
-8-#ifndef 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 -* unwinding at runtime. So only the offline DWARF information is -* useful to anyone. Note we should not use this directive if we -* ever decide to enable DWARF unwinding at runtime. -*/
arch/x86/include/asm/dwarf2.h line 34
Quoted 10 lines
arch/x86/include/asm/dwarf2.h +2
-8- .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 +#else /* !BUILD_VDSO */ +
Did you remove ".cfi_sections .debug_frame" on purpose from the !BUILD_VDSO path compared to V3? Presumably to not only not emit DWARF CFI from assembler, but any source?
Quoted 4 lines
+/*
+ * On x86, these macros aren't used outside VDSO. As well they shouldn't be:
+ * they're fragile and very difficult to maintain.
+ */
Thanks and regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 05/39] x86/asm: Avoid emitting DWARF CFI for non-VDSO
Quoted 14 lines · Jens Remus
Quoted 13 lines · Josh Poimboeuf
arch/x86/include/asm/dwarf2.h +0
-13-#ifndef 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 -* unwinding at runtime. So only the offline DWARF information is -* 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.
Quoted 8 lines
arch/x86/include/asm/dwarf2.h line 34
Quoted 5 lines
arch/x86/include/asm/dwarf2.h +2
-3-*/ - .cfi_sections .eh_frame, .debug_frame -#endif +#else /* !BUILD_VDSO */ +Did you remove ".cfi_sections .debug_frame" on purpose from the !BUILD_VDSO path compared to V3?
Yes, since non-VDSO assembly files won't be emitting any .cfi, there's no .debug_frame to output anyway.
Quoted 2 lines
Presumably to not only not emit DWARF CFI from assembler, but any source?
This only impacts assembly files, notice the __ASSEMBLY__ check at the top of the file.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 6/39 x86/asm: Fix VDSO DWARF generation with kernel IBT enabled
In reply to Josh PoimboeufThe 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. As a result the DWARF unwinding info is wrong at the beginning of all 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.
--- 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
@@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ -#include <asm/vdso.h> +#include <asm/page_types.h> #include <asm/vdso/vsyscall.h> /*
arch/x86/entry/vdso/vgetrandom-chacha.S +0 -2
@@ -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
@@ -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
@@ -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
@@ -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.48.1
Josh Poimboeuf
maintainerPATCH v4 7/39 x86/vdso: Use SYM_FUNC_{START,END} in __kernel_vsyscall()
In reply to Josh PoimboeufUse 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
@@ -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.48.1
Josh Poimboeuf
maintainerPATCH v4 8/39 x86/vdso: Use CFI macros in __vdso_sgx_enter_enclave()
In reply to Josh PoimboeufUse 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
@@ -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.48.1
Josh Poimboeuf
maintainerPATCH v4 9/39 x86/vdso: Enable sframe generation in VDSO
In reply to Josh PoimboeufEnable sframe generation in the VDSO library so kernel and user space can unwind through it.
--- arch/Kconfig | 3 +++ arch/x86/entry/vdso/Makefile | 10 +++++++--- arch/x86/entry/vdso/vdso-layout.lds.S | 3 +++ arch/x86/include/asm/dwarf2.h | 5 ++++- 4 files changed, 17 insertions(+), 4 deletions(-)
arch/Kconfig +3 -0
@@ -435,6 +435,9 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH It uses the same command line parameters, and sysctl interface, as the generic hardlockup detectors. +config AS_SFRAME + def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc) + config HAVE_PERF_REGS bool help
arch/x86/entry/vdso/Makefile +7 -3
@@ -47,13 +47,17 @@ quiet_cmd_vdso2c = VDSO2C $@ $(obj)/vdso-image-%.c: $(obj)/vdso%.so.dbg $(obj)/vdso%.so $(obj)/vdso2c FORCE $(call if_changed,vdso2c) +#ifdef CONFIG_AS_SFRAME +SFRAME_CFLAGS := -Wa$(comma)-gsframe +#endif + # # 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 +67,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.
@@ -104,7 +108,7 @@ $(obj)/%-x32.o: $(obj)/%.o FORCE targets += vdsox32.lds $(vobjx32s-y) -$(obj)/%.so: OBJCOPYFLAGS := -S --remove-section __ex_table +$(obj)/%.so: OBJCOPYFLAGS := -g --remove-section __ex_table $(obj)/%.so: $(obj)/%.so.dbg FORCE $(call if_changed,objcopy)
arch/x86/entry/vdso/vdso-layout.lds.S +3 -0
@@ -63,6 +63,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
@@ -91,6 +92,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
@@ -102,4 +104,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
@@ -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.48.1
Re: [PATCH v4 09/39] x86/vdso: Enable sframe generation in VDSO
Quoted 4 lines · Josh Poimboeuf
Enable sframe generation in the VDSO library so kernel and user space can unwind through it.
Signed-off-by Josh Poimboeuf <[email protected]>
...
arch/x86/include/asm/dwarf2.h line 15
Quoted 9 lines
arch/x86/include/asm/dwarf2.h +0
-1@@ -12,8 +12,11 @@* For the vDSO, emit both runtime unwind information and debug * symbols for the .dbg file. */ -
Nit: Deleted blank line you introduced in "[PATCH v4 05/39] x86/asm:
Avoid emitting DWARF CFI for non-VDSO".
arch/x86/include/asm/dwarf2.h line 15
Quoted 1 line
arch/x86/include/asm/dwarf2.h +1
-0+#ifdef __x86_64__
#if defined(__x86_64__) && defined(CONFIG_AS_SFRAME)
AFAIK the kernel has a minimum binutils requirement of 2.25 [1] and assembler option "--gsframe" as well as directive ".cfi_sections .sframe" were introduced with 2.40.
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) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 09/39] x86/vdso: Enable sframe generation in VDSO
Quoted 13 lines · Jens Remus
arch/x86/include/asm/dwarf2.h line 15
Quoted 9 lines · Josh Poimboeuf
arch/x86/include/asm/dwarf2.h +0
-1@@ -12,8 +12,11 @@* For the vDSO, emit both runtime unwind information and debug * symbols for the .dbg file. */ -Nit: Deleted blank line you introduced in "[PATCH v4 05/39] x86/asm:
Avoid emitting DWARF CFI for non-VDSO".
Indeed.
Quoted 7 lines
arch/x86/include/asm/dwarf2.h line 15
Quoted 1 line
arch/x86/include/asm/dwarf2.h +1
-0+#ifdef __x86_64__#if
defined(__x86_64__)&&defined(CONFIG_AS_SFRAME)AFAIK the kernel has a minimum binutils requirement of 2.25 [1] and assembler option "--gsframe" as well as directive ".cfi_sections .sframe" were introduced with 2.40.
True, I'll change it to just '#ifdef CONFIG_AS_SFRAME' since that's what really matters (and 32-bit doesn't support it anyway).
Quoted 6 lines
Quoted 6 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
Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH v4 09/39] x86/vdso: Enable sframe generation in VDSO
Quoted 4 lines · Josh Poimboeuf
Quoted 3 lines · Jens Remus
Quoted 1 line · Josh Poimboeuf
+#ifdef
__x86_64__
Quoted 8 lines
Quoted 5 lines
#if
defined(__x86_64__)&&defined(CONFIG_AS_SFRAME)AFAIK the kernel has a minimum binutils requirement of 2.25 [1] and assembler option "--gsframe" as well as directive ".cfi_sections .sframe" were introduced with 2.40.
True, I'll change it to just '#ifdef
CONFIG_AS_SFRAME' since that's what really matters (and 32-bit doesn't support it anyway).
Actually, just CONFIG_AS_CFAME didn't work as 64-bit still compiles some 32-bit asm files. I'll go with your suggestion.
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH v4 09/39] x86/vdso: Enable sframe generation in VDSO
In reply to Josh PoimboeufQuoted 15 lines · On Fri, 24 Jan 2025 08:43:32 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 13 lines · Jens Remus
arch/x86/include/asm/dwarf2.h line 15
Quoted 9 lines · Josh Poimboeuf
arch/x86/include/asm/dwarf2.h +0
-1@@ -12,8 +12,11 @@* For the vDSO, emit both runtime unwind information and debug * symbols for the .dbg file. */ -Nit: Deleted blank line you introduced in "[PATCH v4 05/39] x86/asm:
Avoid emitting DWARF CFI for non-VDSO".
Quoted 1 line
Indeed.
Note, I'm getting ready to send out an update of Josh's patches.
This particular nit isn't really an issue, as #ifdef's do sometimes replace blank lines.
Just mentioning this so you don't think I'm ignoring it, when I post patches.
-- Steve
Re: [PATCH v4 09/39] x86/vdso: Enable sframe generation in VDSO
In reply to Josh PoimboeufQuoted 8 lines · Josh Poimboeuf
Enable sframe generation in the VDSO library so kernel and user space can unwind through it.
Signed-off-by Josh Poimboeuf <[email protected]>
arch/x86/entry/vdso/Makefile quiet_cmd_vdso2c = VDSO2C $@ · line 53
Quoted 7 lines
arch/x86/entry/vdso/Makefile +4
-0@@ -47,13 +47,17 @@ quiet_cmd_vdso2c = VDSO2C $@ $(obj)/vdso-image-%.c: $(obj)/vdso%.so.dbg $(obj)/vdso%.so $(obj)/vdso2c FORCE $(call if_changed,vdso2c) +#ifdef CONFIG_AS_SFRAME +SFRAME_CFLAGS := -Wa$(comma)-gsframe +#endif +
You probably erroneously mixed up C preprocessor and Makefile syntax? :-)
ifeq ($(CONFIG_AS_SFRAME),y)
SFRAME_CFLAGS := -Wa,--gsframe
endif
$(comma) does not appear to be required in this context.
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 09/39] x86/vdso: Enable sframe generation in VDSO
Quoted 18 lines · Jens Remus
Quoted 8 lines · Josh Poimboeuf
Enable sframe generation in the VDSO library so kernel and user space can unwind through it.
Signed-off-by Josh Poimboeuf <[email protected]>arch/x86/entry/vdso/Makefile quiet_cmd_vdso2c = VDSO2C $@ · line 52
Quoted 6 lines
arch/x86/entry/vdso/Makefile +4
-0@@ -47,13 +47,17 @@ quiet_cmd_vdso2c = VDSO2C $@ $(obj)/vdso-image-%.c: $(obj)/vdso%.so.dbg $(obj)/vdso%.so $(obj)/vdso2c FORCE $(call if_changed,vdso2c) +#ifdef CONFIG_AS_SFRAME +SFRAME_CFLAGS := -Wa$(comma)-gsframe +#endif +You probably erroneously mixed up C preprocessor and
Makefilesyntax? :-)
Quoted 5 lines
ifeq ($(CONFIG_AS_SFRAME),y)
SFRAME_CFLAGS:= -Wa,--gsframe
endif$(comma) does not appear to be required in this context.
Yeah, before it was in a Makefile macro which needed the $(comma) to distinguish it from the macro arguments.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 10/39 x86/uaccess: Add unsafe_copy_from_user() implementation
In reply to Josh PoimboeufAdd an x86 implementation of unsafe_copy_from_user() similar to the existing unsafe_copy_to_user().
--- arch/x86/include/asm/uaccess.h | 39 +++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-)
arch/x86/include/asm/uaccess.h +29 -10
@@ -599,23 +599,42 @@ _label: \* We want the unsafe accessors to always be inlined and use * the error labels - thus the macro games. */ -#define unsafe_copy_loop (dst, src, len, type, label) \ +#define unsafe_copy_to_user_loop (dst, src, len, type, label) \ while (len >= sizeof(type)) { \ - unsafe_put_user(*(type *)(src),(type __user *)(dst),label); \ + unsafe_put_user(*(type *)(src), (type __user *)(dst), label); \ dst += sizeof(type); \ src += sizeof(type); \ len -= sizeof(type); \ } -#define unsafe_copy_to_user (_dst,_src,_len,label) \ +#define unsafe_copy_to_user (_dst, _src, _len, label) \ do { \ - char __user *__ucu_dst = (_dst); \ - const char *__ucu_src = (_src); \ - size_t __ucu_len = (_len); \ - unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u64, label); \ - unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u32, label); \ - unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u16, label); \ - unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u8, label); \ + void __user *__dst = (_dst); \ + const void *__src = (_src); \ + size_t __len = (_len); \ + unsafe_copy_to_user_loop(__dst, __src, __len, u64, label); \ + unsafe_copy_to_user_loop(__dst, __src, __len, u32, label); \ + unsafe_copy_to_user_loop(__dst, __src, __len, u16, label); \ + unsafe_copy_to_user_loop(__dst, __src, __len, u8, label); \ +} while (0) + +#define unsafe_copy_from_user_loop (dst, src, len, type, label) \ + while (len >= sizeof(type)) { \ + unsafe_get_user(*(type *)(dst),(type __user *)(src),label); \ + dst += sizeof(type); \ + src += sizeof(type); \ + len -= sizeof(type); \ + } + +#define unsafe_copy_from_user (_dst, _src, _len, label) \ +do { \ + void *__dst = (_dst); \ + void __user *__src = (_src); \ + size_t __len = (_len); \ + unsafe_copy_from_user_loop(__dst, __src, __len, u64, label); \ + unsafe_copy_from_user_loop(__dst, __src, __len, u32, label); \ + unsafe_copy_from_user_loop(__dst, __src, __len, u16, label); \ + unsafe_copy_from_user_loop(__dst, __src, __len, u8, label); \ } while (0)#ifdef CONFIG_CC_HAS_ASM_GOTO_OUTPUT
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 11/39 unwind_user: Add user space unwinding API
In reply to Josh PoimboeufIntroduce a generic API for unwinding user stacks.
--- arch/Kconfig | 3 ++ include/linux/unwind_user.h | 15 ++++++++ include/linux/unwind_user_types.h | 31 ++++++++++++++++ kernel/Makefile | 1 + kernel/unwind/Makefile | 1 + kernel/unwind/user.c | 59 +++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+) create mode 100644 include/linux/unwind_user.h create mode 100644 include/linux/unwind_user_types.h create mode 100644 kernel/unwind/Makefile create mode 100644 kernel/unwind/user.c
arch/Kconfig +3 -0
@@ -435,6 +435,9 @@ 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 AS_SFRAME def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc)
include/linux/unwind_user.h new file +15 -0
@@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_UNWIND_USER_H +#define _LINUX_UNWIND_USER_H + +#include <linux/unwind_user_types.h> + +int unwind_user_start (struct unwind_user_state *state); +int unwind_user_next (struct unwind_user_state *state); + +int unwind_user (struct unwind_stacktrace *trace,unsigned int max_entries); + +#define for_each_user_frame (state) \ + for (unwind_user_start((state)); !(state)->done; unwind_user_next((state))) + +#endif /* _LINUX_UNWIND_USER_H */
include/linux/unwind_user_types.h new file +31 -0
@@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_UNWIND_USER_TYPES_H +#define _LINUX_UNWIND_USER_TYPES_H + +#include <linux/types.h> + +enum unwind_user_type { +UNWIND_USER_TYPE_NONE , +}; + +struct unwind_stacktrace { +unsigned int nr; +unsigned long *entries; +}; + +struct unwind_user_frame { +s32 cfa_off; +s32 ra_off; +s32 fp_off; +bool use_fp; +}; + +struct unwind_user_state { +unsigned long ip; +unsigned long sp; +unsigned long fp; +enum unwind_user_type type; +bool done; +}; + +#endif /* _LINUX_UNWIND_USER_TYPES_H */
kernel/Makefile +1 -0
@@ -50,6 +50,7 @@ obj-y += rcu/ obj-y += livepatch/ obj-y += dma/ obj-y += entry/ +obj-y += unwind/ obj-$(CONFIG_MODULES) += module/ obj-$(CONFIG_KCMP) += kcmp.o
kernel/unwind/Makefile new file +1 -0
@@ -0,0 +1 @@ + obj-$(CONFIG_UNWIND_USER) += user.o
kernel/unwind/user.c new file +59 -0
59 lines not on this page · raw message
Signature · 1 line
2.48.1
Re: [PATCH v4 11/39] unwind_user: Add user space unwinding API
Quoted 7 lines · Josh Poimboeuf
kernel/unwind/user.c +4
-0@@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Generic interfaces for unwinding user space +*/
kernel/unwind/user.c line 14
Quoted 10 lines
kernel/unwind/user.c +10
-0+#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/sched/task_stack.h> +#include <linux/unwind_user.h> + +int unwind_user_next (struct unwind_user_state *state) +{ +struct unwind_user_frame _frame; +struct unwind_user_frame *frame = &_frame; +unsigned long cfa =0 , fp, ra =0 ;
Why are cfa and ra initialized to zero? Where is that important in subsequent patches?
"[PATCH v4 12/39] unwind_user: Add frame pointer support" does either unconditionally set both cfa and ra or bail out.
kernel/unwind/user.c line 18
Quoted 4 lines
kernel/unwind/user.c +4
-0+ +/* no implementation yet */ + -EINVAL ; +}
Thanks and regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 11/39] unwind_user: Add user space unwinding API
Quoted 10 lines · Jens Remus
kernel/unwind/user.c line 14
Quoted 5 lines · Josh Poimboeuf
kernel/unwind/user.c new file +5
-0+int unwind_user_next (struct unwind_user_state *state) +{ +struct unwind_user_frame _frame; +struct unwind_user_frame *frame = &_frame; +unsigned long cfa =0 , fp, ra =0 ;Why are cfa and ra initialized to zero? Where is that important in subsequent patches?
Quoted 2 lines
"[PATCH v4 12/39]
unwind_user: Add frame pointer support" does either unconditionally set both cfa and ra or bail out.
Right, probably leftovers from some previous iteration. I drop those.
Signature · 1 line
Josh
Re: [PATCH v4 11/39] unwind_user: Add user space unwinding API
In reply to Josh PoimboeufQuoted 17 lines · Josh Poimboeuf
Introduce a generic API for unwinding user stacks.
Signed-off-by Josh Poimboeuf <[email protected]>--- arch/Kconfig | 3 ++ include/linux/unwind_user.h | 15 ++++++++ include/linux/unwind_user_types.h | 31 ++++++++++++++++ kernel/Makefile | 1 + kernel/unwind/Makefile | 1 + kernel/unwind/user.c | 59 +++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+) create mode 100644 include/linux/unwind_user.h create mode 100644 include/linux/unwind_user_types.h create mode 100644 kernel/unwind/Makefile create mode 100644 kernel/unwind/user.c
[...]
Quoted 7 lines
kernel/unwind/user.c new file +4
-0@@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Generic interfaces for unwinding user space +*/
kernel/unwind/user.c line 14
Quoted 10 lines
kernel/unwind/user.c new file +10
-0+#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/sched/task_stack.h> +#include <linux/unwind_user.h> + +int unwind_user_next (struct unwind_user_state *state) +{ +struct unwind_user_frame _frame; +struct unwind_user_frame *frame = &_frame; +unsigned long cfa =0 , fp, ra =0 ;
wouldn't all the above generate compilation warnings about unused variables, potentially breaking bisection?
kernel/unwind/user.c line 17
Quoted 3 lines
kernel/unwind/user.c new file +3
-0+ +/* no implementation yet */ + -EINVAL ;
return missing?
Quoted 1 line
kernel/unwind/user.c new file +1
-0+}
[...]
Josh Poimboeuf
maintainerRe: [PATCH v4 11/39] unwind_user: Add user space unwinding API
Quoted 9 lines · Andrii Nakryiko
kernel/unwind/user.c line 14
Quoted 5 lines · Josh Poimboeuf
kernel/unwind/user.c new file +5
-0+int unwind_user_next (struct unwind_user_state *state) +{ +struct unwind_user_frame _frame; +struct unwind_user_frame *frame = &_frame; +unsigned long cfa =0 , fp, ra =0 ;wouldn't all the above generate compilation warnings about unused variables, potentially breaking bisection?
It doesn't break bisection because no arches support UNWIND_USER yet so this can't yet be compiled.
But yeah, it's shoddy and I fixed it after Jens' comment about the unnecessary zero-initialized variables.
Quoted 6 lines
kernel/unwind/user.c line 17
Quoted 4 lines
kernel/unwind/user.c new file +3
-0+ +/* no implementation yet */ + -EINVAL ;return missing?
heh :-)
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH v4 11/39] unwind_user: Add user space unwinding API
In reply to Josh PoimboeufQuoted 20 lines · On Tue, 21 Jan 2025 18:31:03 -0800 Josh Poimboeuf <[email protected]> wrote:
kernel/unwind/user.c new file +20
-0+int unwind_user_next (struct unwind_user_state *state) +{ +struct unwind_user_frame _frame; +struct unwind_user_frame *frame = &_frame; +unsigned long cfa =0 , fp, ra =0 ; + +/* no implementation yet */ + -EINVAL ; +} + +int unwind_user_start (struct unwind_user_state *state) +{ +struct pt_regs *regs =task_pt_regs (current); + +memset (state,0 ,sizeof (*state)); + +if (!current->mm || !user_mode (regs)) { + state->done = true; +return -EINVAL ; + }
kernel/unwind/user.c line 39
Quoted 10 lines
kernel/unwind/user.c new file +10
-0+ + state->type =UNWIND_USER_TYPE_NONE ; + + state->ip =instruction_pointer (regs); + state->sp =user_stack_pointer (regs); + state->fp =frame_pointer (regs); + +return 0 ; +} +
I know this is just an introductory of the interface, but this should really have kerneldoc attached to it, as I have no idea what these are supposed to be doing. This patch is meaningless without it. The change log is useless too.
-- Steve
Quoted 20 lines
kernel/unwind/user.c new file +20
-0+int unwind_user (struct unwind_stacktrace *trace,unsigned int max_entries) +{ +struct unwind_user_state state; + + trace->nr =0 ; + +if (!max_entries) +return -EINVAL ; + +if (!current->mm) +return 0 ; + +for_each_user_frame (&state) { + trace->entries[trace->nr++] = state.ip; +if (trace->nr >= max_entries) +break ; + } + +return 0 ; +}
Josh Poimboeuf
maintainerRe: [PATCH v4 11/39] unwind_user: Add user space unwinding API
Quoted 19 lines · Steven Rostedt
Quoted 17 lines · On Tue, 21 Jan 2025 18:31:03 -0800 Josh Poimboeuf <[email protected]> wrote:
kernel/unwind/user.c new file +17
-0+int unwind_user_start (struct unwind_user_state *state) +{ +struct pt_regs *regs =task_pt_regs (current); + +memset (state,0 ,sizeof (*state)); + +if (!current->mm || !user_mode (regs)) { + state->done = true; +return -EINVAL ; + } + + state->type =UNWIND_USER_TYPE_NONE ; + + state->ip =instruction_pointer (regs); + state->sp =user_stack_pointer (regs); + state->fp =frame_pointer (regs); +
Quoted 8 lines
kernel/unwind/user.c line 39
Quoted 3 lines
kernel/unwind/user.c new file +3
-0+return 0 ; +} +I know this is just an introductory of the interface, but this should really have kerneldoc attached to it, as I have no idea what these are supposed to be doing. This patch is meaningless without it. The change log is useless too.
Yeah, sure.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 12/39 unwind_user: Add frame pointer support
In reply to Josh PoimboeufAdd optional support for user space frame pointer unwinding. If supported, the arch needs to enable CONFIG_HAVE_UNWIND_USER_FP and define ARCH_INIT_USER_FP_FRAME.
By encoding the frame offsets in struct unwind_user_frame, much of this code can also be reused for future unwinder implementations like sframe.
--- arch/Kconfig | 4 +++ include/asm-generic/unwind_user.h | 9 ++++++ include/linux/unwind_user_types.h | 1 + kernel/unwind/user.c | 49 +++++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 include/asm-generic/unwind_user.h
arch/Kconfig +4 -0
@@ -438,6 +438,10 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH config UNWIND_USER bool +config HAVE_UNWIND_USER_FP + bool + select UNWIND_USER + config AS_SFRAME def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc)
include/asm-generic/unwind_user.h new file +9 -0
@@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_GENERIC_UNWIND_USER_H +#define _ASM_GENERIC_UNWIND_USER_H + +#ifndef ARCH_INIT_USER_FP_FRAME +#define ARCH_INIT_USER_FP_FRAME +#endif + +#endif /* _ASM_GENERIC_UNWIND_USER_H */
include/linux/unwind_user_types.h +1 -0
@@ -6,6 +6,7 @@enum unwind_user_type {UNWIND_USER_TYPE_NONE , +UNWIND_USER_TYPE_FP , };struct unwind_stacktrace {
kernel/unwind/user.c +46 -3
67 lines not on this page · raw message
Signature · 1 line
2.48.1
Re: [PATCH v4 12/39] unwind_user: Add frame pointer support
Quoted 8 lines · Josh Poimboeuf
Add optional support for user space frame pointer unwinding. If supported, the arch needs to enable
CONFIG_HAVE_UNWIND_USER_FPand defineARCH_INIT_USER_FP_FRAME.By encoding the frame offsets in struct
unwind_user_frame, much of this code can also be reused for future unwinder implementations like sframe.
Quoted 9 lines
Signed-off-by Josh Poimboeuf <[email protected]>--- arch/Kconfig | 4 +++ include/asm-generic/unwind_user.h | 9 ++++++ include/linux/unwind_user_types.h | 1 + kernel/unwind/user.c | 49 +++++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 include/asm-generic/unwind_user.h
Do you plan to reuse this logic for stack unwinding done by perf subsystem in perf_callchain_user()? See is_uprobe_at_func_entry() parts and also fixup_uretprobe_trampoline_entries() for some of the quirks that have to be taken into account when doing frame pointer-based unwinding. It would be great not to lose those in this new reimplementation.
Not sure what's the best way to avoid duplicating the logic, but I thought I'd bring that up.
Quoted 15 lines
arch/Kconfig +4
-0@@ -438,6 +438,10 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH config UNWIND_USER bool +config HAVE_UNWIND_USER_FP + bool + select UNWIND_USER + config AS_SFRAME def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc)
[...]
Josh Poimboeuf
maintainerRe: [PATCH v4 12/39] unwind_user: Add frame pointer support
Quoted 26 lines · Andrii Nakryiko
Quoted 8 lines · Josh Poimboeuf
Add optional support for user space frame pointer unwinding. If supported, the arch needs to enable
CONFIG_HAVE_UNWIND_USER_FPand defineARCH_INIT_USER_FP_FRAME.By encoding the frame offsets in struct
unwind_user_frame, much of this code can also be reused for future unwinder implementations like sframe.Quoted 9 lines
Signed-off-by Josh Poimboeuf <[email protected]>--- arch/Kconfig | 4 +++ include/asm-generic/unwind_user.h | 9 ++++++ include/linux/unwind_user_types.h | 1 + kernel/unwind/user.c | 49 +++++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 include/asm-generic/unwind_user.hDo you plan to reuse this logic for stack unwinding done by perf subsystem in
perf_callchain_user()? Seeis_uprobe_at_func_entry()parts and alsofixup_uretprobe_trampoline_entries()for some of the quirks that have to be taken into account when doing frame pointer-based unwinding. It would be great not to lose those in this new reimplementation.
Quoted 2 lines
Not sure what's the best way to avoid duplicating the logic, but I thought I'd bring that up.
Indeed! That was on the todo list and somehow evaporated.
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH v4 12/39] unwind_user: Add frame pointer support
Quoted 11 lines · On Fri, 24 Jan 2025 10:16:10 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 9 lines
Do you plan to reuse this logic for stack unwinding done by perf subsystem in
perf_callchain_user()? Seeis_uprobe_at_func_entry()parts and alsofixup_uretprobe_trampoline_entries()for some of the quirks that have to be taken into account when doing frame pointer-based unwinding. It would be great not to lose those in this new reimplementation.Not sure what's the best way to avoid duplicating the logic, but I thought I'd bring that up.
Indeed! That was on the todo list and somehow evaporated.
I'm getting ready to post an update of these patches, but I want to mention that this has not been addressed, and I'm replying here to make sure it stays on the radar.
-- Steve
Josh Poimboeuf
maintainerPATCH v4 13/39 unwind_user/x86: Enable frame pointer unwinding on x86
In reply to Josh PoimboeufUse 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
@@ -291,6 +291,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
@@ -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 \ + .cfa_off = (s32)sizeof(long) * 2, \ + .ra_off = (s32)sizeof(long) * -1, \ + .fp_off = (s32)sizeof(long) * -2, \ + .use_fp = true, + +#endif /* _ASM_X86_UNWIND_USER_H */
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 14/39 perf/x86: Rename get_segment_base() and make it global
In reply to Josh Poimboeufget_segment_base() will be used by the unwind_user code, so make it global and rename it so it doesn't conflict with a KVM function of the same name.
--- arch/x86/events/core.c | 10 +++++----- arch/x86/include/asm/perf_event.h | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-)
arch/x86/events/core.c +5 -5
@@ -2790,7 +2790,7 @@ valid_user_frame(const void __user *fp, unsigned long size)return __access_ok (fp, size); } -static unsigned long get_segment_base (unsigned int segment) +unsigned long segment_base_address (unsigned int segment) {struct desc_struct *desc;unsigned int idx = segment >>3 ;
@@ -2874,8 +2874,8 @@ perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry_ctx *entif (user_64bit_mode (regs))return 0 ; - cs_base =get_segment_base (regs->cs); - ss_base =get_segment_base (regs->ss); + cs_base =segment_base_address (regs->cs); + ss_base =segment_base_address (regs->ss); fp =compat_ptr (ss_base + regs->bp);pagefault_disable ();
@@ -2994,11 +2994,11 @@ static unsigned long code_segment_base(struct pt_regs *regs)return 0x10 * regs->cs;if (user_mode (regs) && regs->cs != __USER_CS) -return get_segment_base (regs->cs); +return segment_base_address (regs->cs);#else if (user_mode (regs) && !user_64bit_mode (regs) && regs->cs != __USER32_CS) -return get_segment_base (regs->cs); +return segment_base_address (regs->cs);#endif return 0 ; }
arch/x86/include/asm/perf_event.h +2 -0
@@ -639,4 +639,6 @@ static __always_inline void perf_lopwr_cb(bool lopwr_in)#define arch_perf_out_copy_user copy_from_user_nmi +unsigned long segment_base_address (unsigned int segment); +#endif /* _ASM_X86_PERF_EVENT_H */
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 14/39] perf/x86: Rename get_segment_base() and make it global
Quoted 3 lines · Josh Poimboeuf
get_segment_base()will be used by theunwind_usercode, so make it global and rename it so it doesn't conflict with a KVM function of the same name.
Should it not also get moved out of the perf code in this case?
Josh Poimboeuf
maintainerRe: [PATCH v4 14/39] perf/x86: Rename get_segment_base() and make it global
Quoted 6 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
get_segment_base()will be used by theunwind_usercode, so make it global and rename it so it doesn't conflict with a KVM function of the same name.Should it not also get moved out of the perf code in this case?
True, I'll try to find it a better home.
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH v4 14/39] perf/x86: Rename get_segment_base() and make it global
In reply to Josh Poimboeufarch/x86/events/core.c valid_user_frame(const void __user *fp, unsigned long size) · line 2796
Quoted 13 lines · On Tue, 21 Jan 2025 18:31:06 -0800 Josh Poimboeuf <[email protected]> wrote:
arch/x86/events/core.c +1
-1@@ -2790,7 +2790,7 @@ valid_user_frame(const void __user *fp, unsigned long size)return __access_ok (fp, size); } -static unsigned long get_segment_base (unsigned int segment) +unsigned long segment_base_address (unsigned int segment) {struct desc_struct *desc;unsigned int idx = segment >>3 ;
As this requires interrupts disabled, and if you do move this out of this file, you probably should add:
lockdep_assert_irqs_disabled();
-- Steve
Josh Poimboeuf
maintainerRe: [PATCH v4 14/39] perf/x86: Rename get_segment_base() and make it global
Quoted 20 lines · Steven Rostedt
arch/x86/events/core.c valid_user_frame(const void __user *fp, unsigned long size) · line 2796
Quoted 13 lines · On Tue, 21 Jan 2025 18:31:06 -0800 Josh Poimboeuf <[email protected]> wrote:
arch/x86/events/core.c +1
-1@@ -2790,7 +2790,7 @@ valid_user_frame(const void __user *fp, unsigned long size)return __access_ok (fp, size); } -static unsigned long get_segment_base (unsigned int segment) +unsigned long segment_base_address (unsigned int segment) {struct desc_struct *desc;unsigned int idx = segment >>3 ;As this requires interrupts disabled, and if you do move this out of this file, you probably should add:
Quoted 1 line
lockdep_assert_irqs_disabled();
Indeed...
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 15/39 unwind_user: Add compat mode frame pointer support
In reply to Josh PoimboeufAdd optional support for user space compat mode frame pointer unwinding. If supported, the arch needs to enable CONFIG_HAVE_UNWIND_USER_COMPAT_FP and define ARCH_INIT_USER_COMPAT_FP_FRAME.
--- arch/Kconfig | 4 +++ include/asm-generic/Kbuild | 2 ++ include/asm-generic/unwind_user.h | 15 +++++++++++ include/asm-generic/unwind_user_types.h | 9 +++++++ include/linux/unwind_user_types.h | 3 +++ kernel/unwind/user.c | 36 ++++++++++++++++++++++--- 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 include/asm-generic/unwind_user_types.h
arch/Kconfig +4 -0
@@ -442,6 +442,10 @@ config HAVE_UNWIND_USER_FP bool select UNWIND_USER +config HAVE_UNWIND_USER_COMPAT_FP + bool + depends on HAVE_UNWIND_USER_FP + config AS_SFRAME def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc)
include/asm-generic/Kbuild +2 -0
@@ -58,6 +58,8 @@ mandatory-y += tlbflush.h mandatory-y += topology.h mandatory-y += trace_clock.h mandatory-y += uaccess.h +mandatory-y += unwind_user.h +mandatory-y += unwind_user_types.h mandatory-y += vermagic.h mandatory-y += vga.h mandatory-y += video.h
include/asm-generic/unwind_user.h +15 -0
@@ -2,8 +2,23 @@#ifndef _ASM_GENERIC_UNWIND_USER_H#define _ASM_GENERIC_UNWIND_USER_H +#include <asm/unwind_user_types.h> +#ifndef ARCH_INIT_USER_FP_FRAME #define ARCH_INIT_USER_FP_FRAME #endif +#ifndef ARCH_INIT_USER_COMPAT_FP_FRAME +#define ARCH_INIT_USER_COMPAT_FP_FRAME +#define in_compat_mode (regs) false +#endif + +#ifndef arch_unwind_user_init +static inline void arch_unwind_user_init (struct unwind_user_state *state,struct pt_regs *reg) {} +#endif + +#ifndef arch_unwind_user_next +static inline void arch_unwind_user_next (struct unwind_user_state *state) {} +#endif +#endif /* _ASM_GENERIC_UNWIND_USER_H */
include/asm-generic/unwind_user_types.h new file +9 -0
@@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_GENERIC_UNWIND_USER_TYPES_H +#define _ASM_GENERIC_UNWIND_USER_TYPES_H + +#ifndef arch_unwind_user_state +struct arch_unwind_user_state {}; +#endif + +#endif /* _ASM_GENERIC_UNWIND_USER_TYPES_H */
include/linux/unwind_user_types.h +3 -0
@@ -3,10 +3,12 @@#define _LINUX_UNWIND_USER_TYPES_H#include <linux/types.h> +#include <asm/unwind_user_types.h> enum unwind_user_type {UNWIND_USER_TYPE_NONE ,UNWIND_USER_TYPE_FP , +UNWIND_USER_TYPE_COMPAT_FP , };struct unwind_stacktrace {
@@ -25,6 +27,7 @@ struct unwind_user_state {unsigned long ip;unsigned long sp;unsigned long fp; +struct arch_unwind_user_state arch;enum unwind_user_type type;bool done; };
kernel/unwind/user.c +32 -4
@@ -13,12 +13,32 @@ static struct unwind_user_frame fp_frame = { ARCH_INIT_USER_FP_FRAME }; +static struct unwind_user_frame compat_fp_frame = { +ARCH_INIT_USER_COMPAT_FP_FRAME +}; +static inline bool fp_state (struct unwind_user_state *state) {return IS_ENABLED (CONFIG_HAVE_UNWIND_USER_FP ) && state->type ==UNWIND_USER_TYPE_FP ; } +static inline bool compat_state (struct unwind_user_state *state) +{ +return IS_ENABLED (CONFIG_HAVE_UNWIND_USER_COMPAT_FP ) && + state->type ==UNWIND_USER_TYPE_COMPAT_FP ; +} + +#define UNWIND_GET_USER_LONG (to, from, state) \ +({ \ + int __ret; \ + if (compat_state(state)) \ + __ret = get_user(to, (u32 __user *)(from)); \ + else \ + __ret = get_user(to, (u64 __user *)(from)); \ + __ret; \ +}) +int unwind_user_next (struct unwind_user_state *state) {struct unwind_user_frame _frame;
@@ -28,7 +48,9 @@ int unwind_user_next(struct unwind_user_state *state)if (state->done)return -EINVAL ; -if (fp_state (state)) +if (compat_state (state)) + frame = &compat_fp_frame; +else if (fp_state (state)) frame = &fp_frame;else goto the_end;
@@ -39,10 +61,10 @@ int unwind_user_next(struct unwind_user_state *state)if (cfa <= state->sp) goto the_end; -if (get_user (ra, (unsigned long *)(cfa + frame->ra_off))) +if (UNWIND_GET_USER_LONG (ra, cfa + frame->ra_off, state)) goto the_end; -if (frame->fp_off &&get_user (fp, (unsigned long __user *)(cfa + frame->fp_off))) +if (frame->fp_off &&UNWIND_GET_USER_LONG (fp, cfa + frame->fp_off, state)) goto the_end; state->ip = ra;
@@ -50,6 +72,8 @@ int unwind_user_next(struct unwind_user_state *state)if (frame->fp_off) state->fp = fp; +arch_unwind_user_next (state); +return 0 ; the_end:
@@ -68,7 +92,9 @@ int unwind_user_start(struct unwind_user_state *state)return -EINVAL ; } -if (IS_ENABLED (CONFIG_HAVE_UNWIND_USER_FP )) +if (IS_ENABLED (CONFIG_HAVE_UNWIND_USER_COMPAT_FP ) &&in_compat_mode (regs)) + state->type =UNWIND_USER_TYPE_COMPAT_FP ; +else if (IS_ENABLED (CONFIG_HAVE_UNWIND_USER_FP )) state->type =UNWIND_USER_TYPE_FP ;else state->type =UNWIND_USER_TYPE_NONE ;
@@ -77,6 +103,8 @@ int unwind_user_start(struct unwind_user_state *state) state->sp =user_stack_pointer (regs); state->fp =frame_pointer (regs); +arch_unwind_user_init (state, regs); +return 0 ; }
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 16/39 unwind_user/x86: Enable compat mode frame pointer unwinding on x86
In reply to Josh PoimboeufUse ARCH_INIT_USER_COMPAT_FP_FRAME to describe how frame pointers are unwound on x86, and implement the hooks needed to add the segment base addresses. Enable HAVE_UNWIND_USER_COMPAT_FP if the system has compat mode compiled in.
--- arch/x86/Kconfig | 1 + arch/x86/include/asm/unwind_user.h | 50 ++++++++++++++++++++++++ arch/x86/include/asm/unwind_user_types.h | 17 ++++++++ 3 files changed, 68 insertions(+) create mode 100644 arch/x86/include/asm/unwind_user_types.h
arch/x86/Kconfig +1 -0
@@ -291,6 +291,7 @@ config X86 select HAVE_SYSCALL_TRACEPOINTS select HAVE_UACCESS_VALIDATION if HAVE_OBJTOOL select HAVE_UNSTABLE_SCHED_CLOCK + select HAVE_UNWIND_USER_COMPAT_FP if IA32_EMULATION select HAVE_UNWIND_USER_FP if X86_64 select HAVE_USER_RETURN_NOTIFIER select HAVE_GENERIC_VDSO
arch/x86/include/asm/unwind_user.h +50 -0
60 lines not on this page · raw message
arch/x86/include/asm/unwind_user_types.h new file +17 -0
@@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_UNWIND_USER_TYPES_H +#define _ASM_UNWIND_USER_TYPES_H + +#ifdef CONFIG_IA32_EMULATION + +struct arch_unwind_user_state { +unsigned long ss_base; +unsigned long cs_base; +}; +#define arch_unwind_user_state arch_unwind_user_state + +#endif /* CONFIG_IA32_EMULATION */ + +#include <asm-generic/unwind_user_types.h> + +#endif /* _ASM_UNWIND_USER_TYPES_H */
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 17/39 unwind_user/sframe: Add support for reading .sframe headers
In reply to Josh PoimboeufIn preparation for unwinding user space stacks with sframe, add basic sframe compile infrastructure and support for reading the .sframe section header.
sframe_add_section() reads the header and unconditionally returns an error, so it's not very useful yet. A subsequent patch will improve that.
--- arch/Kconfig | 3 + include/linux/sframe.h | 36 +++++++++++ kernel/unwind/Makefile | 3 +- kernel/unwind/sframe.c | 136 +++++++++++++++++++++++++++++++++++++++++ kernel/unwind/sframe.h | 71 +++++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 include/linux/sframe.h create mode 100644 kernel/unwind/sframe.c create mode 100644 kernel/unwind/sframe.h
arch/Kconfig +3 -0
@@ -446,6 +446,9 @@ config HAVE_UNWIND_USER_COMPAT_FP bool depends on HAVE_UNWIND_USER_FP +config HAVE_UNWIND_USER_SFRAME + bool + config AS_SFRAME def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc)
include/linux/sframe.h new file +36 -0
@@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_SFRAME_H +#define _LINUX_SFRAME_H + +#include <linux/mm_types.h> +#include <linux/unwind_user_types.h> + +#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME + +struct sframe_section { +unsigned long sframe_start; +unsigned long sframe_end; +unsigned long text_start; +unsigned long text_end; + +unsigned long fdes_start; +unsigned long fres_start; +unsigned long fres_end; +unsigned int num_fdes; + +signed char ra_off; +signed char fp_off; +}; + +extern int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end, +unsigned long text_start,unsigned long text_end); +extern int sframe_remove_section (unsigned long sframe_addr); + +#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */ + +static inline int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end) {return -ENOSYS ; } +static inline int sframe_remove_section (unsigned long sframe_addr) {return -ENOSYS ; } + +#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */ + +#endif /* _LINUX_SFRAME_H */
kernel/unwind/Makefile +2 -1
@@ -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 +136 -0
136 lines not on this page · raw message
kernel/unwind/sframe.h new file +71 -0
71 lines not on this page · raw message
Signature · 1 line
2.48.1
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 21 lines · Josh Poimboeuf
In preparation for unwinding user space stacks with sframe, add basic sframe compile infrastructure and support for reading the .sframe section header.
sframe_add_section()reads the header and unconditionally returns an error, so it's not very useful yet. A subsequent patch will improve that.Signed-off-by Josh Poimboeuf <[email protected]>--- arch/Kconfig | 3 + include/linux/sframe.h | 36 +++++++++++ kernel/unwind/Makefile | 3 +- kernel/unwind/sframe.c | 136 +++++++++++++++++++++++++++++++++++++++++ kernel/unwind/sframe.h | 71 +++++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 include/linux/sframe.h create mode 100644 kernel/unwind/sframe.c create mode 100644 kernel/unwind/sframe.h
[...]
include/linux/sframe.h line 31
Quoted 8 lines
include/linux/sframe.h new file +8
-0+ +extern int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end, +unsigned long text_start,unsigned long text_end); +extern int sframe_remove_section (unsigned long sframe_addr); + +#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */ + +static inline int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end) {return -ENOSYS ; }
nit: very-very long, wrap it?
Quoted 5 lines
include/linux/sframe.h new file +5
-0+static inline int sframe_remove_section (unsigned long sframe_addr) {return -ENOSYS ; } + +#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */ + +#endif /* _LINUX_SFRAME_H */
[...]
Quoted 4 lines
kernel/unwind/sframe.c new file +4
-0+static int sframe_read_header (struct sframe_section *sec) +{ +unsigned long header_end, fdes_start, fdes_end, fres_start, fres_end; +struct sframe_header shdr;
kernel/unwind/sframe.c line 40
Quoted 10 lines
kernel/unwind/sframe.c new file +10
-0+unsigned int num_fdes; + +if (copy_from_user (&shdr, (void __user *)sec->sframe_start,sizeof (shdr))) { +dbg ("header usercopy failed\n" ); +return -EFAULT ; + } + + if (shdr.preamble.magic != SFRAME_MAGIC || + shdr.preamble.version != SFRAME_VERSION_2 || + !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||
probably more a question to Indu, but why is this sorting not mandatory and part of SFrame "standard"? How realistically non-sorted FDEs would work in practice? Ain't nobody got time to sort them just to unwind the stack...
kernel/unwind/sframe.c line 46
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+ shdr.auxhdr_len) { +dbg ("bad/unsupported sframe header\n" ); +return -EINVAL ; + } + +if (!shdr.num_fdes || !shdr.num_fres) {
given SFRAME_F_FRAME_POINTER in the header, is it really that nonsensical and illegal to have zero FDEs/FREs? Maybe we should allow that?
kernel/unwind/sframe.c line 52
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {
if we allow zero FDEs/FREs, header_end == sec->sframe_end is legal, right?
kernel/unwind/sframe.c line 63
Quoted 11 lines
kernel/unwind/sframe.c new file +11
-0+dbg ("header doesn't fit in section\n" ); +return -EINVAL ; + } + + num_fdes = shdr.num_fdes; + fdes_start = header_end + shdr.fdes_off; + fdes_end = fdes_start + (num_fdes *sizeof (struct sframe_fde )); + + fres_start = header_end + shdr.fres_off; + fres_end = fres_start + shdr.fre_len; +
maybe use check_add_overflow() in all the above calculation, at least on 32-bit arches this all can overflow and it's not clear if below sanity check detects all possible overflows
Quoted 16 lines
kernel/unwind/sframe.c new file +16
-0+if (fres_start < fdes_end || fres_end > sec->sframe_end) { +dbg ("inconsistent fde/fre offsets\n" ); +return -EINVAL ; + } + + sec->num_fdes = num_fdes; + sec->fdes_start = fdes_start; + sec->fres_start = fres_start; + sec->fres_end = fres_end; + + sec->ra_off = shdr.cfa_fixed_ra_offset; + sec->fp_off = shdr.cfa_fixed_fp_offset; + +return 0 ; +} +
[...]
Quoted 53 lines
kernel/unwind/sframe.h new file +47
-0@@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* +* From https://www.sourceware.org/binutils/docs/sframe-spec.html +*/ +#ifndef _SFRAME_H +#define _SFRAME_H + +#include <linux/types.h> + +#define SFRAME_VERSION_1 1 +#define SFRAME_VERSION_2 2 +#define SFRAME_MAGIC 0xdee2 + +#define SFRAME_F_FDE_SORTED 0x1 +#define SFRAME_F_FRAME_POINTER 0x2 + +#define SFRAME_ABI_AARCH64_ENDIAN_BIG 1 +#define SFRAME_ABI_AARCH64_ENDIAN_LITTLE 2 +#define SFRAME_ABI_AMD64_ENDIAN_LITTLE 3 + +#define SFRAME_FDE_TYPE_PCINC 0 +#define SFRAME_FDE_TYPE_PCMASK 1 + +struct sframe_preamble { +u16 magic; +u8 version; +u8 flags; +} __packed; + +struct sframe_header { +struct sframe_preamble preamble; +u8 abi_arch; +s8 cfa_fixed_fp_offset; +s8 cfa_fixed_ra_offset; +u8 auxhdr_len; +u32 num_fdes; +u32 num_fres; +u32 fre_len; +u32 fdes_off; +u32 fres_off; +} __packed; + +#define SFRAME_HEADER_SIZE (header) \ + ((sizeof(struct sframe_header) + header.auxhdr_len)) + +#define SFRAME_AARCH64_PAUTH_KEY_A 0 +#define SFRAME_AARCH64_PAUTH_KEY_B 1
kernel/unwind/sframe.h line 57
Quoted 10 lines
kernel/unwind/sframe.h new file +10
-0+ +struct sframe_fde { +s32 start_addr; +u32 func_size; +u32 fres_off; +u32 fres_num; +u8 info; +u8 rep_size; +u16 padding; +} __packed;
I couldn't understand from SFrame itself, but why do sframe_header, sframe_preamble, and sframe_fde have to be marked __packed, if it's all naturally aligned (intentionally and by design)?..
Quoted 17 lines
kernel/unwind/sframe.h new file +14
-1+ +#define SFRAME_FUNC_FRE_TYPE (data) (data & 0xf) +#define SFRAME_FUNC_FDE_TYPE (data) ((data >> 4) & 0x1) +#define SFRAME_FUNC_PAUTH_KEY (data) ((data >> 5) & 0x1) + +#define SFRAME_BASE_REG_FP 0 +#define SFRAME_BASE_REG_SP 1 + +#define SFRAME_FRE_CFA_BASE_REG_ID (data) (data & 0x1) +#define SFRAME_FRE_OFFSET_COUNT (data) ((data >> 1) & 0xf) +#define SFRAME_FRE_OFFSET_SIZE (data) ((data >> 5) & 0x3) +#define SFRAME_FRE_MANGLED_RA_P (data) ((data >> 7) & 0x1) + +#endif /* _SFRAME_H */ --2.48.1
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 4 lines · Andrii Nakryiko
Quoted 1 line · Josh Poimboeuf
+static inline int sframe_add_section(unsigned long
sframe_start, unsigned longsframe_end, unsigned longtext_start, unsigned longtext_end) { return-ENOSYS; }nit: very-very long, wrap it?
That was intentional as it's just an empty stub, but yeah, maybe 160 chars is a bit much.
Quoted 8 lines
kernel/unwind/sframe.c line 40
Quoted 3 lines
kernel/unwind/sframe.c new file +3
-0+ if (shdr.preamble.magic != SFRAME_MAGIC || + shdr.preamble.version != SFRAME_VERSION_2 || + !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||probably more a question to Indu, but why is this sorting not mandatory and part of SFrame "standard"? How realistically non-sorted FDEs would work in practice? Ain't nobody got time to sort them just to unwind the stack...
No idea...
Quoted 5 lines
Quoted 1 line
+ if (!shdr.num_fdes || !shdr.num_fres) {
given
SFRAME_F_FRAME_POINTERin the header, is it really that nonsensical and illegal to have zero FDEs/FREs? Maybe we should allow that?
It would seem a bit silly to create an empty .sframe section just to set that SFRAME_F_FRAME_POINTER bit. Regardless, there's nothing the kernel can do with that.
Quoted 8 lines
kernel/unwind/sframe.c line 52
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?
I suppose so, but again I'm not seeing any reason to support that.
Quoted 7 lines
Quoted 7 lines
kernel/unwind/sframe.c new file +7
-0+dbg ("header doesn't fit in section\n" ); +return -EINVAL ; + } + + num_fdes = shdr.num_fdes; + fdes_start = header_end + shdr.fdes_off; + fdes_end = fdes_start + (num_fdes *sizeof (struct sframe_fde ));
Quoted 8 lines
kernel/unwind/sframe.c line 63
Quoted 4 lines
kernel/unwind/sframe.c new file +4
-0+ + fres_start = header_end + shdr.fres_off; + fres_end = fres_start + shdr.fre_len; +maybe use
check_add_overflow()in all the above calculation, at least on 32-bit arches this all can overflow and it's not clear if below sanity check detects all possible overflows
Ok, I'll look into it.
Quoted 24 lines
Quoted 24 lines
kernel/unwind/sframe.h new file +24
-0+struct sframe_preamble { +u16 magic; +u8 version; +u8 flags; +} __packed; + +struct sframe_header { +struct sframe_preamble preamble; +u8 abi_arch; +s8 cfa_fixed_fp_offset; +s8 cfa_fixed_ra_offset; +u8 auxhdr_len; +u32 num_fdes; +u32 num_fres; +u32 fre_len; +u32 fdes_off; +u32 fres_off; +} __packed; + +struct sframe_fde { +s32 start_addr; +u32 func_size; +u32 fres_off; +u32 fres_num;
Quoted 8 lines
kernel/unwind/sframe.h line 57
Quoted 4 lines
kernel/unwind/sframe.h new file +4
-0+u8 info; +u8 rep_size; +u16 padding; +} __packed;I couldn't understand from SFrame itself, but why do
sframe_header,sframe_preamble, andsframe_fdehave to be marked__packed, if it's all naturally aligned (intentionally and by design)?..
Right, but the spec says they're all packed. Maybe the point is that some future sframe version is free to introduce unaligned fields.
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 15 lines · On Fri, 24 Jan 2025 11:21:59 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 3 lines
given
SFRAME_F_FRAME_POINTERin the header, is it really that nonsensical and illegal to have zero FDEs/FREs? Maybe we should allow that?It would seem a bit silly to create an empty .sframe section just to set that
SFRAME_F_FRAME_POINTERbit. Regardless, there's nothing the kernel can do with that.Quoted 7 lines
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {
Quoted 3 lines
Quoted 1 line
if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?I suppose so, but again I'm not seeing any reason to support that.
Hmm, could that be useful for implementing a way to dynamically grow or shrink an sframe because of jits? I'm just thinking about placeholders or something.
-- Steve
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 18 lines · Steven Rostedt
Quoted 15 lines · On Fri, 24 Jan 2025 11:21:59 -0800 Josh Poimboeuf <[email protected]> wrote:
Quoted 3 lines
given
SFRAME_F_FRAME_POINTERin the header, is it really that nonsensical and illegal to have zero FDEs/FREs? Maybe we should allow that?It would seem a bit silly to create an empty .sframe section just to set that
SFRAME_F_FRAME_POINTERbit. Regardless, there's nothing the kernel can do with that.Quoted 7 lines
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {
Quoted 7 lines
Quoted 3 lines
Quoted 1 line
if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?I suppose so, but again I'm not seeing any reason to support that.
Hmm, could that be useful for implementing a way to dynamically grow or shrink an sframe because of jits? I'm just thinking about placeholders or sohething.
Maybe?
I was thinking the kernel would have sframe_section placeholders for JIT code sections, so when sframe_find() retrieves the struct for a given IP, it sees the JIT flag is set along with a pointer to the in-memory shared "sframe section", then goes to read that to get the corresponding sframe entry (insert erratic hand waving).
It's still early days but it's quite possible the in-memory "sframe section" formats might end up looking pretty different from the .sframe file section spec.
Signature · 1 line
Josh
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Josh PoimboeufQuoted 22 lines · Josh Poimboeuf
Quoted 4 lines · Andrii Nakryiko
Quoted 1 line · Josh Poimboeuf
+static inline int sframe_add_section(unsigned long
sframe_start, unsigned longsframe_end, unsigned longtext_start, unsigned longtext_end) { return-ENOSYS; }nit: very-very long, wrap it?
That was intentional as it's just an empty stub, but yeah, maybe 160 chars is a bit much.
Quoted 8 lines
kernel/unwind/sframe.c line 40
Quoted 3 lines
kernel/unwind/sframe.c new file +3
-0+ if (shdr.preamble.magic != SFRAME_MAGIC || + shdr.preamble.version != SFRAME_VERSION_2 || + !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||probably more a question to Indu, but why is this sorting not mandatory and part of SFrame "standard"? How realistically non-sorted FDEs would work in practice? Ain't nobody got time to sort them just to unwind the stack...
No idea...
Quoted 2 lines
Quoted 1 line
+ if (!shdr.num_fdes || !shdr.num_fres) {
Quoted 8 lines
Quoted 3 lines
given
SFRAME_F_FRAME_POINTERin the header, is it really that nonsensical and illegal to have zero FDEs/FREs? Maybe we should allow that?It would seem a bit silly to create an empty .sframe section just to set that
SFRAME_F_FRAME_POINTERbit. Regardless, there's nothing the kernel can do with that.
Yes, in theory, it is allowed (as per the specification) to have an SFrame section with zero number of FDEs/FREs. But since such a section will not be useful, I share the opinion that it makes sense to disallow it in the current unwinding contexts, for now (JIT usecase may change things later).
SFRAME_F_FRAME_POINTER flag is not being set currently by GAS/GNU ld at all.
Quoted 62 lines
Quoted 8 lines
kernel/unwind/sframe.c line 52
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?I suppose so, but again I'm not seeing any reason to support that.
Quoted 7 lines
Quoted 7 lines
kernel/unwind/sframe.c new file +7
-0+dbg ("header doesn't fit in section\n" ); +return -EINVAL ; + } + + num_fdes = shdr.num_fdes; + fdes_start = header_end + shdr.fdes_off; + fdes_end = fdes_start + (num_fdes *sizeof (struct sframe_fde ));Quoted 8 lines
kernel/unwind/sframe.c line 63
Quoted 4 lines
kernel/unwind/sframe.c new file +4
-0+ + fres_start = header_end + shdr.fres_off; + fres_end = fres_start + shdr.fre_len; +maybe use
check_add_overflow()in all the above calculation, at least on 32-bit arches this all can overflow and it's not clear if below sanity check detects all possible overflowsOk, I'll look into it.
Quoted 24 lines
Quoted 24 lines
kernel/unwind/sframe.h new file +24
-0+struct sframe_preamble { +u16 magic; +u8 version; +u8 flags; +} __packed; + +struct sframe_header { +struct sframe_preamble preamble; +u8 abi_arch; +s8 cfa_fixed_fp_offset; +s8 cfa_fixed_ra_offset; +u8 auxhdr_len; +u32 num_fdes; +u32 num_fres; +u32 fre_len; +u32 fdes_off; +u32 fres_off; +} __packed; + +struct sframe_fde { +s32 start_addr; +u32 func_size; +u32 fres_off; +u32 fres_num;Quoted 8 lines
kernel/unwind/sframe.h line 57
Quoted 4 lines
kernel/unwind/sframe.h new file +4
-0+u8 info; +u8 rep_size; +u16 padding; +} __packed;I couldn't understand from SFrame itself, but why do
sframe_header,sframe_preamble, andsframe_fdehave to be marked__packed, if it's all naturally aligned (intentionally and by design)?..
Quoted 3 lines
Right, but the spec says they're all packed. Maybe the point is that some future sframe version is free to introduce unaligned fields.
SFrame specification aims to keep SFrame header and SFrame FDE members at aligned boundaries in future versions.
Only SFrame FRE related accesses may have unaligned accesses.
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 33 lines · Indu Bhagat
Quoted 22 lines · Josh Poimboeuf
Quoted 4 lines · Andrii Nakryiko
Quoted 1 line · Josh Poimboeuf
+static inline int sframe_add_section(unsigned long
sframe_start, unsigned longsframe_end, unsigned longtext_start, unsigned longtext_end) { return-ENOSYS; }nit: very-very long, wrap it?
That was intentional as it's just an empty stub, but yeah, maybe 160 chars is a bit much.
Quoted 8 lines
kernel/unwind/sframe.c line 40
Quoted 3 lines
kernel/unwind/sframe.c new file +3
-0+ if (shdr.preamble.magic != SFRAME_MAGIC || + shdr.preamble.version != SFRAME_VERSION_2 || + !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||probably more a question to Indu, but why is this sorting not mandatory and part of SFrame "standard"? How realistically non-sorted FDEs would work in practice? Ain't nobody got time to sort them just to unwind the stack...
No idea...
Quoted 2 lines
Quoted 1 line
+ if (!shdr.num_fdes || !shdr.num_fres) {
Quoted 8 lines
Quoted 3 lines
given
SFRAME_F_FRAME_POINTERin the header, is it really that nonsensical and illegal to have zero FDEs/FREs? Maybe we should allow that?It would seem a bit silly to create an empty .sframe section just to set that
SFRAME_F_FRAME_POINTERbit. Regardless, there's nothing the kernel can do with that.
Quoted 6 lines
Yes, in theory, it is allowed (as per the specification) to have an SFrame section with zero number of FDEs/FREs. But since such a section will not be useful, I share the opinion that it makes sense to disallow it in the current unwinding contexts, for now (JIT usecase may change things later).
I disagree, actually. If it's a legal thing, it shouldn't be randomly rejected. If we later make use of that, we'd have to worry not to accidentally cause problems on older kernels that arbitrarily rejected empty FDE just because it didn't make sense at some point (without causing any issues).
Quoted 12 lines
SFRAME_F_FRAME_POINTERflag is not being set currently by GAS/GNU ld at all.Quoted 10 lines
Quoted 8 lines
kernel/unwind/sframe.c line 52
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?I suppose so, but again I'm not seeing any reason to support that.
Let's invert this. Is there any reason why it shouldn't be supported? ;)
Quoted 52 lines
Quoted 52 lines
Quoted 16 lines
kernel/unwind/sframe.c line 63
Quoted 12 lines
kernel/unwind/sframe.c new file +11
-0+dbg ("header doesn't fit in section\n" ); +return -EINVAL ; + } + + num_fdes = shdr.num_fdes; + fdes_start = header_end + shdr.fdes_off; + fdes_end = fdes_start + (num_fdes *sizeof (struct sframe_fde )); + + fres_start = header_end + shdr.fres_off; + fres_end = fres_start + shdr.fre_len; +maybe use
check_add_overflow()in all the above calculation, at least on 32-bit arches this all can overflow and it's not clear if below sanity check detects all possible overflowsOk, I'll look into it.
Quoted 32 lines
Quoted 18 lines
kernel/unwind/sframe.h new file +18
-0+struct sframe_preamble { +u16 magic; +u8 version; +u8 flags; +} __packed; + +struct sframe_header { +struct sframe_preamble preamble; +u8 abi_arch; +s8 cfa_fixed_fp_offset; +s8 cfa_fixed_ra_offset; +u8 auxhdr_len; +u32 num_fdes; +u32 num_fres; +u32 fre_len; +u32 fdes_off; +u32 fres_off; +} __packed;kernel/unwind/sframe.h line 57
Quoted 10 lines
kernel/unwind/sframe.h new file +10
-0+ +struct sframe_fde { +s32 start_addr; +u32 func_size; +u32 fres_off; +u32 fres_num; +u8 info; +u8 rep_size; +u16 padding; +} __packed;I couldn't understand from SFrame itself, but why do
sframe_header,sframe_preamble, andsframe_fdehave to be marked__packed, if it's all naturally aligned (intentionally and by design)?..
Quoted 8 lines
Quoted 3 lines
Right, but the spec says they're all packed. Maybe the point is that some future sframe version is free to introduce unaligned fields.
SFrame specification aims to keep SFrame header and SFrame FDE members at aligned boundaries in future versions.
Only SFrame FRE related accesses may have unaligned accesses.
Yeah, and it's actually bothering me quite a lot :) I have a tentative proposal, maybe we can discuss this for SFrame v3? Let me briefly outline the idea.
So, currently in v2, FREs within FDEs use an array-of-structs layout. If we use preudo-C type definitions, it would be something like this for FDE + its FREs:
struct FDE_and_FREs {
struct sframe_func_desc_entry fde_metadata;
union FRE {
struct FRE8 {
u8 sfre_start_address;
u8 sfre_info;
u8|u16|u32 offsets[M];
}
struct FRE16 {
u16 sfre_start_address;
u16 sfre_info;
u8|u16|u32 offsets[M];
}
struct FRE32 {
u32 sfre_start_address;
u32 sfre_info;
u8|u16|u32 offsets[M];
}
} fres[N] __packed;
};where all fres[i]s are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
But what if we take a struct-of-arrays approach and represent it more like:
struct FDE_and_FREs {
struct sframe_func_desc_entry fde_metadata;
u8|u16|u32 start_addrs[N]; /* can extend to u64 as well */
u8 sfre_infos[N];
u8 offsets8[M8];
u16 offsets16[M16] __aligned(2);
u32 offsets32[M32] __aligned(4);
/* we can naturally extend to support also u64 offsets */
};i.e., we split all FRE records into their three constituents: start addresses, info bytes, and then each FRE can fall into either 8-, 16-, or 32-bit offsets "bucket". We collect all the offsets, depending on their size, into these aligned offsets{8,16,32} arrays (with natural extension to 64 bits, if necessary), with at most wasting 1-3 bytes to ensure proper alignment everywhere.
Note, at this point we need to decide if we want to make FREs binary searchable or not.
If not, we don't really need anything extra. As we process each start_addrs[i] and sfre_infos[i] to find matching FRE, we keep track of how many 8-, 16-, and 32-bit offsets already processed FREs consumed, and when we find the right one, we know exactly the starting index within offset{8,16,32}. Done.
But if we were to make FREs binary searchable, we need to basically have an index of offset pointers to quickly find offsetsX[j] position corresponding to FRE #i. For that, we can have an extra array right next to start_addrs, "semantically parallel" to it:
u8|u16|u32 start_addrs[N]; u8|u16|u32 offset_idxs[N];
where start_addrs[i] corresponds to offset_idxs[i], and offset_idxs[i] points to the first offset corresponding to FRE #i in offsetX[] array (depending on FRE's "bitness"). This is a bit more storage for this offset index, but for FDEs with lots of FREs this might be a worthwhile tradeoff.
Few points:
a) we can decide this "binary searchability" per-FDE, and for FDEs
with 1-2-3 FREs not bother, while those with more FREs would be searchable ones with index. So we can combine both fast lookups, natural alignment of on-disk format, and compactness. The presence of index is just another bit in FDE metadata.
b) bitness of offset_idxs[] can be coupled with bitness ofstart_addrs (for simplicity), or could be completely independent and identified by FDE's metadata (2 more bits to define this just like start_addr bitness is defined). Independent probably would be my preference, with linker (or whoever will be producing .sframe data) can pick the smallest bitness that is sufficient to represent everything.
Yes, it's a bit more complicated to draw and explain, but everything will be nicely aligned, extensible to 64 bits, and (optionally at least) binary searchable. Implementation-wise on the kernel side it shouldn't be significantly more involved. Maybe the compiler would need to be a bit smarter when producing FDE data, but it's no rocket science.
Thoughts?
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 7 lines · Andrii Nakryiko
Quoted 6 lines
Yes, in theory, it is allowed (as per the specification) to have an SFrame section with zero number of FDEs/FREs. But since such a section will not be useful, I share the opinion that it makes sense to disallow it in the current unwinding contexts, for now (JIT usecase may change things later).
Quoted 5 lines
I disagree, actually. If it's a legal thing, it shouldn't be randomly rejected. If we later make use of that, we'd have to worry not to accidentally cause problems on older kernels that arbitrarily rejected empty FDE just because it didn't make sense at some point (without causing any issues).
If such older kernels don't do anything with the section anyway, what's the point of pretending they do?
Returning an error would actually make more sense as it communicates that the kernel doesn't support whatever hypothetical thing you're trying to do with 0 FDEs.
Quoted 9 lines
Quoted 9 lines
SFRAME_F_FRAME_POINTERflag is not being set currently by GAS/GNU ld at all.Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {
Quoted 5 lines
Quoted 3 lines
Quoted 3 lines
Quoted 1 line
if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?I suppose so, but again I'm not seeing any reason to support that.
Let's invert this. Is there any reason why it shouldn't be supported? ;)
It's simple, we don't add code to "support" some vague hypothetical.
For whatever definition of "support", since there's literally nothing the kernel can do with that.
Quoted 12 lines
But what if we take a struct-of-arrays approach and represent it more like:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; u8|u16|u32 start_addrs[N]; /* can extend to u64 as well */ u8 sfre_infos[N]; u8 offsets8[M8]; u16 offsets16[M16] __aligned(2); u32 offsets32[M32] __aligned(4); /* we can naturally extend to support also u64 offsets */ };
Quoted 6 lines
i.e., we split all FRE records into their three constituents: start addresses, info bytes, and then each FRE can fall into either 8-, 16-, or 32-bit offsets "bucket". We collect all the offsets, depending on their size, into these aligned offsets{8,16,32} arrays (with natural extension to 64 bits, if necessary), with at most wasting 1-3 bytes to ensure proper alignment everywhere.
Makes sense. Though I also have another idea below.
Quoted 9 lines
Note, at this point we need to decide if we want to make FREs binary searchable or not.
If not, we don't really need anything extra. As we process each start_addrs[i] and sfre_infos[i] to find matching FRE, we keep track of how many 8-, 16-, and 32-bit offsets already processed FREs consumed, and when we find the right one, we know exactly the starting index within offset{8,16,32}. Done.
Quoted 7 lines
But if we were to make FREs binary searchable, we need to basically have an index of offset pointers to quickly find offsetsX[j] position corresponding to FRE #i. For that, we can have an extra array right next to
start_addrs, "semantically parallel" to it:u8|u16|u32 start_addrs[N]; u8|u16|u32 offset_idxs[N];
Binary search would definitely help. I did a crude histogram for "FREs per FDE" for a few binaries on my test system:
gdb (the biggest binary on my test system):
10th Percentile: 1
20th Percentile: 1
30th Percentile: 1
40th Percentile: 3
50th Percentile: 5
60th Percentile: 8
70th Percentile: 11
80th Percentile: 14
90th Percentile: 16
100th Percentile: 472
bash:
10th Percentile: 1
20th Percentile: 1
30th Percentile: 3
40th Percentile: 5
50th Percentile: 7
60th Percentile: 9
70th Percentile: 12
80th Percentile: 16
90th Percentile: 17
100th Percentile: 46
glibc:
10th Percentile: 1
20th Percentile: 1
30th Percentile: 1
40th Percentile: 1
50th Percentile: 4
60th Percentile: 6
70th Percentile: 9
80th Percentile: 14
90th Percentile: 16
100th Percentile: 44
libpython:
10th Percentile: 1
20th Percentile: 3
30th Percentile: 4
40th Percentile: 6
50th Percentile: 8
60th Percentile: 11
70th Percentile: 12
80th Percentile: 16
90th Percentile: 20
100th Percentile: 112
So binary search would help in a lot of cases.
However, if we're going that route, we might want to even consider a completely revamped data layout. For example:
One insight is that the vast majority of (cfa, fp, ra) tuples aren't unique. They could be deduped by storing the unique tuples in a standalone 'fre_data' array which is referenced by another address-specific array.
struct fre_data {
s8|s16|s32 cfa, fp, ra;
u8 info; };
struct fre_data fre_data[num_fre_data];
The storage sizes of cfa/fp/ra can be a constant specified in the global sframe header. It's fine all being the same size as it looks like this array wouldn't typically be more than a few hundred entries anyway.
Then there would be an array of sorted FRE entries which reference the fre_data[] entries:
struct fre {
s32|s64 start_address; u8|u16 fre_data_idx;
} __packed; struct fre fres[num_fres];
(For alignment reasons that should probably be two separate arrays, even though not ideal for cache locality)
Here again the field storage sizes would be specified in the global sframe header.
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the fre_data struct. So a simple binary search of fres[] is all that's really needed.
But wait, there's more...
The binary search could be made significantly faster using a small fast lookup array which is indexed evenly across the text address offset space, similar to what ORC does:
u32 fre_chunks[num_chunks];
The text address space (starting at offset 0) can be split into 'num_chunks' chunks of size 'chunk_size'. The value of fre_chunks[offset/chunk_size] is an index into the fres[] array.
Taking my gdb binary as an example:
.text is 6417058 bytes, with 146997 total sframe FREs. Assuming a chunk size of 1024, fre_chunks[] needs 6417058/1024 = 6267 entries.
For unwinding at text offset 0x400000, the index into fre_chunks[] would be 0x400000/1024 = 4096. If fre_chunks[4096] == 96074 and fre_chunks[4096+1] == 96098, you need only do a binary search of the 24 entries between fres[96074] && fres[96098] rather than searching the entire 146997 byte array.
.sframe size calculation:
374 unique fre_data entries (out of 146997 total FREs!)
= 374 * (2 * 3) = 2244 bytes
146997 fre entries
= 146997 * (4 + 2) = 881982 bytes
.text size 6417058 (chunk_size = 1024, num_chunks=6267)
= 6267 * 8 = 43869 bytesApproximate total .sframe size would be 2244 + 881982 + 8192 = 906k, plus negligible header size. Which is smaller than the v2 .sframe on my gdb binary (985k).
With the chunked lookup table, the avg lookup is:
log2(146997/6267) = ~4.5 iterations
whereas a full binary search would be:
log2(146997) = 17 iterations
So assuming I got all that math right, it's over 3x faster and the binary is smaller (or at least should be roughly comparable).
Of course the downside is it's an all new format. Presumably the linker would need to do more work than it's currently doing, e.g., find all the duplicates and set up the data accordingly.
Signature · 1 line
Josh
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 33 lines · Josh Poimboeuf
Quoted 7 lines · Andrii Nakryiko
Quoted 6 lines
Yes, in theory, it is allowed (as per the specification) to have an SFrame section with zero number of FDEs/FREs. But since such a section will not be useful, I share the opinion that it makes sense to disallow it in the current unwinding contexts, for now (JIT usecase may change things later).
Quoted 5 lines
I disagree, actually. If it's a legal thing, it shouldn't be randomly rejected. If we later make use of that, we'd have to worry not to accidentally cause problems on older kernels that arbitrarily rejected empty FDE just because it didn't make sense at some point (without causing any issues).
If such older kernels don't do anything with the section anyway, what's the point of pretending they do?
Returning an error would actually make more sense as it communicates that the kernel doesn't support whatever hypothetical thing you're trying to do with 0 FDEs.
Quoted 11 lines
Quoted 11 lines
SFRAME_F_FRAME_POINTERflag is not being set currently by GAS/GNU ld at all.Quoted 9 lines
Quoted 8 lines
kernel/unwind/sframe.c line 52
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Quoted 1 line
I suppose so, but again I'm not seeing any reason to support that.
Let's invert this. Is there any reason why it shouldn't be supported? ;)
It's simple, we don't add code to "support" some vague hypothetical.
For whatever definition of "support", since there's literally nothing the kernel can do with that.
If under the format specification it's legal, there is no reason for the kernel to proactively reject it, IMO. But ok, whatever.
Quoted 94 lines
Quoted 13 lines
But what if we take a struct-of-arrays approach and represent it more like:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; u8|u16|u32 start_addrs[N]; /* can extend to u64 as well */ u8 sfre_infos[N]; u8 offsets8[M8]; u16 offsets16[M16] __aligned(2); u32 offsets32[M32] __aligned(4); /* we can naturally extend to support also u64 offsets */ };Quoted 6 lines
i.e., we split all FRE records into their three constituents: start addresses, info bytes, and then each FRE can fall into either 8-, 16-, or 32-bit offsets "bucket". We collect all the offsets, depending on their size, into these aligned offsets{8,16,32} arrays (with natural extension to 64 bits, if necessary), with at most wasting 1-3 bytes to ensure proper alignment everywhere.
Makes sense. Though I also have another idea below.
Quoted 9 lines
Note, at this point we need to decide if we want to make FREs binary searchable or not.
If not, we don't really need anything extra. As we process each start_addrs[i] and sfre_infos[i] to find matching FRE, we keep track of how many 8-, 16-, and 32-bit offsets already processed FREs consumed, and when we find the right one, we know exactly the starting index within offset{8,16,32}. Done.
Quoted 7 lines
But if we were to make FREs binary searchable, we need to basically have an index of offset pointers to quickly find offsetsX[j] position corresponding to FRE #i. For that, we can have an extra array right next to
start_addrs, "semantically parallel" to it:u8|u16|u32 start_addrs[N]; u8|u16|u32 offset_idxs[N];Binary search would definitely help. I did a crude histogram for "FREs per FDE" for a few binaries on my test system:
gdb (the biggest binary on my test system):
10th Percentile: 1
20th Percentile: 1
30th Percentile: 1
40th Percentile: 3
50th Percentile: 5
60th Percentile: 8
70th Percentile: 11
80th Percentile: 14
90th Percentile: 16
100th Percentile: 472bash:
10th Percentile: 1
20th Percentile: 1
30th Percentile: 3
40th Percentile: 5
50th Percentile: 7
60th Percentile: 9
70th Percentile: 12
80th Percentile: 16
90th Percentile: 17
100th Percentile: 46glibc:
10th Percentile: 1
20th Percentile: 1
30th Percentile: 1
40th Percentile: 1
50th Percentile: 4
60th Percentile: 6
70th Percentile: 9
80th Percentile: 14
90th Percentile: 16
100th Percentile: 44libpython:
10th Percentile: 1
20th Percentile: 3
30th Percentile: 4
40th Percentile: 6
50th Percentile: 8
60th Percentile: 11
70th Percentile: 12
80th Percentile: 16
90th Percentile: 20
100th Percentile: 112
Quoted 1 line
So binary search would help in a lot of cases.
yep, agreed, seems like a worthwhile thing to support, given the stats (I suspect big production C++ applications might be even worse in this regard)
Quoted 19 lines
However, if we're going that route, we might want to even consider a completely revamped data layout. For example:
One insight is that the vast majority of (cfa, fp, ra) tuples aren't unique. They could be deduped by storing the unique tuples in a standalone '
fre_data' array which is referenced by another address-specific array.struct fre_data { s8|s16|s32 cfa, fp, ra; u8 info; }; struct fre_data fre_data[num_fre_data];The storage sizes of cfa/fp/ra can be a constant specified in the global sframe header. It's fine all being the same size as it looks like this array wouldn't typically be more than a few hundred entries anyway.
Quoted 6 lines
Then there would be an array of sorted FRE entries which reference the fre_data[] entries:
struct fre { s32|s64 start_address; u8|u16 fre_data_idx;
even u16 seems dangerous, I'd use u32, not sure it's worth limiting the format just to 64K unique combinations
Quoted 55 lines
} __packed; struct fre fres[num_fres];(For alignment reasons that should probably be two separate arrays, even though not ideal for cache locality)
Here again the field storage sizes would be specified in the global sframe header.
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the
fre_datastruct. So a simple binary search of fres[] is all that's really needed.But wait, there's more...
The binary search could be made significantly faster using a small fast lookup array which is indexed evenly across the text address offset space, similar to what ORC does:
u32 fre_chunks[num_chunks];The text address space (starting at offset 0) can be split into '
num_chunks' chunks of size 'chunk_size'. The value of fre_chunks[offset/chunk_size] is an index into the fres[] array.Taking my gdb binary as an example:
.text is 6417058 bytes, with 146997 total sframe FREs. Assuming a chunk size of 1024, fre_chunks[] needs 6417058/1024 = 6267 entries.
For unwinding at text offset
0x400000, the index into fre_chunks[] would be 0x400000/1024 = 4096. If fre_chunks[4096] == 96074 and fre_chunks[4096+1] == 96098, you need only do a binary search of the 24 entries between fres[96074] && fres[96098] rather than searching the entire 146997 byte array..sframe size calculation:
374 unique fre_data entries (out of 146997 total FREs!) = 374 * (2 * 3) = 2244 bytes 146997 fre entries = 146997 * (4 + 2) = 881982 bytes .text size 6417058 (chunk_size = 1024, num_chunks=6267) = 6267 * 8 = 43869 bytesApproximate total .sframe size would be 2244 + 881982 + 8192 = 906k, plus negligible header size. Which is smaller than the v2 .sframe on my gdb binary (985k).
With the chunked lookup table, the avg lookup is:
Quoted 8 lines
log2(146997/6267) = ~4.5 iterations
whereas a full binary search would be:
log2(146997) = 17 iterations
So assuming I got all that math right, it's over 3x faster and the binary is smaller (or at least should be roughly comparable).
I'm not sure about this chunked lookup approach for arbitrary user space applications. Those executable sections can be a) big and b) discontiguous. E.g., one of the production binaries I looked at. Here are its three main executable sections:
...
[17] .bolt.org.text PROGBITS 000000000b00e640 0ae0d640
0000000011ad621c 0000000000000000 AX 0 0 64
...
[48] .text PROGBITS 000000001e600000 1ce00000
0000000000775dd8 0000000000000000 AX 0 0 2097152
[49] .text.cold PROGBITS 000000001ed75e00 1d575e00
00000000007d3271 0000000000000000 AX 0 0 64...
Total text size is about 300MB:
Quoted 1 line
Quoted 1 line
Quoted 1 line
0x0000000000775dd8+0x00000000007d3271+0x0000000011ad621c
312603237
Section #17 ends at:
Quoted 1 line
Quoted 1 line
Quoted 1 line
hex(0x0000000011ad621c +
0x000000000b00e640)
'0x1cae485c'
While .text starts at 000000001e600000, so we have a gap of ~28MB:
Quoted 1 line
Quoted 1 line
Quoted 1 line
0x000000001e600000-0x1cae485c
28424100
So unless we do something more clever to support multiple discontiguous chunks, this seems like a bad fit for user space.
I think having all this just binary searchable is already a big win anyways and should be plenty fast, no?
Quoted 4 lines
Of course the downside is it's an all new format. Presumably the linker would need to do more work than it's currently doing, e.g., find all the duplicates and set up the data accordingly.
I do like the idea of deduplicating those records and just indexing them, as in practice this should probably be much more compact. So it might be worth looking into this some more. I'll defer to Indu.
Quoted 3 lines
--
Josh
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 25 lines · Andrii Nakryiko
On Tue, Jan 28, 2025 at 6:02 PM Josh Poimboeuf <[email protected]> wrote:
I'm not sure about this chunked lookup approach for arbitrary user space applications. Those executable sections can be a) big and b) discontiguous. E.g., one of the production binaries I looked at. Here are its three main executable sections:...
[17] .bolt.org.text PROGBITS000000000b00e6400ae0d6400000000011ad621c 0000000000000000 AX 0 0 64 ... [48] .text PROGBITS 000000001e600000 1ce00000 0000000000775dd8 0000000000000000 AX 0 0 2097152 [49] .text.cold PROGBITS 000000001ed75e00 1d575e00 00000000007d3271 0000000000000000 AX 0 0 64...
Total text size is about 300MB:
Quoted 1 line
Quoted 1 line
Quoted 1 line
0x0000000000775dd8+0x00000000007d3271+0x0000000011ad621c312603237
Section #17 ends at:
Quoted 1 line
Quoted 1 line
Quoted 1 line
hex(0x0000000011ad621c +
0x000000000b00e640)'
0x1cae485c'
Quoted 7 lines
While .text starts at
000000001e600000, so we have a gap of ~28MB:Quoted 1 line
Quoted 1 line
Quoted 1 line
0x000000001e600000-0x1cae485c28424100
So unless we do something more clever to support multiple discontiguous chunks, this seems like a bad fit for user space.
Nothing clever needed, we could just have multiple sframe sections, each one with a pointer to its text segment. That would also have the benefit of allowing the sframe data to be much more compact for the noncontiguous cases.
Quoted 2 lines
I think having all this just binary searchable is already a big win anyways and should be plenty fast, no?
Sframe is trying to compete with frame pointers which are MUCH faster. 3-4x faster in my testing, not including the page faults (which tend to only affect performance in the very beginning).
Signature · 1 line
Josh
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Josh PoimboeufQuoted 134 lines · Josh Poimboeuf
Quoted 7 lines · Andrii Nakryiko
Quoted 6 lines
Yes, in theory, it is allowed (as per the specification) to have an SFrame section with zero number of FDEs/FREs. But since such a section will not be useful, I share the opinion that it makes sense to disallow it in the current unwinding contexts, for now (JIT usecase may change things later).
Quoted 5 lines
I disagree, actually. If it's a legal thing, it shouldn't be randomly rejected. If we later make use of that, we'd have to worry not to accidentally cause problems on older kernels that arbitrarily rejected empty FDE just because it didn't make sense at some point (without causing any issues).
If such older kernels don't do anything with the section anyway, what's the point of pretending they do?
Returning an error would actually make more sense as it communicates that the kernel doesn't support whatever hypothetical thing you're trying to do with 0 FDEs.
Quoted 9 lines
Quoted 9 lines
SFRAME_F_FRAME_POINTERflag is not being set currently by GAS/GNU ld at all.Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
kernel/unwind/sframe.c new file +6
-0+dbg ("no fde/fre entries\n" ); +return -EINVAL ; + } + + header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr); +if (header_end >= sec->sframe_end) {Quoted 5 lines
Quoted 3 lines
Quoted 3 lines
Quoted 1 line
if we allow zero FDEs/FREs,
header_end==sec->sframe_endis legal, right?I suppose so, but again I'm not seeing any reason to support that.
Let's invert this. Is there any reason why it shouldn't be supported? ;)
It's simple, we don't add code to "support" some vague hypothetical.
For whatever definition of "support", since there's literally nothing the kernel can do with that.
Quoted 12 lines
But what if we take a struct-of-arrays approach and represent it more like:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; u8|u16|u32 start_addrs[N]; /* can extend to u64 as well */ u8 sfre_infos[N]; u8 offsets8[M8]; u16 offsets16[M16] __aligned(2); u32 offsets32[M32] __aligned(4); /* we can naturally extend to support also u64 offsets */ };Quoted 6 lines
i.e., we split all FRE records into their three constituents: start addresses, info bytes, and then each FRE can fall into either 8-, 16-, or 32-bit offsets "bucket". We collect all the offsets, depending on their size, into these aligned offsets{8,16,32} arrays (with natural extension to 64 bits, if necessary), with at most wasting 1-3 bytes to ensure proper alignment everywhere.
Makes sense. Though I also have another idea below.
Quoted 9 lines
Note, at this point we need to decide if we want to make FREs binary searchable or not.
If not, we don't really need anything extra. As we process each start_addrs[i] and sfre_infos[i] to find matching FRE, we keep track of how many 8-, 16-, and 32-bit offsets already processed FREs consumed, and when we find the right one, we know exactly the starting index within offset{8,16,32}. Done.
Quoted 7 lines
But if we were to make FREs binary searchable, we need to basically have an index of offset pointers to quickly find offsetsX[j] position corresponding to FRE #i. For that, we can have an extra array right next to
start_addrs, "semantically parallel" to it:u8|u16|u32 start_addrs[N]; u8|u16|u32 offset_idxs[N];Binary search would definitely help. I did a crude histogram for "FREs per FDE" for a few binaries on my test system:
gdb (the biggest binary on my test system):
10th Percentile: 1
20th Percentile: 1
30th Percentile: 1
40th Percentile: 3
50th Percentile: 5
60th Percentile: 8
70th Percentile: 11
80th Percentile: 14
90th Percentile: 16
100th Percentile: 472bash:
10th Percentile: 1
20th Percentile: 1
30th Percentile: 3
40th Percentile: 5
50th Percentile: 7
60th Percentile: 9
70th Percentile: 12
80th Percentile: 16
90th Percentile: 17
100th Percentile: 46glibc:
10th Percentile: 1
20th Percentile: 1
30th Percentile: 1
40th Percentile: 1
50th Percentile: 4
60th Percentile: 6
70th Percentile: 9
80th Percentile: 14
90th Percentile: 16
100th Percentile: 44libpython:
10th Percentile: 1
20th Percentile: 3
30th Percentile: 4
40th Percentile: 6
50th Percentile: 8
60th Percentile: 11
70th Percentile: 12
80th Percentile: 16
90th Percentile: 20
100th Percentile: 112
Quoted 2 lines
So binary search would help in a lot of cases.
Thanks for gathering this.
I suspect on aarch64, the numbers will be very different (leaning towards very low number of FREs per FDE).
Making SFrame FREs amenable to binary search can be targeted. Both your and Andrii's proposal do address that...
Quoted 8 lines
However, if we're going that route, we might want to even consider a completely revamped data layout. For example:
One insight is that the vast majority of (cfa, fp, ra) tuples aren't unique. They could be deduped by storing the unique tuples in a standalone '
fre_data' array which is referenced by another address-specific array.
Quoted 6 lines
struct fre_data {
s8|s16|s32 cfa, fp, ra;
u8 info;
};
struct fre_data fre_data[num_fre_data];We had the same observation at the time of SFrame V1. And this method of compaction (deduped tuples) was brain-stormed a bit. Back then, the costs were thought to be:
- more work at build time.
- an additional data access once the FRE is found (as there is
indirection).
So it was really compaction at the costs above. We did steer towards simplicity and the SFrame FRE is what it stands today.
The difference in the pros and cons now from then:
- pros: helps mitigate unaligned accesses
- cons: interferes slightly with the design goal of efficient
addition and removal of stack trace information per function for JIT. Think "removal" as the set of actions necessary for addressing fragmentation in SFrame section data in JIT usecase.
Quoted 17 lines
The storage sizes of cfa/fp/ra can be a constant specified in the global sframe header. It's fine all being the same size as it looks like this array wouldn't typically be more than a few hundred entries anyway.
Then there would be an array of sorted FRE entries which reference the fre_data[] entries:
struct fre { s32|s64 start_address; u8|u16 fre_data_idx; } __packed; struct fre fres[num_fres];(For alignment reasons that should probably be two separate arrays, even though not ideal for cache locality)
Quoted 8 lines
Here again the field storage sizes would be specified in the global sframe header.
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the
fre_datastruct. So a simple binary search of fres[] is all that's really needed.
Splitting out information (start_address) to an FDE (as done in V1/V2) has the benefit that a job like relocating information is proportional to O(NumFunctions).
In the case above, IIUC, where the proposal puts start_address in the FRE, these costs will be (much) higher.
In addition, not being able to identify stack trace information per function will affect the JIT usecase. We need to able to mark stack trace information stale for functions in JIT environment.
I think the first conceptual landing point in the information layout should be a per-function entry.
Quoted 53 lines
But wait, there's more...
The binary search could be made significantly faster using a small fast lookup array which is indexed evenly across the text address offset space, similar to what ORC does:
u32 fre_chunks[num_chunks];The text address space (starting at offset 0) can be split into '
num_chunks' chunks of size 'chunk_size'. The value of fre_chunks[offset/chunk_size] is an index into the fres[] array.Taking my gdb binary as an example:
.text is 6417058 bytes, with 146997 total sframe FREs. Assuming a chunk size of 1024, fre_chunks[] needs 6417058/1024 = 6267 entries.
For unwinding at text offset
0x400000, the index into fre_chunks[] would be 0x400000/1024 = 4096. If fre_chunks[4096] == 96074 and fre_chunks[4096+1] == 96098, you need only do a binary search of the 24 entries between fres[96074] && fres[96098] rather than searching the entire 146997 byte array..sframe size calculation:
374 unique fre_data entries (out of 146997 total FREs!) = 374 * (2 * 3) = 2244 bytes 146997 fre entries = 146997 * (4 + 2) = 881982 bytes .text size 6417058 (chunk_size = 1024, num_chunks=6267) = 6267 * 8 = 43869 bytesApproximate total .sframe size would be 2244 + 881982 + 8192 = 906k, plus negligible header size. Which is smaller than the v2 .sframe on my gdb binary (985k).
With the chunked lookup table, the avg lookup is:
log2(146997/6267) = ~4.5 iterationswhereas a full binary search would be:
log2(146997) = 17 iterationsSo assuming I got all that math right, it's over 3x faster and the binary is smaller (or at least should be roughly comparable).
Of course the downside is it's an all new format. Presumably the linker would need to do more work than it's currently doing, e.g., find all the duplicates and set up the data accordingly.
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 26 lines · Indu Bhagat
Quoted 8 lines · Josh Poimboeuf
However, if we're going that route, we might want to even consider a completely revamped data layout. For example:
One insight is that the vast majority of (cfa, fp, ra) tuples aren't unique. They could be deduped by storing the unique tuples in a standalone '
fre_data' array which is referenced by another address-specific array.Quoted 6 lines
struct fre_data { s8|s16|s32 cfa, fp, ra; u8 info; }; struct fre_data fre_data[num_fre_data];We had the same observation at the time of SFrame V1. And this method of compaction (deduped tuples) was brain-stormed a bit. Back then, the costs were thought to be:
- more work at build time.
- an additional data access once the FRE is found (as there is
indirection).So it was really compaction at the costs above. We did steer towards simplicity and the SFrame FRE is what it stands today.
Quoted 6 lines
The difference in the pros and cons now from then:
- pros: helps mitigate unaligned accesses
- cons: interferes slightly with the design goal of efficient addition and
removal of stack trace information per function for JIT. Think "removal" as the set of actions necessary for addressing fragmentation in SFrame section data in JIT usecase.
If fre_data[] is allowed to have duplicates then the deduping could be optional.
Quoted 11 lines
Quoted 4 lines
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the
fre_datastruct. So a simple binary search of fres[] is all that's really needed.Splitting out information (
start_address) to an FDE (as done in V1/V2) has the benefit that a job like relocating information is proportional to O(NumFunctions).In the case above, IIUC, where the proposal puts
start_addressin the FRE, these costs will be (much) higher.
I'm not sure I follow, is this referring to the link-time work of sorting things?
Quoted 3 lines
In addition, not being able to identify stack trace information per function will affect the JIT usecase. We need to able to mark stack trace information stale for functions in JIT environment.
Maybe, though it's hard to really say how any of these changes would affect JIT without knowing what those interfaces are going to look like.
Signature · 1 line
Josh
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 46 lines · Josh Poimboeuf
Quoted 26 lines · Indu Bhagat
Quoted 8 lines · Josh Poimboeuf
However, if we're going that route, we might want to even consider a completely revamped data layout. For example:
One insight is that the vast majority of (cfa, fp, ra) tuples aren't unique. They could be deduped by storing the unique tuples in a standalone '
fre_data' array which is referenced by another address-specific array.Quoted 6 lines
struct fre_data { s8|s16|s32 cfa, fp, ra; u8 info; }; struct fre_data fre_data[num_fre_data];We had the same observation at the time of SFrame V1. And this method of compaction (deduped tuples) was brain-stormed a bit. Back then, the costs were thought to be:
- more work at build time.
- an additional data access once the FRE is found (as there is
indirection).So it was really compaction at the costs above. We did steer towards simplicity and the SFrame FRE is what it stands today.
Quoted 6 lines
The difference in the pros and cons now from then:
- pros: helps mitigate unaligned accesses
- cons: interferes slightly with the design goal of efficient addition and
removal of stack trace information per function for JIT. Think "removal" as the set of actions necessary for addressing fragmentation in SFrame section data in JIT usecase.If fre_data[] is allowed to have duplicates then the deduping could be optional.
Quoted 9 lines
Quoted 4 lines
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the
fre_datastruct. So a simple binary search of fres[] is all that's really needed.Splitting out information (
start_address) to an FDE (as done in V1/V2) has the benefit that a job like relocating information is proportional to O(NumFunctions).
Quoted 6 lines
Quoted 2 lines
In the case above, IIUC, where the proposal puts
start_addressin the FRE, these costs will be (much) higher.I'm not sure I follow, is this referring to the link-time work of sorting things?
I meant the work of tracking the start address of each function. This could be done at link-time as is done in most cases.
But also depending on the case : e.g., kernel module loader will need to apply these relocations in the .rela.sframe section...
If the granularity is finer than a function, more number of relocations will need to be applied.
Quoted 7 lines
Quoted 3 lines
In addition, not being able to identify stack trace information per function will affect the JIT usecase. We need to able to mark stack trace information stale for functions in JIT environment.
Maybe, though it's hard to really say how any of these changes would affect JIT without knowing what those interfaces are going to look like.
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Josh PoimboeufQuoted 4 lines · Josh Poimboeuf
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the
fre_datastruct. So a simple binary search of fres[] is all that's really needed.
In SFrame V2 FDEs specify ranges bound by function start address and length. FREs in contrast specify open ranges bounded by start address. Their effect ends either with the next FRE becoming into effect or when their FDE range ends.
This concept enables holes in the .text section which do not have any valid FDE/FRE information associated.
Your proposal lacks some sort of mechanism to replicate those holes. It could be FDEs with a flag (or no offsets?) that specifies their range has no valid information.
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 14 lines · Jens Remus
Quoted 4 lines · Josh Poimboeuf
Note FDEs aren't even needed here as the unwinder doesn't need to know when a function begins/ends. The only info needed by the unwinder is just the
fre_datastruct. So a simple binary search of fres[] is all that's really needed.In SFrame V2 FDEs specify ranges bound by function start address and length. FREs in contrast specify open ranges bounded by start address. Their effect ends either with the next FRE becoming into effect or when their FDE range ends.
This concept enables holes in the .text section which do not have any valid FDE/FRE information associated.
Quoted 3 lines
Your proposal lacks some sort of mechanism to replicate those holes. It could be FDEs with a flag (or no offsets?) that specifies their range has no valid information.
In ORC, a hole is simply specified by an ORC entry (aka "FRE") of type UNDEFINED.
That could be done here as well: the linker would replace a gap with an "undefined" FRE which could be identified either by setting a bit in the FRE header, or by using a special fre_data[] entry. For example, fre_data index 0 could just be a placeholder for the undefined FRE type.
Signature · 1 line
Josh
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Andrii NakryikoQuoted 34 lines · Andrii Nakryiko
Quoted 34 lines
Quoted 34 lines
Quoted 23 lines
Quoted 23 lines
kernel/unwind/sframe.h new file +23
-0+struct sframe_preamble { +u16 magic; +u8 version; +u8 flags; +} __packed; + +struct sframe_header { +struct sframe_preamble preamble; +u8 abi_arch; +s8 cfa_fixed_fp_offset; +s8 cfa_fixed_ra_offset; +u8 auxhdr_len; +u32 num_fdes; +u32 num_fres; +u32 fre_len; +u32 fdes_off; +u32 fres_off; +} __packed; + +struct sframe_fde { +s32 start_addr; +u32 func_size; +u32 fres_off;Quoted 8 lines
kernel/unwind/sframe.h line 57
Quoted 5 lines
kernel/unwind/sframe.h new file +5
-0+u32 fres_num; +u8 info; +u8 rep_size; +u16 padding; +} __packed;I couldn't understand from SFrame itself, but why do
sframe_header,sframe_preamble, andsframe_fdehave to be marked__packed, if it's all naturally aligned (intentionally and by design)?..Right, but the spec says they're all packed. Maybe the point is that some future sframe version is free to introduce unaligned fields.
Quoted 8 lines
Quoted 4 lines
SFrame specification aims to keep SFrame header and SFrame FDE members at aligned boundaries in future versions.
Only SFrame FRE related accesses may have unaligned accesses.
Yeah, and it's actually bothering me quite a lot 🙂 I have a tentative proposal, maybe we can discuss this for SFrame v3? Let me briefly outline the idea.
I looked at the idea below. It could work wrt unaligned accesses.
Speaking of unaligned accesses, I will ask away: Is the reason to avoid unaligned accesses performance hit or are there other practical reasons to it ?
Quoted 73 lines
So, currently in v2, FREs within FDEs use an array-of-structs layout. If we use preudo-C type definitions, it would be something like this for FDE + its FREs:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; union FRE { struct FRE8 { u8 sfre_start_address; u8 sfre_info; u8|u16|u32 offsets[M]; } struct FRE16 { u16 sfre_start_address; u16 sfre_info; u8|u16|u32 offsets[M]; } struct FRE32 { u32 sfre_start_address; u32 sfre_info; u8|u16|u32 offsets[M]; } } fres[N] __packed; };where all fres[i]s are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
But what if we take a struct-of-arrays approach and represent it more like:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; u8|u16|u32 start_addrs[N]; /* can extend to u64 as well */ u8 sfre_infos[N]; u8 offsets8[M8]; u16 offsets16[M16] __aligned(2); u32 offsets32[M32] __aligned(4); /* we can naturally extend to support also u64 offsets */ };i.e., we split all FRE records into their three constituents: start addresses, info bytes, and then each FRE can fall into either 8-, 16-, or 32-bit offsets "bucket". We collect all the offsets, depending on their size, into these aligned offsets{8,16,32} arrays (with natural extension to 64 bits, if necessary), with at most wasting 1-3 bytes to ensure proper alignment everywhere.
Note, at this point we need to decide if we want to make FREs binary searchable or not.
If not, we don't really need anything extra. As we process each start_addrs[i] and sfre_infos[i] to find matching FRE, we keep track of how many 8-, 16-, and 32-bit offsets already processed FREs consumed, and when we find the right one, we know exactly the starting index within offset{8,16,32}. Done.
But if we were to make FREs binary searchable, we need to basically have an index of offset pointers to quickly find offsetsX[j] position corresponding to FRE #i. For that, we can have an extra array right next to
start_addrs, "semantically parallel" to it:u8|u16|u32 start_addrs[N]; u8|u16|u32 offset_idxs[N];where start_addrs[i] corresponds to offset_idxs[i], and offset_idxs[i] points to the first offset corresponding to FRE #i in offsetX[] array (depending on FRE's "bitness"). This is a bit more storage for this offset index, but for FDEs with lots of FREs this might be a worthwhile tradeoff.
Quoted 6 lines
Few points:
a) we can decide this "binary searchability" per-FDE, and for FDEs
with 1-2-3 FREs not bother, while those with more FREs would be searchable ones with index. So we can combine both fast lookups, natural alignment of on-disk format, and compactness. The presence of index is just another bit in FDE metadata.
I have been going back and forth on this one. So there seem to be the following options here:
#1. Make "binary searchability" a per-FDE decision.
#2. Make "binary searchability" a per-section decision (I expect
aarch64 to have very low number of FREs per FDE).
#3. Bake "binary searchability" into the SFrame FRE specification.
So its always ON for all FDEs. The advantage is that it makes stack tracers simpler to implement with less code.
I do think #2, #3 appear simpler in concept.
Quoted 8 lines
b) bitness of offset_idxs[] can be coupled with bitness of
start_addrs(for simplicity), or could be completely independent and identified by FDE's metadata (2 more bits to define this just likestart_addrbitness is defined). Independent probably would be my preference, with linker (or whoever will be producing .sframe data) can pick the smallest bitness that is sufficient to represent everything.
ATM, GAS does apply special logic to decide the bitness of start_addrs per function, and ld just uses that info. Coupling the bitness of offset_idx with bitness of start_addrs will be easy (or _easier_ I think), but for now, I leave it as "should be doable" :)
Quoted 8 lines
Yes, it's a bit more complicated to draw and explain, but everything will be nicely aligned, extensible to 64 bits, and (optionally at least) binary searchable. Implementation-wise on the kernel side it shouldn't be significantly more involved. Maybe the compiler would need to be a bit smarter when producing FDE data, but it's no rocket science.
Thoughts?
Combining the requirements from your email and Josh's follow up:
- No unaligned accesses
- Sorted FREs
I would put compaction as a "good to have" requirement. It appears to me that any compaction will mean a sort of post-processing which will interfere with JIT usecase.
Josh Poimboeuf
maintainerRe: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 10 lines · Indu Bhagat
Quoted 4 lines
Yeah, and it's actually bothering me quite a lot 🙂 I have a tentative proposal, maybe we can discuss this for SFrame v3? Let me briefly outline the idea.
I looked at the idea below. It could work wrt unaligned accesses.
Speaking of unaligned accesses, I will ask away: Is the reason to avoid unaligned accesses performance hit or are there other practical reasons to it ?
I think performance is the main concern, though there are still some CPU arches out there which don't support unaligned accesses.
Quoted 7 lines
Combining the requirements from your email and Josh's follow up:
- No unaligned accesses
- Sorted FREsI would put compaction as a "good to have" requirement. It appears to me that any compaction will mean a sort of post-processing which will interfere with JIT usecase.
I think we should still consider the fast lookup table. We might want to prototype something just to see what the speedup looks like. Similar to compaction it could just be an optional feature implemented by the linker which JIT doesn't have to use.
Signature · 1 line
Josh
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Indu BhagatQuoted 45 lines · Indu Bhagat
Quoted 34 lines · Andrii Nakryiko
Quoted 34 lines
Quoted 34 lines
Quoted 23 lines
Quoted 23 lines
kernel/unwind/sframe.h new file +23
-0+struct sframe_preamble { +u16 magic; +u8 version; +u8 flags; +} __packed; + +struct sframe_header { +struct sframe_preamble preamble; +u8 abi_arch; +s8 cfa_fixed_fp_offset; +s8 cfa_fixed_ra_offset; +u8 auxhdr_len; +u32 num_fdes; +u32 num_fres; +u32 fre_len; +u32 fdes_off; +u32 fres_off; +} __packed; + +struct sframe_fde { +s32 start_addr; +u32 func_size; +u32 fres_off;Quoted 8 lines
kernel/unwind/sframe.h line 57
Quoted 5 lines
kernel/unwind/sframe.h new file +5
-0+u32 fres_num; +u8 info; +u8 rep_size; +u16 padding; +} __packed;I couldn't understand from SFrame itself, but why do
sframe_header,sframe_preamble, andsframe_fdehave to be marked__packed, if it's all naturally aligned (intentionally and by design)?..Right, but the spec says they're all packed. Maybe the point is that some future sframe version is free to introduce unaligned fields.
Quoted 8 lines
Quoted 4 lines
SFrame specification aims to keep SFrame header and SFrame FDE members at aligned boundaries in future versions.
Only SFrame FRE related accesses may have unaligned accesses.
Yeah, and it's actually bothering me quite a lot 🙂 I have a tentative proposal, maybe we can discuss this for SFrame v3? Let me briefly outline the idea.
Quoted 5 lines
I looked at the idea below. It could work wrt unaligned accesses.
Speaking of unaligned accesses, I will ask away: Is the reason to avoid unaligned accesses performance hit or are there other practical reasons to it ?
Performance hit on architectures like x86-64 that do support unaligned, but it's actually a CPU error for some other architectures, so you'd need to code with that in mind, making local aligned copies, etc. In general, I'd say it's a bit of a red flag that a format that is meant to be memory-mapped (effectively) and used without pre-processing requires dealing with unaligned accesses. So if we can fix that, that would be a win.
Quoted 90 lines
Quoted 74 lines
So, currently in v2, FREs within FDEs use an array-of-structs layout. If we use preudo-C type definitions, it would be something like this for FDE + its FREs:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; union FRE { struct FRE8 { u8 sfre_start_address; u8 sfre_info; u8|u16|u32 offsets[M]; } struct FRE16 { u16 sfre_start_address; u16 sfre_info; u8|u16|u32 offsets[M]; } struct FRE32 { u32 sfre_start_address; u32 sfre_info; u8|u16|u32 offsets[M]; } } fres[N] __packed; };where all fres[i]s are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
But what if we take a struct-of-arrays approach and represent it more like:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; u8|u16|u32 start_addrs[N]; /* can extend to u64 as well */ u8 sfre_infos[N]; u8 offsets8[M8]; u16 offsets16[M16] __aligned(2); u32 offsets32[M32] __aligned(4); /* we can naturally extend to support also u64 offsets */ };i.e., we split all FRE records into their three constituents: start addresses, info bytes, and then each FRE can fall into either 8-, 16-, or 32-bit offsets "bucket". We collect all the offsets, depending on their size, into these aligned offsets{8,16,32} arrays (with natural extension to 64 bits, if necessary), with at most wasting 1-3 bytes to ensure proper alignment everywhere.
Note, at this point we need to decide if we want to make FREs binary searchable or not.
If not, we don't really need anything extra. As we process each start_addrs[i] and sfre_infos[i] to find matching FRE, we keep track of how many 8-, 16-, and 32-bit offsets already processed FREs consumed, and when we find the right one, we know exactly the starting index within offset{8,16,32}. Done.
But if we were to make FREs binary searchable, we need to basically have an index of offset pointers to quickly find offsetsX[j] position corresponding to FRE #i. For that, we can have an extra array right next to
start_addrs, "semantically parallel" to it:u8|u16|u32 start_addrs[N]; u8|u16|u32 offset_idxs[N];where start_addrs[i] corresponds to offset_idxs[i], and offset_idxs[i] points to the first offset corresponding to FRE #i in offsetX[] array (depending on FRE's "bitness"). This is a bit more storage for this offset index, but for FDEs with lots of FREs this might be a worthwhile tradeoff.
Quoted 6 lines
Few points:
a) we can decide this "binary searchability" per-FDE, and for FDEs
with 1-2-3 FREs not bother, while those with more FREs would be searchable ones with index. So we can combine both fast lookups, natural alignment of on-disk format, and compactness. The presence of index is just another bit in FDE metadata.I have been going back and forth on this one. So there seem to be the following options here:
#1. Make "binary searchability" a per-FDE decision.
#2. Make "binary searchability" a per-section decision (I expect
aarch64 to have very low number of FREs per FDE).
#3. Bake "binary searchability" into the SFrame FRE specification.
So its always ON for all FDEs. The advantage is that it makes stack tracers simpler to implement with less code.
Quoted 1 line
I do think #2, #3 appear simpler in concept.
Whichever makes it easier across the entire stack (compiler, linker, kernel/unwinder). As long as binary searchability is possible, especially for FDEs with lots of FREs. Making it per-FDE just allows to pick most compact (but still with good performance) representation.
Quoted 10 lines
Quoted 9 lines
b) bitness of offset_idxs[] can be coupled with bitness of
start_addrs(for simplicity), or could be completely independent and identified by FDE's metadata (2 more bits to define this just likestart_addrbitness is defined). Independent probably would be my preference, with linker (or whoever will be producing .sframe data) can pick the smallest bitness that is sufficient to represent everything.
Quoted 4 lines
ATM, GAS does apply special logic to decide the bitness of
start_addrsper function, and ld just uses that info. Coupling the bitness ofoffset_idxwith bitness ofstart_addrswill be easy (or _easier_ I think), but for now, I leave it as "should be doable" :)
Those offsets are relative to fde's start_addr, right? So, generally speaking, should be usually small? I my understanding is correct, then yeah, coupling is probably ok.
Quoted 10 lines
Quoted 9 lines
Yes, it's a bit more complicated to draw and explain, but everything will be nicely aligned, extensible to 64 bits, and (optionally at least) binary searchable. Implementation-wise on the kernel side it shouldn't be significantly more involved. Maybe the compiler would need to be a bit smarter when producing FDE data, but it's no rocket science.
Thoughts?
Quoted 8 lines
Combining the requirements from your email and Josh's follow up:
- No unaligned accesses
- Sorted FREsI would put compaction as a "good to have" requirement. It appears to me that any compaction will mean a sort of post-processing which will interfere with JIT usecase.
sgtm
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Andrii NakryikoQuoted 26 lines · Andrii Nakryiko
So, currently in v2, FREs within FDEs use an array-of-structs layout. If we use preudo-C type definitions, it would be something like this for FDE + its FREs:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; union FRE { struct FRE8 { u8 sfre_start_address; u8 sfre_info; u8|u16|u32 offsets[M]; } struct FRE16 { u16 sfre_start_address; u16 sfre_info; u8|u16|u32 offsets[M]; } struct FRE32 { u32 sfre_start_address; u32 sfre_info; u8|u16|u32 offsets[M]; } } fres[N] __packed; };
Quoted 4 lines
where all fres[i]s are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
Just for clarification of the SFrame V2 format, as there may be some misconception. Using pseudo-C type definition:
struct sframe_fre8 {
u8 sfre_start_address;
u8 sfre_info;
s8|s16|s32 offsets[M];
};
struct sframe_fre16 {
u16 sfre_start_address;
u8 sfre_info;
s8|s16|s32 offsets[M];
};
struct sframe_fre32 {
u32 sfre_start_address;
u8 sfre_info;
s8|s16|s32 offsets[M];
};struct sframe_section {
/* Headers. */
struct sframe_preamble preamble;
struct sframe_header header;
struct sframe_auxiliary_header auxhdr;
/* FDEs. */
struct sframe_fde fdes[N_FDE];
/* FRE8s / FRE16s / FRE32s per FDE. */
struct sframe_fre{8|16|32} fres_fde1[N_FRE_FDE1] __packed;
...
struct sframe_fre{8|16|32} fres_fdeN[N_FRE_FDEN] __packed;
};Where:
- fdes[] can be binary searched: All fdes[i] are of equal size and sorted on start address.
- Each fdes[i] points at its fres_fdei[].
- fres_fdei[] cannot be binary searched: For each fdes[i] they are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
Quoted 37 lines · Jens Remus
Quoted 26 lines · Andrii Nakryiko
So, currently in v2, FREs within FDEs use an array-of-structs layout. If we use preudo-C type definitions, it would be something like this for FDE + its FREs:
struct FDE_and_FREs { struct sframe_func_desc_entry fde_metadata; union FRE { struct FRE8 { u8 sfre_start_address; u8 sfre_info; u8|u16|u32 offsets[M]; } struct FRE16 { u16 sfre_start_address; u16 sfre_info; u8|u16|u32 offsets[M]; } struct FRE32 { u32 sfre_start_address; u32 sfre_info; u8|u16|u32 offsets[M]; } } fres[N] __packed; };Quoted 4 lines
where all fres[i]s are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
Just for clarification of the SFrame V2 format, as there may be some misconception. Using pseudo-C type definition:
Quoted 16 lines
struct sframe_fre8 {
u8 sfre_start_address;
u8 sfre_info;
s8|s16|s32 offsets[M];
};
struct sframe_fre16 {
u16 sfre_start_address;
u8 sfre_info;
s8|s16|s32 offsets[M];
};
struct sframe_fre32 {
u32 sfre_start_address;
u8 sfre_info;
s8|s16|s32 offsets[M];
};yeah, sorry, copy/paste error for those sfre_info, it's always u8
Quoted 15 lines
struct sframe_section {
/* Headers. */
struct sframe_preamble preamble;
struct sframe_header header;
struct sframe_auxiliary_header auxhdr;
/* FDEs. */
struct sframe_fde fdes[N_FDE];
/* FRE8s / FRE16s / FRE32s per FDE. */
struct sframe_fre{8|16|32} fres_fde1[N_FRE_FDE1] __packed;
...
struct sframe_fre{8|16|32} fres_fdeN[N_FRE_FDEN] __packed;
};Quoted 10 lines
Where:
- fdes[] can be binary searched: All fdes[i] are of equal size and sorted on start address.
- Each fdes[i] points at its fres_fdei[].
- fres_fdei[] cannot be binary searched: For each fdes[i] they are one of those FRE8/FRE16/FRE32, so start addresses have the same size, but each FRE has potentially different offsets sizing, so there is no common alignment, and so everything has to be packed and unaligned.
yep
Quoted 13 lines
Regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
+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/
Re: [PATCH v4 17/39] unwind_user/sframe: Add support for reading .sframe headers
In reply to Andrii NakryikoQuoted 50 lines · Andrii Nakryiko
Quoted 20 lines · Josh Poimboeuf
In preparation for unwinding user space stacks with sframe, add basic sframe compile infrastructure and support for reading the .sframe section header.
sframe_add_section()reads the header and unconditionally returns an error, so it's not very useful yet. A subsequent patch will improve that.Signed-off-by Josh Poimboeuf<[email protected]>--- arch/Kconfig | 3 + include/linux/sframe.h | 36 +++++++++++ kernel/unwind/Makefile | 3 +- kernel/unwind/sframe.c | 136 +++++++++++++++++++++++++++++++++++++++++ kernel/unwind/sframe.h | 71 +++++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-)create mode 100644
include/linux/sframe.h
create mode 100644kernel/unwind/sframe.c
create mode 100644kernel/unwind/sframe.h[...]
include/linux/sframe.h line 31
Quoted 8 lines
include/linux/sframe.h new file +8
-0+ +extern int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end, +unsigned long text_start,unsigned long text_end); +extern int sframe_remove_section (unsigned long sframe_addr); + +#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */ + +static inline int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end) {return -ENOSYS ; }nit: very-very long, wrap it?
Quoted 5 lines
include/linux/sframe.h new file +5
-0+static inline int sframe_remove_section (unsigned long sframe_addr) {return -ENOSYS ; } + +#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */ + +#endif /* _LINUX_SFRAME_H */ [...]
Quoted 10 lines
kernel/unwind/sframe.c new file +10
-0+static int sframe_read_header (struct sframe_section *sec) +{ +unsigned long header_end, fdes_start, fdes_end, fres_start, fres_end; +struct sframe_header shdr; +unsigned int num_fdes; + +if (copy_from_user (&shdr, (void __user *)sec->sframe_start,sizeof (shdr))) { +dbg ("header usercopy failed\n" ); +return -EFAULT ; + }
Quoted 8 lines
kernel/unwind/sframe.c line 40
Quoted 4 lines
kernel/unwind/sframe.c new file +4
-0+ + if (shdr.preamble.magic != SFRAME_MAGIC || + shdr.preamble.version != SFRAME_VERSION_2 || + !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||probably more a question to Indu, but why is this sorting not mandatory and part of SFrame "standard"? How realistically non-sorted FDEs would work in practice? Ain't nobody got time to sort them just to unwind the stack...
It is not worthwhile for the assembler (even wasteful as it adds to build time for nothing) to generate an .sframe section with FDEs in sorted order of start PC of function. This is because the final order is decided by the linker as it merges all input sections.
Thats one reason why it is already necessary that the specification allows SFRAME_F_FDE_SORTED not set in the section. I can also see how not making the sorting mandatory may also be necessary for JIT use-case..
FWIW, for non-JIT environments, non-sorted FDEs are not expected in linked binaries; such a thing does not seem to be useful in practice.
Hope that helps
Indu
Josh Poimboeuf
maintainerPATCH v4 18/39 unwind_user/sframe: Store sframe section data in per-mm maple tree
In reply to Josh PoimboeufAssociate an sframe section with its mm by adding it to a per-mm maple tree which is indexed by the corresponding text address range. A single sframe section can be associated with multiple text ranges.
--- arch/x86/include/asm/mmu.h | 2 +- include/linux/mm_types.h | 3 +++ include/linux/sframe.h | 13 +++++++++ kernel/fork.c | 10 +++++++ kernel/unwind/sframe.c | 55 +++++++++++++++++++++++++++++++++++--- mm/init-mm.c | 2 ++ 6 files changed, 81 insertions(+), 4 deletions(-)
arch/x86/include/asm/mmu.h +1 -1
@@ -73,7 +73,7 @@ typedef struct { .context = { \ .ctx_id = 1, \ .lock = __MUTEX_INITIALIZER(mm.context.lock), \ - } + },void leave_mm (void );#define leave_mm leave_mm
include/linux/mm_types.h +3 -0
@@ -1064,6 +1064,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 +13 -0
@@ -22,14 +22,27 @@ struct sframe_section {signed char fp_off; }; +#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0), +extern void sframe_free_mm (struct mm_struct *mm); +extern int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end);extern int sframe_remove_section (unsigned long sframe_addr); +static inline bool current_has_sframe (void ) +{ +struct mm_struct *mm = current->mm; + +return mm && !mtree_empty (&mm->sframe_mt); +} +#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */ +#define INIT_MM_SFRAME +static inline void sframe_free_mm (struct mm_struct *mm) {}static inline int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end) {return -ENOSYS ; }static inline int sframe_remove_section (unsigned long sframe_addr) {return -ENOSYS ; } +static inline bool current_has_sframe (void ) {return false; }#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
kernel/fork.c +10 -0
@@ -105,6 +105,7 @@#include <uapi/linux/pidfd.h> #include <linux/pidfs.h> #include <linux/tick.h> +#include <linux/sframe.h> #include <asm/pgalloc.h> #include <linux/uaccess.h>
@@ -925,6 +926,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); }
@@ -1252,6 +1254,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) {
@@ -1283,6 +1292,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/unwind/sframe.c +52 -3
67 lines not on this page · raw message
mm/init-mm.c +2 -0
@@ -11,6 +11,7 @@#include <linux/atomic.h> #include <linux/user_namespace.h> #include <linux/iommu.h> +#include <linux/sframe.h> #include <asm/mmu.h> #ifndef INIT_MM_CONTEXT
@@ -45,6 +46,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.48.1
Josh Poimboeuf
maintainerPATCH v4 19/39 unwind_user/sframe: Add support for reading .sframe contents
In reply to Josh PoimboeufIn preparation for using sframe to unwind user space stacks, add an sframe_find() interface for finding the sframe information associated with a given text address.
For performance, use user_read_access_begin() and the corresponding unsafe_*() accessors. Note that use of pr_debug() in uaccess-enabled regions would break noinstr validation, so there aren't any debug messages yet. That will be added in a subsequent commit.
--- include/linux/sframe.h | 5 + kernel/unwind/sframe.c | 295 ++++++++++++++++++++++++++++++++++- kernel/unwind/sframe_debug.h | 35 +++++ 3 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 kernel/unwind/sframe_debug.h
include/linux/sframe.h +5 -0
@@ -3,11 +3,14 @@#define _LINUX_SFRAME_H#include <linux/mm_types.h> +#include <linux/srcu.h> #include <linux/unwind_user_types.h> #ifdef CONFIG_HAVE_UNWIND_USER_SFRAME struct sframe_section { + struct rcu_head rcu; + unsigned long sframe_start; unsigned long sframe_end; unsigned long text_start;
@@ -28,6 +31,7 @@ extern void sframe_free_mm(struct mm_struct *mm);extern int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,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 ) {
@@ -42,6 +46,7 @@ static inline bool current_has_sframe(void)static inline void sframe_free_mm (struct mm_struct *mm) {}static inline int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end) {return -ENOSYS ; }static inline int sframe_remove_section (unsigned long sframe_addr) {return -ENOSYS ; } +static inline int sframe_find (unsigned long ip,struct unwind_user_frame *frame) {return -ENOSYS ; }static inline bool current_has_sframe (void ) {return false; }#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
kernel/unwind/sframe.c +291 -4
321 lines not on this page · raw message
kernel/unwind/sframe_debug.h new file +35 -0
@@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SFRAME_DEBUG_H +#define _SFRAME_DEBUG_H + +#include <linux/sframe.h> +#include "sframe.h" + +#ifdef CONFIG_DYNAMIC_DEBUG + +#define dbg (fmt, ...) \ + pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__) + +static __always_inline void dbg_print_header(struct sframe_section *sec) +{ +unsigned long fdes_end; + + fdes_end = sec->fdes_start + (sec->num_fdes *sizeof (struct sframe_fde )); + +dbg ("SEC: sframe:0x%lx-0x%lx text:0x%lx-0x%lx " +"fdes:0x%lx-0x%lx fres:0x%lx-0x%lx " +"ra_off:%d fp_off:%d\n" , + sec->sframe_start, sec->sframe_end, sec->text_start, sec->text_end, + sec->fdes_start, fdes_end, sec->fres_start, sec->fres_end, + sec->ra_off, sec->fp_off); +} + +#else /* !CONFIG_DYNAMIC_DEBUG */ + +#define dbg(args...) no_printk(args) + +static inline void dbg_print_header (struct sframe_section *sec) {} + +#endif /* !CONFIG_DYNAMIC_DEBUG */ + +#endif /* _SFRAME_DEBUG_H */
Signature · 1 line
2.48.1
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 4 lines · Josh Poimboeuf
include/linux/sframe.h +0
-0@@ -3,11 +3,14 @@#define _LINUX_SFRAME_H
include/linux/sframe.h line 13
Quoted 10 lines
include/linux/sframe.h +3
-0#include <linux/mm_types.h> +#include <linux/srcu.h> #include <linux/unwind_user_types.h> #ifdef CONFIG_HAVE_UNWIND_USER_SFRAME struct sframe_section { + struct rcu_head rcu; +
Nit: You are adding a blank line, that you later remove with "[PATCH v4 25/39] unwind_user/sframe: Show file name in debug output".
include/linux/sframe.h line 16
Quoted 3 lines
include/linux/sframe.h +0
-0unsigned long sframe_start;unsigned long sframe_end;unsigned long text_start;
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 4 lines · Jens Remus
Quoted 1 line · Josh Poimboeuf
include/linux/sframe.h +0
-0
Quoted 12 lines
include/linux/sframe.h line 10
Quoted 9 lines
include/linux/sframe.h +3
-0@@ -3,11 +3,14 @@#define _LINUX_SFRAME_H#include <linux/mm_types.h> +#include <linux/srcu.h> #include <linux/unwind_user_types.h> #ifdef CONFIG_HAVE_UNWIND_USER_SFRAME struct sframe_section { + struct rcu_head rcu; +Nit: You are adding a blank line, that you later remove with "[PATCH v4 25/39] unwind_user/sframe: Show file name in debug output".
I suppose that was intentional. The original blank line created visual separation between the rcu head and the sframe values. The later patch instead sort of uses the ifdef to keep some separation? But yeah, I'll keep the blank lines for consistency. <shrug>
struct sframe_section {
struct rcu_head rcu;#ifdef CONFIG_DYNAMIC_DEBUG const char *filename;
#endif
unsigned long sframe_start; unsigned long sframe_start; unsigned long sframe_end; unsigned long text_start; unsigned long text_end; unsigned long fdes_start; unsigned long fres_start; unsigned long fres_end; unsigned int num_fdes; signed char ra_off; signed char fp_off; };
Signature · 1 line
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
In reply to Josh PoimboeufQuoted 18 lines · Josh Poimboeuf
In preparation for using sframe to unwind user space stacks, add an
sframe_find()interface for finding the sframe information associated with a given text address.For performance, use
user_read_access_begin()and the corresponding unsafe_*() accessors. Note that use ofpr_debug()in uaccess-enabled regions would break noinstr validation, so there aren't any debug messages yet. That will be added in a subsequent commit.Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/sframe.h | 5 + kernel/unwind/sframe.c | 295 ++++++++++++++++++++++++++++++++++- kernel/unwind/sframe_debug.h | 35 +++++ 3 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 kernel/unwind/sframe_debug.h
[...]
kernel/unwind/sframe.c line 56
Quoted 13 lines
kernel/unwind/sframe.c +13
-0+ +static __always_inline int __read_fde(struct sframe_section *sec, +unsigned int fde_num, +struct sframe_fde *fde) +{ +unsigned long fde_addr, ip; + + fde_addr = sec->fdes_start + (fde_num *sizeof (struct sframe_fde )); +unsafe_copy_from_user (fde, (void __user *)fde_addr, +sizeof (struct sframe_fde ), Efault); + + ip = sec->sframe_start + fde->start_addr; +if (ip < sec->text_start || ip > sec->text_end)
ip >= sec->test_end ? ip == sec->test_end doesn't make sense, no?
Quoted 8 lines
kernel/unwind/sframe.c +8
-0+return -EINVAL ; + +return 0 ; + +Efault: +return -EFAULT ; +} +
[...]
Quoted 8 lines
kernel/unwind/sframe.c +8
-0+static __always_inline int __read_fre(struct sframe_section *sec, +struct sframe_fde *fde, +unsigned long fre_addr, +struct sframe_fre *fre) +{ +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;
kernel/unwind/sframe.c line 164
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+s32 ip_off, cfa_off, ra_off, fp_off; +unsigned long cur = fre_addr; +unsigned char addr_size; +u8 info; + + addr_size =fre_type_to_size (fre_type); +if (!addr_size) +return -EFAULT ; + +if (fre_addr + addr_size +1 > sec->fres_end)
nit: isn't this the same as fre_addr + addr_size >= sec->fres_end ?
kernel/unwind/sframe.c line 168
Quoted 4 lines
kernel/unwind/sframe.c +4
-0+return -EFAULT ; + +UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault); +if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size)
is ip_off == fde->func_size allowable?
kernel/unwind/sframe.c line 177
Quoted 9 lines
kernel/unwind/sframe.c +9
-0+return -EFAULT ; + +UNSAFE_GET_USER_INC (info, cur,1 , Efault); + offset_count =SFRAME_FRE_OFFSET_COUNT (info); + offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end)
offset_count * offset_size done in u8 can overflow, no? maybe upcast to unsigned long or use check_add_overflow?
Quoted 33 lines
kernel/unwind/sframe.c +33
-0+return -EFAULT ; + + fre->size = addr_size +1 + (offset_count * offset_size); + +UNSAFE_GET_USER_INC (cfa_off, cur, offset_size, Efault); + offset_count--; + + ra_off = sec->ra_off; +if (!ra_off) { +if (!offset_count--) +return -EFAULT ; + +UNSAFE_GET_USER_INC (ra_off, cur, offset_size, Efault); + } + + fp_off = sec->fp_off; +if (!fp_off && offset_count) { + offset_count--; +UNSAFE_GET_USER_INC (fp_off, cur, offset_size, Efault); + } + +if (offset_count) +return -EFAULT ; + + fre->ip_off = ip_off; + fre->cfa_off = cfa_off; + fre->ra_off = ra_off; + fre->fp_off = fp_off; + fre->info = info; + +return 0 ; + +Efault:
kernel/unwind/sframe.c line 220
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+return -EFAULT ; +} + +static __always_inline 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); +struct sframe_fre *fre, *prev_fre =NULL ; +struct sframe_fre fres[2 ];
you only need prev_fre->ip_off, so why all this which and fres[2] business if all you need is prev_fre_off and a bool whether you have prev_fre at all?
kernel/unwind/sframe.c line 229
Quoted 9 lines
kernel/unwind/sframe.c +9
-0+unsigned long fre_addr; +bool which = false; +unsigned int i; +s32 ip_off; + + ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr; + +if (fde_type ==SFRAME_FDE_TYPE_PCMASK ) + ip_off %= fde->rep_size;
did you check that fde->rep_size is not zero?
kernel/unwind/sframe.c line 233
Quoted 4 lines
kernel/unwind/sframe.c +4
-0+ + fre_addr = sec->fres_start + fde->fres_off; + +for (i =0 ; i < fde->fres_num; i++) {
why not binary search? seem more logical to guard against cases with lots of FREs and be pretty fast in common case anyways.
Quoted 36 lines
kernel/unwind/sframe.c +36
-0+int ret; + +/* +* Alternate between the two fre_addr[] entries for 'fre' and +* 'prev_fre'. +*/ + fre = which ? fres : fres +1 ; + which = !which; + + ret =__read_fre (sec, fde, fre_addr, fre); +if (ret) +return ret; + + fre_addr += fre->size; + +if (prev_fre && fre->ip_off <= prev_fre->ip_off) +return -EFAULT ; + +if (fre->ip_off > ip_off) +break ; + + prev_fre = fre; + } + +if (!prev_fre) +return -EINVAL ; + fre = prev_fre; + + frame->cfa_off = fre->cfa_off; + frame->ra_off = fre->ra_off; + frame->fp_off = fre->fp_off; + frame->use_fp =SFRAME_FRE_CFA_BASE_REG_ID (fre->info) ==SFRAME_BASE_REG_FP ; + +return 0 ; +} +
[...]
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 15 lines · Andrii Nakryiko
kernel/unwind/sframe.c line 56
Quoted 12 lines · Josh Poimboeuf
kernel/unwind/sframe.c +12
-0+static __always_inline int __read_fde(struct sframe_section *sec, +unsigned int fde_num, +struct sframe_fde *fde) +{ +unsigned long fde_addr, ip; + + fde_addr = sec->fdes_start + (fde_num *sizeof (struct sframe_fde )); +unsafe_copy_from_user (fde, (void __user *)fde_addr, +sizeof (struct sframe_fde ), Efault); + + ip = sec->sframe_start + fde->start_addr; +if (ip < sec->text_start || ip > sec->text_end)ip >=
sec->test_end? ip ==sec->test_enddoesn't make sense, no?
Believe it or not, this may have been intentional: 'text_end' can actually be a valid stack return address if the last instruction of the section is a call to a noreturn function. The unwinder still needs to know the state of the stack at that point.
That said, there would be no reason to put an FDE at the end. So yeah, it should be '>='.
Quoted 12 lines
Quoted 12 lines
kernel/unwind/sframe.c +12
-0+static __always_inline int __read_fre(struct sframe_section *sec, +struct sframe_fde *fde, +unsigned long fre_addr, +struct sframe_fre *fre) +{ +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 ip_off, cfa_off, ra_off, fp_off; +unsigned long cur = fre_addr; +unsigned char addr_size; +u8 info;
Quoted 8 lines
kernel/unwind/sframe.c line 164
Quoted 6 lines
kernel/unwind/sframe.c +6
-0+ + addr_size =fre_type_to_size (fre_type); +if (!addr_size) +return -EFAULT ; + +if (fre_addr + addr_size +1 > sec->fres_end)nit: isn't this the same as
fre_addr + addr_size >= sec->fres_end?
Yes, but that would further obfuscate the fact that this is validating that the next two reads don't go past sec->fres_end:
UNSAFE_GET_USER_INC(ip_off, cur, addr_size, Efault); UNSAFE_GET_USER_INC(info, cur, 1, Efault);
The explicit "1" is for that second read.
I'll do something like SFRAME_FRE_INFO_SIZE instead of 1 to make that clearer.
Quoted 6 lines
kernel/unwind/sframe.c line 168
Quoted 4 lines
kernel/unwind/sframe.c +4
-0+return -EFAULT ; + +UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault); +if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size)is
ip_off==fde->func_sizeallowable?
This was another one where I was probably thinking about the whole "last insn could be a call to noreturn" scenario.
But even in that case, the frame would be unchanged after the call, so there shouldn't need to be an FRE there.
In fact, for the unwinder to deal with that scenario, for a call frame (as opposed to an interrupted one), it should always subtract one from the return address before calling into sframe so that it points to the calling instruction. </me makes note to go do that>
So yeah, this looks like another off-by-one, caused by overthinking yet not thinking enough.
Quoted 4 lines
Quoted 4 lines
kernel/unwind/sframe.c +4
-0+return -EFAULT ; + +UNSAFE_GET_USER_INC (info, cur,1 , Efault); + offset_count =SFRAME_FRE_OFFSET_COUNT (info);
Quoted 8 lines
kernel/unwind/sframe.c line 177
Quoted 5 lines
kernel/unwind/sframe.c +5
-0+ offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end)
offset_count*offset_sizedone in u8 can overflow, no? maybe upcast to unsigned long or usecheck_add_overflow?
offset_size is <= 2 as returned by offset_size_enum_to_size().
offset_count is expected to be <= 3, enforced by the !offset_count check at the bottom.
An overflow here would be harmless as it would be caught by the !offset_count anyway. Though I also notice offset_count isn't big enough to hold the 2-byte SFRAME_FRE_OFFSET_COUNT() value. Which is harmless for the same reason, but yeah I'll make offset_count an unsigned int.
Quoted 11 lines
kernel/unwind/sframe.c line 220
Quoted 7 lines
kernel/unwind/sframe.c +7
-0+static __always_inline 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); +struct sframe_fre *fre, *prev_fre =NULL ; +struct sframe_fre fres[2 ];you only need
prev_fre->ip_off, so why all thiswhichandfres[2]business if all you need isprev_fre_offand a bool whether you haveprev_freat all?
So this cleverness probably needs a comment. prev_fre is actually needed after the loop:
Quoted 3 lines
kernel/unwind/sframe.c line 260
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+if (!prev_fre) +return -EINVAL ; + fre = prev_fre;
In the body of the loop, prev_fre is a tentative match, unless the next fre also matches, in which case that one becomes the new tentative match.
I'll add a comment. Also it'll probably be less confusing if I rename "prev_fre" to "fre", and "fre" to "next_fre".
Quoted 11 lines
kernel/unwind/sframe.c line 229
Quoted 9 lines
kernel/unwind/sframe.c +9
-0+unsigned long fre_addr; +bool which = false; +unsigned int i; +s32 ip_off; + + ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr; + +if (fde_type ==SFRAME_FDE_TYPE_PCMASK ) + ip_off %= fde->rep_size;did you check that
fde->rep_sizeis not zero?
I did not, good catch!
Quoted 6 lines
kernel/unwind/sframe.c line 233
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+ fre_addr = sec->fres_start + fde->fres_off; + +for (i =0 ; i < fde->fres_num; i++) {why not binary search? seem more logical to guard against cases with lots of FREs and be pretty fast in common case anyways.
That would be nice, but the FREs are variable-sized and you don't know how big one is until you start reading it.
Signature · 1 line
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 18 lines · Josh Poimboeuf
Quoted 15 lines · Andrii Nakryiko
kernel/unwind/sframe.c line 56
Quoted 12 lines · Josh Poimboeuf
kernel/unwind/sframe.c +12
-0+static __always_inline int __read_fde(struct sframe_section *sec, +unsigned int fde_num, +struct sframe_fde *fde) +{ +unsigned long fde_addr, ip; + + fde_addr = sec->fdes_start + (fde_num *sizeof (struct sframe_fde )); +unsafe_copy_from_user (fde, (void __user *)fde_addr, +sizeof (struct sframe_fde ), Efault); + + ip = sec->sframe_start + fde->start_addr; +if (ip < sec->text_start || ip > sec->text_end)ip >=
sec->test_end? ip ==sec->test_enddoesn't make sense, no?
Quoted 5 lines
Believe it or not, this may have been intentional: '
text_end' can actually be a valid stack return address if the last instruction of the section is a call to a noreturn function. The unwinder still needs to know the state of the stack at that point.
TIL...
Quoted 27 lines
That said, there would be no reason to put an FDE at the end. So yeah, it should be '>='.
Quoted 12 lines
Quoted 12 lines
kernel/unwind/sframe.c +12
-0+static __always_inline int __read_fre(struct sframe_section *sec, +struct sframe_fde *fde, +unsigned long fre_addr, +struct sframe_fre *fre) +{ +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 ip_off, cfa_off, ra_off, fp_off; +unsigned long cur = fre_addr; +unsigned char addr_size; +u8 info;Quoted 8 lines
kernel/unwind/sframe.c line 164
Quoted 6 lines
kernel/unwind/sframe.c +6
-0+ + addr_size =fre_type_to_size (fre_type); +if (!addr_size) +return -EFAULT ; + +if (fre_addr + addr_size +1 > sec->fres_end)nit: isn't this the same as
fre_addr + addr_size >= sec->fres_end?Yes, but that would further obfuscate the fact that this is validating that the next two reads don't go past
sec->fres_end:
Quoted 7 lines
UNSAFE_GET_USER_INC(ip_off, cur, addr_size, Efault); UNSAFE_GET_USER_INC(info, cur, 1, Efault);The explicit "1" is for that second read.
I'll do something like
SFRAME_FRE_INFO_SIZEinstead of 1 to make that clearer.
yeah, I definitely didn't connect 1 with the size of fre info byte, named #define makes sense, thanks!
Quoted 37 lines
Quoted 7 lines
kernel/unwind/sframe.c line 168
Quoted 5 lines
kernel/unwind/sframe.c +4
-0+return -EFAULT ; + +UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault); +if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size)is
ip_off==fde->func_sizeallowable?This was another one where I was probably thinking about the whole "last insn could be a call to noreturn" scenario.
But even in that case, the frame would be unchanged after the call, so there shouldn't need to be an FRE there.
In fact, for the unwinder to deal with that scenario, for a call frame (as opposed to an interrupted one), it should always subtract one from the return address before calling into sframe so that it points to the calling instruction. </me makes note to go do that>
So yeah, this looks like another off-by-one, caused by overthinking yet not thinking enough.
Quoted 4 lines
Quoted 4 lines
kernel/unwind/sframe.c +4
-0+return -EFAULT ; + +UNSAFE_GET_USER_INC (info, cur,1 , Efault); + offset_count =SFRAME_FRE_OFFSET_COUNT (info);Quoted 8 lines
kernel/unwind/sframe.c line 177
Quoted 5 lines
kernel/unwind/sframe.c +5
-0+ offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end)
offset_count*offset_sizedone in u8 can overflow, no? maybe upcast to unsigned long or usecheck_add_overflow?
offset_sizeis <= 2 as returned byoffset_size_enum_to_size().
Quoted 8 lines
offset_countis expected to be <= 3, enforced by the !offset_count check at the bottom.An overflow here would be harmless as it would be caught by the !offset_count anyway. Though I also notice
offset_countisn't big enough to hold the 2-byteSFRAME_FRE_OFFSET_COUNT()value. Which is harmless for the same reason, but yeah I'll makeoffset_countan unsigned int.
yeah, the less whoever is reading has to worry about overflows, the better, thanks!
Quoted 20 lines
Quoted 12 lines
kernel/unwind/sframe.c line 220
Quoted 8 lines
kernel/unwind/sframe.c +7
-0+static __always_inline 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); +struct sframe_fre *fre, *prev_fre =NULL ; +struct sframe_fre fres[2 ];you only need
prev_fre->ip_off, so why all thiswhichandfres[2]business if all you need isprev_fre_offand a bool whether you haveprev_freat all?So this cleverness probably needs a comment.
prev_freis actually needed after the loop:Quoted 3 lines
kernel/unwind/sframe.c line 260
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+if (!prev_fre) +return -EINVAL ; + fre = prev_fre;
Quoted 6 lines
In the body of the loop,
prev_freis a tentative match, unless the next fre also matches, in which case that one becomes the new tentative match.I'll add a comment. Also it'll probably be less confusing if I rename "
prev_fre" to "fre", and "fre" to "next_fre".
ah, yeah, I missed that, you are right
Quoted 19 lines
Quoted 12 lines
kernel/unwind/sframe.c line 229
Quoted 10 lines
kernel/unwind/sframe.c +9
-0+unsigned long fre_addr; +bool which = false; +unsigned int i; +s32 ip_off; + + ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr; + +if (fde_type ==SFRAME_FDE_TYPE_PCMASK ) + ip_off %= fde->rep_size;did you check that
fde->rep_sizeis not zero?I did not, good catch!
Quoted 4 lines
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+ fre_addr = sec->fres_start + fde->fres_off; + +for (i =0 ; i < fde->fres_num; i++) {
Quoted 5 lines
Quoted 2 lines
why not binary search? seem more logical to guard against cases with lots of FREs and be pretty fast in common case anyways.
That would be nice, but the FREs are variable-sized and you don't know how big one is until you start reading it.
ah, another non-obvious thing, yeah... do you think it's worth fixing this and making FREs binary searchable in v3?
Indu, do you have some stats on distribution of FRE count per FDE in practice?
Tbh, FRE format is bothering me quite a lot... but let's discuss that in another thread with you and Indu
Quoted 3 lines
--
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 12 lines · Andrii Nakryiko
Quoted 11 lines · Josh Poimboeuf
Quoted 10 lines · Andrii Nakryiko
Quoted 7 lines · Josh Poimboeuf
kernel/unwind/sframe.c +7
-0+UNSAFE_GET_USER_INC (info, cur,1 , Efault); + offset_count =SFRAME_FRE_OFFSET_COUNT (info); + offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end)
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
offset_count*offset_sizedone in u8 can overflow, no? maybe upcast to unsigned long or usecheck_add_overflow?
The maximum offset_count * offset_size is 15 * 4 = 60 if I am not wrong:
Quoted 1 line
Quoted 1 line
offset_sizeis <= 2 as returned byoffset_size_enum_to_size().
SFrame V2 FRE offset sizes are either 1, 2, or 4 bytes. This is also reflected in offset_size_enum_to_size().
Quoted 2 lines
Quoted 2 lines
offset_countis expected to be <= 3, enforced by the !offset_count check at the bottom.
SFrame V2 FRE offset count is 4 bits unsigned, so 0 <= offset_count <= 15.
Quoted 5 lines
Quoted 5 lines
An overflow here would be harmless as it would be caught by the !offset_count anyway. Though I also notice
offset_countisn't big enough to hold the 2-byteSFRAME_FRE_OFFSET_COUNT()value. Which is harmless for the same reason, but yeah I'll makeoffset_countan unsigned int.
As mentioned above the FRE offset count is 4 bits, not 2 bytes. This is also reflected in SFRAME_FRE_OFFSET_COUNT().
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 27 lines · Jens Remus
Quoted 12 lines · Andrii Nakryiko
Quoted 11 lines · Josh Poimboeuf
Quoted 10 lines · Andrii Nakryiko
Quoted 7 lines · Josh Poimboeuf
kernel/unwind/sframe.c +7
-0+UNSAFE_GET_USER_INC (info, cur,1 , Efault); + offset_count =SFRAME_FRE_OFFSET_COUNT (info); + offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end)Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
offset_count*offset_sizedone in u8 can overflow, no? maybe upcast to unsigned long or usecheck_add_overflow?The maximum
offset_count*offset_sizeis 15 * 4 = 60 if I am not wrong:Quoted 1 line
Quoted 1 line
offset_sizeis <= 2 as returned byoffset_size_enum_to_size().SFrame V2 FRE offset sizes are either 1, 2, or 4 bytes. This is also reflected in
offset_size_enum_to_size().Quoted 2 lines
Quoted 2 lines
offset_countis expected to be <= 3, enforced by the !offset_count check at the bottom.SFrame V2 FRE offset count is 4 bits unsigned, so 0 <=
offset_count<= 15.
Quoted 8 lines
Quoted 5 lines
Quoted 5 lines
An overflow here would be harmless as it would be caught by the !offset_count anyway. Though I also notice
offset_countisn't big enough to hold the 2-byteSFRAME_FRE_OFFSET_COUNT()value. Which is harmless for the same reason, but yeah I'll makeoffset_countan unsigned int.As mentioned above the FRE offset count is 4 bits, not 2 bytes. This is also reflected in
SFRAME_FRE_OFFSET_COUNT().
You are right on both counts, not sure what I was smoking that day.
Signature · 1 line
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
In reply to Andrii NakryikoQuoted 14 lines · Andrii Nakryiko
Quoted 7 lines · Josh Poimboeuf
Quoted 6 lines · Andrii Nakryiko
Quoted 3 lines · Josh Poimboeuf
kernel/unwind/sframe.c +3
-0+ fre_addr = sec->fres_start + fde->fres_off; + +for (i =0 ; i < fde->fres_num; i++) {Quoted 5 lines
Quoted 2 lines
why not binary search? seem more logical to guard against cases with lots of FREs and be pretty fast in common case anyways.
That would be nice, but the FREs are variable-sized and you don't know how big one is until you start reading it.
Quoted 7 lines
ah, another non-obvious thing, yeah... do you think it's worth fixing this and making FREs binary searchable in v3?
Indu, do you have some stats on distribution of FRE count per FDE in practice?
Tbh, FRE format is bothering me quite a lot... but let's discuss that in another thread with you and Indu
I would be interested to be part of that discussion. I could share some preliminary stats from my s390 work.
Thanks and regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
In reply to Josh PoimboeufQuoted 21 lines · Josh Poimboeuf
Quoted 12 lines · Andrii Nakryiko
kernel/unwind/sframe.c line 220
Quoted 7 lines · Josh Poimboeuf
kernel/unwind/sframe.c +7
-0+static __always_inline 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); +struct sframe_fre *fre, *prev_fre =NULL ; +struct sframe_fre fres[2 ];you only need
prev_fre->ip_off, so why all thiswhichandfres[2]business if all you need isprev_fre_offand a bool whether you haveprev_freat all?So this cleverness probably needs a comment.
prev_freis actually needed after the loop:Quoted 3 lines
kernel/unwind/sframe.c line 260
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+if (!prev_fre) +return -EINVAL ; + fre = prev_fre;
Quoted 7 lines
In the body of the loop,
prev_freis a tentative match, unless the next fre also matches, in which case that one becomes the new tentative match.I'll add a comment. Also it'll probably be less confusing if I rename "
prev_fre" to "fre", and "fre" to "next_fre".
Nit: swap() might be a simplify way to alternate pointers between two fre_addr[] entries.
For example,
static __always_inline int __find_fre(struct sframe_section *sec,
struct sframe_fde *fde, unsigned long ip, struct unwind_user_frame *frame)
{
/* intialize fres[] with invalid value */
struct sframe_fre fres[2] = {0};
struct sframe_fre *fre = &fres[1], *prev_fre = fres;
for (i = 0; i < fde->fres_num; i++) {
swap(fre, next_fre);
ret = __read_fre(sec, fde, fre_addr, fre);...
if (fre->ip_off > ip_off) break; } if (fre->size == 0) return -EINVAL;
...
}
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 8 lines · Weinan Liu
Nit:
swap()might be a simplify way to alternate pointers between two fre_addr[] entries.For example,
static
__always_inlineint __find_fre(structsframe_section*sec,
structsframe_fde*fde, unsigned long ip, structunwind_user_frame*frame)
Quoted 8 lines
{
/* intialize fres[] with invalid value */
struct sframe_fre fres[2] = {0};
struct sframe_fre *fre = &fres[1], *prev_fre = fres;
for (i = 0; i < fde->fres_num; i++) {
swap(fre, next_fre);
ret = __read_fre(sec, fde, fre_addr, fre);Problem is, if it breaks out early here on the first iteration:
Quoted 6 lines
if (fre->ip_off > ip_off) break; } if (fre->size == 0) return -EINVAL;
Then fre isn't valid even though it has a nonzero size.
Signature · 1 line
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
In reply to Josh PoimboeufQuoted 281 lines · Josh Poimboeuf
In preparation for using sframe to unwind user space stacks, add an
sframe_find()interface for finding the sframe information associated with a given text address.For performance, use
user_read_access_begin()and the corresponding unsafe_*() accessors. Note that use ofpr_debug()in uaccess-enabled regions would break noinstr validation, so there aren't any debug messages yet. That will be added in a subsequent commit.Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/sframe.h | 5 + kernel/unwind/sframe.c | 295 ++++++++++++++++++++++++++++++++++- kernel/unwind/sframe_debug.h | 35 +++++ 3 files changed, 331 insertions(+), 4 deletions(-)create mode 100644
kernel/unwind/sframe_debug.hinclude/linux/sframe.h +5
-0@@ -3,11 +3,14 @@#define _LINUX_SFRAME_H#include <linux/mm_types.h> +#include <linux/srcu.h> #include <linux/unwind_user_types.h> #ifdef CONFIG_HAVE_UNWIND_USER_SFRAME struct sframe_section { + struct rcu_head rcu; + unsigned long sframe_start; unsigned long sframe_end; unsigned long text_start;@@ -28,6 +31,7 @@ extern void sframe_free_mm(struct mm_struct *mm);extern int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,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 ) {@@ -42,6 +46,7 @@ static inline bool current_has_sframe(void)static inline void sframe_free_mm (struct mm_struct *mm) {}static inline int sframe_add_section (unsigned long sframe_start,unsigned long sframe_end,unsigned long text_start,unsigned long text_end) {return -ENOSYS ; }static inline int sframe_remove_section (unsigned long sframe_addr) {return -ENOSYS ; } +static inline int sframe_find (unsigned long ip,struct unwind_user_frame *frame) {return -ENOSYS ; }static inline bool current_has_sframe (void ) {return false; }#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */ kernel/unwind/sframe.c +218
-2@@ -15,9 +15,287 @@#include <linux/unwind_user_types.h> #include "sframe.h" +#include "sframe_debug.h" -#define dbg (fmt, ...) \ - pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__) +struct sframe_fre { +unsigned int size; +s32 ip_off; +s32 cfa_off; +s32 ra_off; +s32 fp_off; +u8 info; +}; + +DEFINE_STATIC_SRCU (sframe_srcu); + +static __always_inline unsigned char fre_type_to_size(unsigned char fre_type) +{ +if (fre_type >2 ) +return 0 ; +return 1 << fre_type; +} + +static __always_inline unsigned char offset_size_enum_to_size(unsigned char off_size) +{ +if (off_size >2 ) +return 0 ; +return 1 << off_size; +} + +static __always_inline int __read_fde(struct sframe_section *sec, +unsigned int fde_num, +struct sframe_fde *fde) +{ +unsigned long fde_addr, ip; + + fde_addr = sec->fdes_start + (fde_num *sizeof (struct sframe_fde )); +unsafe_copy_from_user (fde, (void __user *)fde_addr, +sizeof (struct sframe_fde ), Efault); + + ip = sec->sframe_start + fde->start_addr; +if (ip < sec->text_start || ip > sec->text_end) +return -EINVAL ; + +return 0 ; + +Efault: +return -EFAULT ; +} + +static __always_inline int __find_fde(struct sframe_section *sec, +unsigned long ip, +struct sframe_fde *fde) +{ +s32 ip_off, func_off_low =S32_MIN , func_off_high =S32_MAX ; +struct sframe_fde __user *first, *low, *high, *found =NULL ; +int ret; + + ip_off = ip - sec->sframe_start; + + first = (void __user *)sec->fdes_start; + low = first; + high = first + sec->num_fdes -1 ; + +while (low <= high) { +struct sframe_fde __user *mid; +s32 func_off; + + mid = low + ((high - low) /2 ); + +unsafe_get_user (func_off, (s32 __user *)mid, Efault); + +if (ip_off >= func_off) { +if (func_off < func_off_low) +return -EFAULT ; + + func_off_low = func_off; + + found = mid; + low = mid +1 ; + }else { +if (func_off > func_off_high) +return -EFAULT ; + + func_off_high = func_off; + + high = mid -1 ; + } + } + +if (!found) +return -EINVAL ; + + ret =__read_fde (sec, found - first, fde); +if (ret) +return ret; + +/* make sure it's not in a gap */ +if (ip_off < fde->start_addr || ip_off >= fde->start_addr + fde->func_size) +return -EINVAL ; + +return 0 ; + +Efault: +return -EFAULT ; +} + +#define __UNSAFE_GET_USER_INC (to, from, type, label) \ +({ \ + type __to; \ + unsafe_get_user(__to, (type __user *)from, label); \ + from += sizeof(__to); \ + to = (typeof(to))__to; \ +}) + +#define UNSAFE_GET_USER_INC (to, from, size, label) \ +({ \ + switch (size) { \ + case 1: \ + __UNSAFE_GET_USER_INC(to, from, u8, label); \ + break; \ + case 2: \ + __UNSAFE_GET_USER_INC(to, from, u16, label); \ + break; \ + case 4: \ + __UNSAFE_GET_USER_INC(to, from, u32, label); \ + break; \ + default: \ + return -EFAULT; \ + } \ +}) + +static __always_inline int __read_fre(struct sframe_section *sec, +struct sframe_fde *fde, +unsigned long fre_addr, +struct sframe_fre *fre) +{ +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 ip_off, cfa_off, ra_off, fp_off; +unsigned long cur = fre_addr; +unsigned char addr_size; +u8 info; + + addr_size =fre_type_to_size (fre_type); +if (!addr_size) +return -EFAULT ; + +if (fre_addr + addr_size +1 > sec->fres_end) +return -EFAULT ; + +UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault); +if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size) +return -EFAULT ; + +UNSAFE_GET_USER_INC (info, cur,1 , Efault); + offset_count =SFRAME_FRE_OFFSET_COUNT (info); + offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end) +return -EFAULT ; + + fre->size = addr_size +1 + (offset_count * offset_size); + +UNSAFE_GET_USER_INC (cfa_off, cur, offset_size, Efault); + offset_count--; + + ra_off = sec->ra_off; +if (!ra_off) { +if (!offset_count--) +return -EFAULT ; + +UNSAFE_GET_USER_INC (ra_off, cur, offset_size, Efault); + } + + fp_off = sec->fp_off; +if (!fp_off && offset_count) { + offset_count--; +UNSAFE_GET_USER_INC (fp_off, cur, offset_size, Efault); + } + +if (offset_count) +return -EFAULT ; + + fre->ip_off = ip_off; + fre->cfa_off = cfa_off; + fre->ra_off = ra_off; + fre->fp_off = fp_off; + fre->info = info; + +return 0 ; + +Efault: +return -EFAULT ; +} + +static __always_inline 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); +struct sframe_fre *fre, *prev_fre =NULL ; +struct sframe_fre fres[2 ]; +unsigned long fre_addr; +bool which = false; +unsigned int i; +s32 ip_off; + + ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr; + +if (fde_type ==SFRAME_FDE_TYPE_PCMASK ) + ip_off %= fde->rep_size; + + fre_addr = sec->fres_start + fde->fres_off; + +for (i =0 ; i < fde->fres_num; i++) { +int ret; + +/*
kernel/unwind/sframe.c line 246
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+* Alternate between the two fre_addr[] entries for 'fre' and +* 'prev_fre'. +*/ + fre = which ? fres : fres +1 ; + which = !which; + + ret =__read_fre (sec, fde, fre_addr, fre); +if (ret) +return ret; +
It should be possible to only read the ip_off and info from FRE and defer the reading of offsets (as done in __read_fre) until later when you do need the offsets. See below.
We can find the relevant FRE with the following pieces of information:
- ip_off
- fre_size (this will mean we need to read the uin8_t info in the FRE)
Quoted 5 lines
kernel/unwind/sframe.c +5
-0+ fre_addr += fre->size; + +if (prev_fre && fre->ip_off <= prev_fre->ip_off) +return -EFAULT ; +
kernel/unwind/sframe.c line 261
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+if (fre->ip_off > ip_off) +break ; + + prev_fre = fre; + } + +if (!prev_fre) +return -EINVAL ; + fre = prev_fre; +
(Invoke the __read_fre here as we know now that this FRE is what we are looking for.)
Quoted 7 lines
kernel/unwind/sframe.c +7
-0+ frame->cfa_off = fre->cfa_off; + frame->ra_off = fre->ra_off; + frame->fp_off = fre->fp_off; + frame->use_fp =SFRAME_FRE_CFA_BASE_REG_ID (fre->info) ==SFRAME_BASE_REG_FP ; + +return 0 ; +}
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 20 lines · Indu Bhagat
Quoted 4 lines · Josh Poimboeuf
kernel/unwind/sframe.c +4
-0+for (i =0 ; i < fde->fres_num; i++) { +int ret; + +/* kernel/unwind/sframe.c line 246
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+* Alternate between the two fre_addr[] entries for 'fre' and +* 'prev_fre'. +*/ + fre = which ? fres : fres +1 ; + which = !which; + + ret =__read_fre (sec, fde, fre_addr, fre); +if (ret) +return ret; +It should be possible to only read the
ip_offand info from FRE and defer the reading of offsets (as done in__read_fre) until later when you do need the offsets. See below.
Quoted 3 lines
We can find the relevant FRE with the following pieces of information:
-ip_off
-fre_size(this will mean we need to read theuin8_tinfo in the FRE)
Indeed, I'll skip reading the offsets until after the loop.
Signature · 1 line
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
In reply to Josh PoimboeufQuoted 12 lines · Josh Poimboeuf
In preparation for using sframe to unwind user space stacks, add an
sframe_find()interface for finding the sframe information associated with a given text address.For performance, use
user_read_access_begin()and the corresponding unsafe_*() accessors. Note that use ofpr_debug()in uaccess-enabled regions would break noinstr validation, so there aren't any debug messages yet. That will be added in a subsequent commit.Signed-off-by Josh Poimboeuf <[email protected]>
kernel/unwind/sframe.c
Quoted 4 lines
kernel/unwind/sframe.c +3
-0+struct sframe_fre { +unsigned int size; +s32 ip_off;
The IP offset (from function start) in the SFrame V2 FDE is unsigned:
u32 ip_off;
Quoted 5 lines
kernel/unwind/sframe.c +5
-0+s32 cfa_off; +s32 ra_off; +s32 fp_off; +u8 info; +};
...
Quoted 14 lines
kernel/unwind/sframe.c +14
-0+#define __UNSAFE_GET_USER_INC (to, from, type, label) \ +({ \ + type __to; \ + unsafe_get_user(__to, (type __user *)from, label); \ + from += sizeof(__to); \ + to = (typeof(to))__to; \ +}) + +#define UNSAFE_GET_USER_INC (to, from, size, label) \ +({ \ + switch (size) { \ + case 1: \ + __UNSAFE_GET_USER_INC(to, from, u8, label); \ + break; \
kernel/unwind/sframe.c
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+case 2 : \ +__UNSAFE_GET_USER_INC (to, from, u16, label); \ +break ; \ +case 4 : \ +__UNSAFE_GET_USER_INC (to, from, u32, label); \ +break ; \ +default : \ +return -EFAULT ; \ + } \ +})
This does not work for the signed SFrame fields, such as the FRE CFA, RA, and FP offsets, as it does not perform the required sign extension. One option would be to rename to UNSAFE_GET_USER_UNSIGNED_INC() and re-introduce UNSAFE_GET_USER_SIGNED_INC() using s8, s16, and s32.
kernel/unwind/sframe.c
Quoted 9 lines
kernel/unwind/sframe.c +9
-0+static __always_inline int __read_fre(struct sframe_section *sec, +struct sframe_fde *fde, +unsigned long fre_addr, +struct sframe_fre *fre) +{ +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 ip_off, cfa_off, ra_off, fp_off;
The IP offset (from function start) in the SFrame V2 FRE is unsigned:
u32 ip_off;
s32 cfa_off, ra_off, fp_off;
kernel/unwind/sframe.c
Quoted 12 lines
kernel/unwind/sframe.c +12
-0+unsigned long cur = fre_addr; +unsigned char addr_size; +u8 info; + + addr_size =fre_type_to_size (fre_type); +if (!addr_size) +return -EFAULT ; + +if (fre_addr + addr_size +1 > sec->fres_end) +return -EFAULT ; + +UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault);
Ok: The SFrame V2 FRE IP offset is unsigned u8, u16, or u32.
kernel/unwind/sframe.c
Quoted 4 lines
kernel/unwind/sframe.c +4
-0+if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size) +return -EFAULT ; + +UNSAFE_GET_USER_INC (info, cur,1 , Efault);
Ok: The SFrame V2 FRE info word is one byte of data.
kernel/unwind/sframe.c
Quoted 11 lines
kernel/unwind/sframe.c +11
-0+ offset_count =SFRAME_FRE_OFFSET_COUNT (info); + offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); +if (!offset_count || !offset_size) +return -EFAULT ; + +if (cur + (offset_count * offset_size) > sec->fres_end) +return -EFAULT ; + + fre->size = addr_size +1 + (offset_count * offset_size); + +UNSAFE_GET_USER_INC (cfa_off, cur, offset_size, Efault);
Issue: The SFrame V2 FRE CFA offset is signed s8, s16, or s32. Sign extension required when storing in s32.
kernel/unwind/sframe.c
Quoted 8 lines
kernel/unwind/sframe.c +8
-0+ offset_count--; + + ra_off = sec->ra_off; +if (!ra_off) { +if (!offset_count--) +return -EFAULT ; + +UNSAFE_GET_USER_INC (ra_off, cur, offset_size, Efault);
Issue: The SFrame V2 FRE RA offset is signed s8, s16, or s32. Sign extension required when storing in s32.
kernel/unwind/sframe.c
Quoted 6 lines
kernel/unwind/sframe.c +6
-0+ } + + fp_off = sec->fp_off; +if (!fp_off && offset_count) { + offset_count--; +UNSAFE_GET_USER_INC (fp_off, cur, offset_size, Efault);
Issue: The SFrame V2 FRE FP offset is signed s8, s16, or s32. Sign extension required when storing in s32.
Quoted 20 lines
kernel/unwind/sframe.c +20
-0+ } + +if (offset_count) +return -EFAULT ; + + fre->ip_off = ip_off; + fre->cfa_off = cfa_off; + fre->ra_off = ra_off; + fre->fp_off = fp_off; + fre->info = info; + +return 0 ; + +Efault: +return -EFAULT ; +} + +static __always_inline int __find_fre(struct sframe_section *sec, +struct sframe_fde *fde,unsigned long ip, +struct unwind_user_frame *frame)
kernel/unwind/sframe.c
Quoted 10 lines
kernel/unwind/sframe.c +10
-0+{ +unsigned char fde_type =SFRAME_FUNC_FDE_TYPE (fde->info); +struct sframe_fre *fre, *prev_fre =NULL ; +struct sframe_fre fres[2 ]; +unsigned long fre_addr; +bool which = false; +unsigned int i; +s32 ip_off; + + ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr;
The IP offset (from function start) in the SFrame V2 FRE is unsigned. The function start address offset (from .sframe section begin) is signed. Therefore:
u32 ip_off;
ip_off = ip - (sec->sframe_start + fde->start_addr);
kernel/unwind/sframe.c
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+ +if (fde_type ==SFRAME_FDE_TYPE_PCMASK ) + ip_off %= fde->rep_size;
Following is a patch with the suggested changes, that were required to make unwinding of user space using SFrame work on s390:
kernel/unwind/sframe.c +31 -11
@@ -19,7 +19,7 @@struct sframe_fre {unsigned int size; -s32 ip_off; +u32 ip_off;s32 cfa_off;s32 ra_off;s32 fp_off;
@@ -136,7 +136,26 @@ static __always_inline int __find_fde(struct sframe_section *sec, to = (typeof (to ))__to; \ }) -#define UNSAFE_GET_USER_INC (to, from, size, label) \ +#define UNSAFE_GET_USER_SIGNED_INC (to, from, size, label) \ +({ \ + switch (size) { \ + case 1: \ + __UNSAFE_GET_USER_INC(to, from, s8, label); \ + break; \ + case 2: \ + __UNSAFE_GET_USER_INC(to, from, s16, label); \ + break; \ + case 4: \ + __UNSAFE_GET_USER_INC(to, from, s32, label); \ + break; \ + default: \ + dbg_sec_uaccess("%d: bad UNSAFE_GET_USER_SIGNED__INC size %u\n",\ + __LINE__, size); \ + return -EFAULT; \ + } \ +}) + +#define UNSAFE_GET_USER_UNSIGNED_INC (to, from, size, label) \ ({ \ switch (size) { \ case 1: \
@@ -149,7 +168,7 @@ static __always_inline int __find_fde(struct sframe_section *sec,__UNSAFE_GET_USER_INC (to, from, u32, label); \break ; \default : \ -dbg_sec_uaccess ("%d: bad UNSAFE_GET_USER_INC size %u\n" ,\ +dbg_sec_uaccess ("%d: bad UNSAFE_GET_USER_UNSIGNED_INC size %u\n" ,\ __LINE__, size); \return -EFAULT ; \ } \
@@ -163,7 +182,8 @@ static __always_inline int __read_fre(struct sframe_section *sec,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 ip_off, cfa_off, ra_off, fp_off; +u32 ip_off; +s32 cfa_off, ra_off, fp_off;unsigned long cur = fre_addr;unsigned char addr_size;u8 info;
@@ -179,14 +199,14 @@ static __always_inline int __read_fre(struct sframe_section *sec,return -EFAULT ; } -UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault); +UNSAFE_GET_USER_UNSIGNED_INC (ip_off, cur, addr_size, Efault);if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size) {dbg_sec_uaccess ("fre starts past end of function: ip_off=0x%x, func_size=0x%x\n" , ip_off, fde->func_size);return -EFAULT ; } -UNSAFE_GET_USER_INC (info, cur,1 , Efault); +UNSAFE_GET_USER_UNSIGNED_INC (info, cur,1 , Efault); offset_count =SFRAME_FRE_OFFSET_COUNT (info); offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info));if (!offset_count || !offset_size) {
@@ -200,7 +220,7 @@ static __always_inline int __read_fre(struct sframe_section *sec, fre->size = addr_size +1 + (offset_count * offset_size); -UNSAFE_GET_USER_INC (cfa_off, cur, offset_size, Efault); +UNSAFE_GET_USER_SIGNED_INC (cfa_off, cur, offset_size, Efault); offset_count--; ra_off = sec->ra_off;
@@ -210,13 +230,13 @@ static __always_inline int __read_fre(struct sframe_section *sec,return -EFAULT ; } -UNSAFE_GET_USER_INC (ra_off, cur, offset_size, Efault); +UNSAFE_GET_USER_SIGNED_INC (ra_off, cur, offset_size, Efault); } fp_off = sec->fp_off;if (!fp_off && offset_count) { offset_count--; -UNSAFE_GET_USER_INC (fp_off, cur, offset_size, Efault); +UNSAFE_GET_USER_SIGNED_INC (fp_off, cur, offset_size, Efault); }if (offset_count) {
@@ -247,9 +267,9 @@ static __always_inline int __find_fre(struct sframe_section *sec,unsigned long fre_addr;bool which = false;unsigned int i; -s32 ip_off; +u32 ip_off; - ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr; + ip_off = ip - (sec->sframe_start + fde->start_addr);if (fde_type ==SFRAME_FDE_TYPE_PCMASK ) ip_off %= fde->rep_size;
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 8 lines · Jens Remus
Review
Quoted 3 lines · Josh Poimboeuf
(quoted diff) +3
-0+struct sframe_fre { + unsigned int size; + s32 ip_off;The IP offset (from function start) in the SFrame V2 FDE is unsigned:
u32
ip_off;
Indeed.
Quoted 21 lines
Quoted 21 lines
(quoted diff) +21
-0+#define __UNSAFE_GET_USER_INC(to, from, type, label) \ +({ \ + type __to; \ + unsafe_get_user(__to, (type __user *)from, label); \ + from += sizeof(__to); \ + to = (typeof(to))__to; \ +}) + +#define UNSAFE_GET_USER_INC(to, from, size, label) \ +({ \ + switch (size) { \ + case 1: \ + __UNSAFE_GET_USER_INC(to, from, u8, label); \ + break; \ + case 2: \ + __UNSAFE_GET_USER_INC(to, from, u16, label); \ + break; \ + case 4: \ + __UNSAFE_GET_USER_INC(to, from, u32, label); \ + break; \ + default: \
Quoted 8 lines
kernel/unwind/sframe.c static __always_inline int __find_fde(struct sframe_section *sec, · line 156
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+return -EFAULT ; \ + } \ +})This does not work for the signed SFrame fields, such as the FRE CFA, RA, and FP offsets, as it does not perform the required sign extension. One option would be to rename to
UNSAFE_GET_USER_UNSIGNED_INC()and re-introduceUNSAFE_GET_USER_SIGNED_INC()using s8, s16, and s32.
See the following line in __UNSAFE_GET_USER_INC():
to = (typeof(to))__to;
Does that not do the sign extension?
Signature · 1 line
Josh
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 33 lines · Josh Poimboeuf
Quoted 23 lines · Jens Remus
Quoted 21 lines · Josh Poimboeuf
(quoted diff) +21
-0+#define __UNSAFE_GET_USER_INC(to, from, type, label) \ +({ \ + type __to; \ + unsafe_get_user(__to, (type __user *)from, label); \ + from += sizeof(__to); \ + to = (typeof(to))__to; \ +}) + +#define UNSAFE_GET_USER_INC(to, from, size, label) \ +({ \ + switch (size) { \ + case 1: \ + __UNSAFE_GET_USER_INC(to, from, u8, label); \ + break; \ + case 2: \ + __UNSAFE_GET_USER_INC(to, from, u16, label); \ + break; \ + case 4: \ + __UNSAFE_GET_USER_INC(to, from, u32, label); \ + break; \ + default: \Quoted 8 lines
kernel/unwind/sframe.c static __always_inline int __find_fde(struct sframe_section *sec, · line 156
Quoted 3 lines
kernel/unwind/sframe.c +3
-0+return -EFAULT ; \ + } \ +})This does not work for the signed SFrame fields, such as the FRE CFA, RA, and FP offsets, as it does not perform the required sign extension. One option would be to rename to
UNSAFE_GET_USER_UNSIGNED_INC()and re-introduceUNSAFE_GET_USER_SIGNED_INC()using s8, s16, and s32.
Quoted 5 lines
See the following line in
__UNSAFE_GET_USER_INC():to = (typeof(to))__to;Does that not do the sign extension?
No. In practice with my proposed changes reverted and the following debugging code added:
@@ -293,6 +293,10 @@ static __always_inline int __find_fre(struct sframe_section *sec,
return -EINVAL;
fre = prev_fre;+ dbg_sec_uaccess("fre: ip_off=%u, cfa_off=%d, ra_off=%d, fp_off=%d, use_fp=%s, sp_val_off=%d\n",
+ fre->ip_off, fre->cfa_off, fre->ra_off, fre->fp_off, + SFRAME_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP ? "y" : "n", + sframe_sp_val_off());
Excerpt from dmesg:
sframe: /usr/lib/ld64.so.1: fre: ip_off=16, cfa_off=440, ra_off=208, fp_off=184, use_fp=n, sp_val_off=-160
sframe: /usr/lib/ld64.so.1: fre: ip_off=2600, cfa_off=672, ra_off=208, fp_off=184, use_fp=y, sp_val_off=-160
sframe: /usr/lib/ld64.so.1: fre: ip_off=10, cfa_off=368, ra_off=0, fp_off=0, use_fp=n, sp_val_off=-160
sframe: /usr/lib/ld64.so.1: fre: ip_off=722, cfa_off=672, ra_off=208, fp_off=184, use_fp=y, sp_val_off=-160
On s390 the register save slots have negative offsets from CFA (due to the CFA to be defined as SP at call site + 160). The RA, if saved, would be saved at CFA-48 on the stack. I.e. ra_off=-48 instead of ra_off=208 would have been correct.
208 = 0xd0 (unsigned) = -48 (signed)
Looking at the code:
UNSAFE_GET_USER_INC(ra_off, cur, offset_size, Efault);
With offset_size=1 expands into:
__UNSAFE_GET_USER_INC(/*to=*/ra_off, /*from=*cur, /*type=*/u8, /*label=*/Efault);
Expands into:
{
u8 __to;
unsafe_get_user(__to, (u8 __user *)cur, Efault);
cur += sizeof(__to);
ra_off = (typeof(ra_off))__to;
}The issue is that on the last line __to is u8 instead of s8 and thus u8 gets casted to s32, which is performed without sign extension. __to would need to be s8 or get casted to s8 for sign extension to take place.
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 15 lines · Jens Remus
UNSAFE_GET_USER_INC(ra_off, cur, offset_size, Efault);With offset_size=1 expands into:
__UNSAFE_GET_USER_INC(/*to=*/ra_off, /*from=*cur, /*type=*/u8, /*label=*/Efault);Expands into:
{ u8 __to; unsafe_get_user(__to, (u8 __user *)cur, Efault); cur += sizeof(__to); ra_off = (typeof(ra_off))__to; }
Quoted 4 lines
The issue is that on the last line
__tois u8 instead of s8 and thus u8 gets casted to s32, which is performed without sign extension.__towould need to be s8 or get casted to s8 for sign extension to take place.
Ah, I get it now, thanks for spelling that out for me.
Here's what I have at the moment:
#define __UNSAFE_GET_USER_INC(to, from, type, label) \
({ \
type __to; \
unsafe_get_user(__to, (type __user *)from, label); \
from += sizeof(__to); \
to = __to; \
})#define __UNSAFE_GET_USER_INC(to, from, size, label, u_or_s) \
({ \
switch (size) { \
case 1: \
__UNSAFE_GET_USER_INC(to, from, u_or_s##8, label); \
break; \
case 2: \
__UNSAFE_GET_USER_INC(to, from, u_or_s##16, label); \
break; \
case 4: \
__UNSAFE_GET_USER_INC(to, from, u_or_s##32, label); \
break; \
default: \
dbg_sec_uaccess("%d: bad unsafe_get_user() size %u\n", \
__LINE__, size); \
return -EFAULT; \
} \
})#define UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label) \
__UNSAFE_GET_USER_INC(to, from, size, label, u)
#define UNSAFE_GET_USER_SIGNED_INC(to, from, size, label) \
__UNSAFE_GET_USER_INC(to, from, size, label, s)
#define UNSAFE_GET_USER_INC(to, from, size, label) \ _Generic(to, \ u8: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ u16: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ u32: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ s8: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \ s16: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \ s32: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
Re: [PATCH v4 19/39] unwind_user/sframe: Add support for reading .sframe contents
Quoted 16 lines · Josh Poimboeuf
Quoted 15 lines · Jens Remus
UNSAFE_GET_USER_INC(ra_off, cur, offset_size, Efault);With offset_size=1 expands into:
__UNSAFE_GET_USER_INC(/*to=*/ra_off, /*from=*cur, /*type=*/u8, /*label=*/Efault);Expands into:
{ u8 __to; unsafe_get_user(__to, (u8 __user *)cur, Efault); cur += sizeof(__to); ra_off = (typeof(ra_off))__to; }
Quoted 8 lines
Quoted 4 lines
The issue is that on the last line
__tois u8 instead of s8 and thus u8 gets casted to s32, which is performed without sign extension.__towould need to be s8 or get casted to s8 for sign extension to take place.Ah, I get it now, thanks for spelling that out for me.
Here's what I have at the moment:
Thanks! Using your new UNSAFE_GET_USER_INC() in all places works great on s390 when resolving the duplicate macro names (see below).
Quoted 10 lines
#define
__UNSAFE_GET_USER_INC(to, from, type, label)\({ \ type __to; \ unsafe_get_user(__to, (type __user *)from, label); \ from += sizeof(__to); \ to = __to; \ })#define
__UNSAFE_GET_USER_INC(to, from, size, label, u_or_s)\
That does not compile. One of the macros needs to be renamed.
CC kernel/unwind/sframe.okernel/unwind/sframe.c:141:9: warning: "__UNSAFE_GET_USER_INC" redefined
141 | #define __UNSAFE_GET_USER_INC(to, from, size, label, u_or_s) \
| ^~~~~~~~~~~~~~~~~~~~~
kernel/unwind/sframe.c:133:9: note: this is the location of the previous definition
133 | #define __UNSAFE_GET_USER_INC(to, from, type, label) \
| ^~~~~~~~~~~~~~~~~~~~~Quoted 24 lines
({ \ switch (size) { \ case 1: \ __UNSAFE_GET_USER_INC(to, from, u_or_s##8, label); \ break; \ case 2: \ __UNSAFE_GET_USER_INC(to, from, u_or_s##16, label); \ break; \ case 4: \ __UNSAFE_GET_USER_INC(to, from, u_or_s##32, label); \ break; \ default: \ dbg_sec_uaccess("%d: bad unsafe_get_user() size %u\n", \ __LINE__, size); \ return -EFAULT; \ } \ })#define
UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label)\
__UNSAFE_GET_USER_INC(to, from, size, label, u)#define
UNSAFE_GET_USER_SIGNED_INC(to, from, size, label)\
__UNSAFE_GET_USER_INC(to, from, size, label, s)
Quoted 8 lines
#define UNSAFE_GET_USER_INC(to, from, size, label) \ _Generic(to, \ u8: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ u16: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ u32: UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \ s8: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \ s16: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \ s32: UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerPATCH v4 20/39 unwind_user/sframe: Detect .sframe sections in executables
In reply to Josh PoimboeufWhen loading an ELF executable, automatically detect an .sframe section and associate it with the mm_struct.
--- fs/binfmt_elf.c | 49 +++++++++++++++++++++++++++++++++++++--- include/uapi/linux/elf.h | 1 + 2 files changed, 47 insertions(+), 3 deletions(-)
fs/binfmt_elf.c +46 -3
@@ -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>
@@ -629,6 +630,21 @@ static inline int make_prot(u32 p_flags, struct arch_elf_state *arch_state,return arch_elf_adjust_prot (prot, arch_state, has_interp, is_interp); } +static void elf_add_sframe (struct elf_phdr *text,struct elf_phdr *sframe, +unsigned long base_addr) +{ +unsigned long sframe_start, sframe_end, text_start, text_end; + + sframe_start = base_addr + sframe->p_vaddr; + sframe_end = sframe_start + sframe->p_memsz; + + text_start = base_addr + text->p_vaddr; + text_end = text_start + text->p_memsz; + +/* Ignore return value, sframe section isn't critical */ +sframe_add_section (sframe_start, sframe_end, text_start, text_end); +} +/* This is much more generalized than the library routine read function, so we keep this separate. Technically the library read function is only provided so that we can read a.out libraries that have
@@ -639,7 +655,7 @@ 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 ;
@@ -665,7 +681,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);
@@ -704,6 +721,20 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, error = -ENOMEM ; goto out; } +break ; + } +case PT_GNU_SFRAME : + sframe_phdr = eppnt; +break ; + } + } + +if (sframe_phdr) { + eppnt = interp_elf_phdata; +for (i =0 ; i < interp_elf_ex->e_phnum; i++, eppnt++) { +if (eppnt->p_flags &PF_X ) { +elf_add_sframe (eppnt, sframe_phdr, load_addr); + } } }
@@ -829,7 +860,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;
@@ -937,6 +968,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,
@@ -1227,6 +1262,14 @@ static int load_elf_binary(struct linux_binprm *bprm) elf_brk = k; } +if (sframe_phdr) { +for (i =0 , elf_ppnt = elf_phdata; + i < elf_ex->e_phnum; i++, elf_ppnt++) { +if ((elf_ppnt->p_flags &PF_X )) +elf_add_sframe (elf_ppnt, sframe_phdr, load_bias); + } + } + e_entry = elf_ex->e_entry + load_bias; phdr_addr += load_bias; elf_brk += load_bias;
include/uapi/linux/elf.h +1 -0
@@ -39,6 +39,7 @@ typedef __s64 Elf64_Sxword;#define PT_GNU_STACK (PT_LOOS + 0x474e551)#define PT_GNU_RELRO (PT_LOOS + 0x474e552)#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553) +#define PT_GNU_SFRAME (PT_LOOS + 0x474e554)/* ARM MTE memory tag segment type */
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 21/39 unwind_user/sframe: Add prctl() interface for registering .sframe sections
In reply to Josh PoimboeufThe kernel doesn't have direct visibility to the ELF contents of shared libraries. Add some prctl() interfaces which allow glibc to tell the kernel where to find .sframe sections.
--- include/uapi/linux/prctl.h | 5 ++++- kernel/sys.c | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-)
include/uapi/linux/prctl.h +4 -1
@@ -351,6 +351,9 @@ struct prctl_mm_map {* configuration. All bits may be locked via this call, including * undefined bits. */ -#define PR_LOCK_SHADOW_STACK_STATUS 76 +#define PR_LOCK_SHADOW_STACK_STATUS 76 + +#define PR_ADD_SFRAME 77 +#define PR_REMOVE_SFRAME 78#endif /* _LINUX_PRCTL_H */
kernel/sys.c +9 -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>
@@ -2809,6 +2810,14 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,return -EINVAL ; error =arch_lock_shadow_stack_status (me, arg2);break ; +case PR_ADD_SFRAME : + error =sframe_add_section (arg2, arg3, arg4, arg5); +break ; +case PR_REMOVE_SFRAME : +if (arg3 || arg4 || arg5) +return -EINVAL ; + error =sframe_remove_section (arg2); +break ;default : error = -EINVAL ;break ;
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 22/39 unwind_user/sframe: Wire up unwind_user to sframe
In reply to Josh PoimboeufNow that the sframe infrastructure is fully in place, make it work by hooking it up to the unwind_user interface.
--- arch/Kconfig | 1 + include/linux/unwind_user_types.h | 1 + kernel/unwind/user.c | 22 +++++++++++++++++++--- 3 files changed, 21 insertions(+), 3 deletions(-)
arch/Kconfig +1 -0
@@ -448,6 +448,7 @@ config HAVE_UNWIND_USER_COMPAT_FP 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)
include/linux/unwind_user_types.h +1 -0
@@ -9,6 +9,7 @@ enum unwind_user_type {UNWIND_USER_TYPE_NONE ,UNWIND_USER_TYPE_FP ,UNWIND_USER_TYPE_COMPAT_FP , + UNWIND_USER_TYPE_SFRAME, }; struct unwind_stacktrace {
kernel/unwind/user.c +19 -3
@@ -6,6 +6,7 @@#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>
@@ -29,6 +30,12 @@ static inline bool compat_state(struct unwind_user_state *state) state->type ==UNWIND_USER_TYPE_COMPAT_FP ; } +static inline bool sframe_state (struct unwind_user_state *state) +{ +return IS_ENABLED (CONFIG_HAVE_UNWIND_USER_SFRAME ) && + state->type ==UNWIND_USER_TYPE_SFRAME ; +} +#define UNWIND_GET_USER_LONG (to, from, state) \ ({ \ int __ret; \
@@ -48,12 +55,19 @@ int unwind_user_next(struct unwind_user_state *state)if (state->done)return -EINVAL ; -if (compat_state (state)) +if (compat_state (state)) { frame = &compat_fp_frame; -else if (fp_state (state)) + }else if (sframe_state (state)) { +if (sframe_find (state->ip, frame)) { +if (!IS_ENABLED (CONFIG_HAVE_UNWIND_USER_FP )) + goto the_end; + frame = &fp_frame; + } + }else if (fp_state (state)) { frame = &fp_frame; -else + }else { goto the_end; + } cfa = (frame->use_fp ? state->fp : state->sp) + frame->cfa_off;
@@ -94,6 +108,8 @@ int unwind_user_start(struct unwind_user_state *state)if (IS_ENABLED (CONFIG_HAVE_UNWIND_USER_COMPAT_FP ) &&in_compat_mode (regs)) state->type =UNWIND_USER_TYPE_COMPAT_FP ; +else if (current_has_sframe ()) + state->type =UNWIND_USER_TYPE_SFRAME ;else if (IS_ENABLED (CONFIG_HAVE_UNWIND_USER_FP )) state->type =UNWIND_USER_TYPE_FP ;else
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 23/39 unwind_user/sframe/x86: Enable sframe unwinding on x86
In reply to Josh PoimboeufThe x86 sframe 2.0 implementation works fairly well, starting with binutils 2.41 (though some bugs are getting fixed in later versions). Enable it.
--- arch/x86/Kconfig | 1 + 1 file changed, 1 insertion(+)
arch/x86/Kconfig +1 -0
@@ -293,6 +293,7 @@ config X86 select HAVE_UNSTABLE_SCHED_CLOCK select HAVE_UNWIND_USER_COMPAT_FP if IA32_EMULATION 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.48.1
Josh Poimboeuf
maintainerPATCH v4 24/39 unwind_user/sframe: Remove .sframe section on detected corruption
In reply to Josh PoimboeufTo avoid continued attempted use of a bad .sframe section, remove it on demand when the first sign of corruption is detected.
--- kernel/unwind/sframe.c | 4 ++++ 1 file changed, 4 insertions(+)
kernel/unwind/sframe.c +4 -0
@@ -294,6 +294,10 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame) ret =__find_fre (sec, &fde, ip, frame); end:user_read_access_end (); + +if (ret == -EFAULT ) +WARN_ON_ONCE (sframe_remove_section (sec->sframe_start)); +return ret; }
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 25/39 unwind_user/sframe: Show file name in debug output
In reply to Josh PoimboeufWhen debugging sframe issues, the error messages aren't all that helpful without knowing what file a corresponding .sframe section belongs to. Prefix debug output strings with the file name.
--- include/linux/sframe.h | 4 +++- kernel/unwind/sframe.c | 23 ++++++++++++-------- kernel/unwind/sframe_debug.h | 41 ++++++++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 16 deletions(-)
include/linux/sframe.h +3 -1
@@ -10,7 +10,9 @@struct sframe_section {struct rcu_head rcu; - +#ifdef CONFIG_DYNAMIC_DEBUG +const char *filename; +#endif unsigned long sframe_start;unsigned long sframe_end;unsigned long text_start;
kernel/unwind/sframe.c +14 -9
@@ -295,14 +295,17 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame) end:user_read_access_end (); -if (ret == -EFAULT ) +if (ret == -EFAULT ) { +dbg_sec ("removing bad .sframe section\n" );WARN_ON_ONCE (sframe_remove_section (sec->sframe_start)); + }return ret; }static void free_section (struct sframe_section *sec) { +dbg_free (sec);kfree (sec); }
@@ -313,7 +316,7 @@ static int sframe_read_header(struct sframe_section *sec)unsigned int num_fdes;if (copy_from_user (&shdr, (void __user *)sec->sframe_start,sizeof (shdr))) { -dbg ("header usercopy failed\n" ); +dbg_sec ("header usercopy failed\n" );return -EFAULT ; }
@@ -321,18 +324,18 @@ static int sframe_read_header(struct sframe_section *sec) shdr.preamble.version != SFRAME_VERSION_2 || !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) || shdr.auxhdr_len) { -dbg ("bad/unsupported sframe header\n" ); +dbg_sec ("bad/unsupported sframe header\n" );return -EINVAL ; }if (!shdr.num_fdes || !shdr.num_fres) { -dbg ("no fde/fre entries\n" ); +dbg_sec ("no fde/fre entries\n" );return -EINVAL ; } header_end = sec->sframe_start +SFRAME_HEADER_SIZE (shdr);if (header_end >= sec->sframe_end) { -dbg ("header doesn't fit in section\n" ); +dbg_sec ("header doesn't fit in section\n" );return -EINVAL ; }
@@ -344,7 +347,7 @@ static int sframe_read_header(struct sframe_section *sec) fres_end = fres_start + shdr.fre_len;if (fres_start < fdes_end || fres_end > sec->sframe_end) { -dbg ("inconsistent fde/fre offsets\n" ); +dbg_sec ("inconsistent fde/fre offsets\n" );return -EINVAL ; }
@@ -400,6 +403,8 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end, sec->text_start = text_start; sec->text_end = text_end; +dbg_init (sec); + ret =sframe_read_header (sec);if (ret) {dbg_print_header (sec);
@@ -408,8 +413,8 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end, ret =mtree_insert_range (sframe_mt, sec->text_start, sec->text_end, sec,GFP_KERNEL );if (ret) { -dbg ("mtree_insert_range failed: text=%lx-%lx\n" , - sec->text_start, sec->text_end); +dbg_sec ("mtree_insert_range failed: text=%lx-%lx\n" , + sec->text_start, sec->text_end); goto err_free; }
@@ -431,7 +436,7 @@ static int __sframe_remove_section(struct mm_struct *mm, struct sframe_section *sec) {if (!mtree_erase (&mm->sframe_mt, sec->text_start)) { -dbg ("mtree_erase failed: text=%lx\n" , sec->text_start); +dbg_sec ("mtree_erase failed: text=%lx\n" , sec->text_start);return -EINVAL ; }
kernel/unwind/sframe_debug.h +35 -6
61 lines not on this page · raw message
Signature · 1 line
2.48.1
Re: [PATCH v4 25/39] unwind_user/sframe: Show file name in debug output
Quoted 10 lines · Josh Poimboeuf
When debugging sframe issues, the error messages aren't all that helpful without knowing what file a corresponding .sframe section belongs to. Prefix debug output strings with the file name.
Signed-off-by Josh Poimboeuf <[email protected]>kernel/unwind/sframe_debug.h +2
-0+static inline void dbg_init (struct sframe_section *sec) +{
kernel/unwind/sframe_debug.h line 41
Quoted 10 lines
kernel/unwind/sframe_debug.h +10
-0+struct mm_struct *mm = current->mm; +struct vm_area_struct *vma; + +guard (mmap_read_lock)(mm); + vma =vma_lookup (mm, sec->sframe_start); +if (!vma) + sec->filename =kstrdup ("(vma gone???)" ,GFP_KERNEL ); +else if (vma->vm_file) + sec->filename =kstrdup_quotable_file (vma->vm_file,GFP_KERNEL ); +else if (!vma->vm_mm)
This condition does not appear to work for vdso on s390. The following does:
else if (in_range(sec->sframe_start, current->mm->context.vdso_base, vdso_text_size()))
kernel/unwind/sframe_debug.h line 45
Quoted 4 lines
kernel/unwind/sframe_debug.h +4
-0+ sec->filename =kstrdup ("(vdso)" ,GFP_KERNEL ); + else + sec->filename = kstrdup("(anonymous)", GFP_KERNEL); +}
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 25/39] unwind_user/sframe: Show file name in debug output
Quoted 9 lines · Jens Remus
Quoted 7 lines · Josh Poimboeuf
When debugging sframe issues, the error messages aren't all that helpful without knowing what file a corresponding .sframe section belongs to. Prefix debug output strings with the file name.
Signed-off-by Josh Poimboeuf <[email protected]>kernel/unwind/sframe_debug.h +0
-0
Quoted 14 lines
kernel/unwind/sframe_debug.h
Quoted 12 lines
kernel/unwind/sframe_debug.h +12
-0+static inline void dbg_init (struct sframe_section *sec) +{ +struct mm_struct *mm = current->mm; +struct vm_area_struct *vma; + +guard (mmap_read_lock)(mm); + vma =vma_lookup (mm, sec->sframe_start); +if (!vma) + sec->filename =kstrdup ("(vma gone???)" ,GFP_KERNEL ); +else if (vma->vm_file) + sec->filename =kstrdup_quotable_file (vma->vm_file,GFP_KERNEL ); +else if (!vma->vm_mm)This condition does not appear to work for vdso on s390.
I had a fix for this which somehow got dropped:
kernel/unwind/sframe_debug.h +4 -0
@@ -67,6 +67,10 @@ static inline void dbg_init(struct sframe_section *sec) sec->filename =kstrdup ("(vma gone???)" ,GFP_KERNEL ); else if (vma->vm_file) sec->filename = kstrdup_quotable_file(vma->vm_file, GFP_KERNEL); + else if (vma->vm_ops && vma->vm_ops->name) + sec->filename = kstrdup(vma->vm_ops->name(vma), GFP_KERNEL); + else if (arch_vma_name(vma)) + sec->filename = kstrdup(arch_vma_name(vma), GFP_KERNEL); else if (!vma->vm_mm) sec->filename =kstrdup ("(vdso)" ,GFP_KERNEL ); else
Re: [PATCH v4 25/39] unwind_user/sframe: Show file name in debug output
Quoted 27 lines · Josh Poimboeuf
Quoted 4 lines · Jens Remus
Quoted 1 line · Josh Poimboeuf
kernel/unwind/sframe_debug.h +0
-0Quoted 14 lines
kernel/unwind/sframe_debug.h
Quoted 12 lines
kernel/unwind/sframe_debug.h +12
-0+static inline void dbg_init (struct sframe_section *sec) +{ +struct mm_struct *mm = current->mm; +struct vm_area_struct *vma; + +guard (mmap_read_lock)(mm); + vma =vma_lookup (mm, sec->sframe_start); +if (!vma) + sec->filename =kstrdup ("(vma gone???)" ,GFP_KERNEL ); +else if (vma->vm_file) + sec->filename =kstrdup_quotable_file (vma->vm_file,GFP_KERNEL ); +else if (!vma->vm_mm)This condition does not appear to work for vdso on s390.
I had a fix for this which somehow got dropped:
kernel/unwind/sframe_debug.h +0
-0
kernel/unwind/sframe_debug.h static inline void dbg_init(struct sframe_section *sec) · line 76
Quoted 10 lines
kernel/unwind/sframe_debug.h +4
-0@@ -67,6 +67,10 @@ static inline void dbg_init(struct sframe_section *sec) sec->filename =kstrdup ("(vma gone???)" ,GFP_KERNEL ); else if (vma->vm_file) sec->filename = kstrdup_quotable_file(vma->vm_file, GFP_KERNEL); + else if (vma->vm_ops && vma->vm_ops->name) + sec->filename = kstrdup(vma->vm_ops->name(vma), GFP_KERNEL); + else if (arch_vma_name(vma)) + sec->filename = kstrdup(arch_vma_name(vma), GFP_KERNEL); else if (!vma->vm_mm) sec->filename =kstrdup ("(vdso)" ,GFP_KERNEL ); else
Thanks! Your patch works fine on s390:
test_vdso 1087 104.721004: 10000 task-clock:ppp:
3ffd11f1702 __cvdso_clock_gettime_data.constprop.0+0x52 ([vdso])
3ffd11f14c6 __kernel_clock_gettime+0x16 ([vdso])
3ff8f6f113e clock_gettime@@GLIBC_2.17+0x1e (/usr/lib64/libc.so.6)
3ff8f6e20e8 __gettimeofday+0x38 (/usr/lib64/libc.so.6)
100066e main+0x1e (/root/test/vdso/test_vdso)
3ff8f63459c __libc_start_call_main+0x8c (/usr/lib64/libc.so.6)
3ff8f63469e __libc_start_main@@GLIBC_2.34+0xae (/usr/lib64/libc.so.6)
100073a _start+0x3a (/root/test/vdso/test_vdso)Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerPATCH v4 26/39 unwind_user/sframe: Enable debugging in uaccess regions
In reply to Josh PoimboeufObjtool warns about calling pr_debug() from uaccess-enabled regions, and rightfully so. Add a dbg_sec_uaccess() macro which temporarily disables uaccess before doing the dynamic printk, and use that to add debug messages throughout the uaccess-enabled regions.
--- kernel/unwind/sframe.c | 59 ++++++++++++++++++++++++++++-------- kernel/unwind/sframe_debug.h | 31 +++++++++++++++++++ 2 files changed, 77 insertions(+), 13 deletions(-)
kernel/unwind/sframe.c +46 -13
@@ -53,12 +53,15 @@ static __always_inline int __read_fde(struct sframe_section *sec, sizeof(struct sframe_fde), Efault); ip = sec->sframe_start + fde->start_addr; -if (ip < sec->text_start || ip > sec->text_end) +if (ip < sec->text_start || ip > sec->text_end) { +dbg_sec_uaccess ("bad fde num %d\n" , fde_num);return -EINVAL ; + }return 0 ; Efault: +dbg_sec_uaccess ("fde %d usercopy failed\n" , fde_num);return -EFAULT ; }
@@ -85,16 +88,22 @@ static __always_inline int __find_fde(struct sframe_section *sec,unsafe_get_user (func_off, (s32 __user *)mid, Efault);if (ip_off >= func_off) { -if (func_off < func_off_low) +if (func_off < func_off_low) { +dbg_sec_uaccess ("fde %u not sorted\n" , + (unsigned int )(mid - first));return -EFAULT ; + } func_off_low = func_off; found = mid; low = mid +1 ; }else { -if (func_off > func_off_high) +if (func_off > func_off_high) { +dbg_sec_uaccess ("fde %u not sorted\n" , + (unsigned int )(mid - first));return -EFAULT ; + } func_off_high = func_off;
@@ -140,6 +149,8 @@ static __always_inline int __find_fde(struct sframe_section *sec,__UNSAFE_GET_USER_INC (to, from, u32, label); \break ; \default : \ +dbg_sec_uaccess ("%d: bad UNSAFE_GET_USER_INC size %u\n" ,\ + __LINE__, size); \return -EFAULT ; \ } \ })
@@ -158,24 +169,34 @@ static __always_inline int __read_fre(struct sframe_section *sec,u8 info; addr_size =fre_type_to_size (fre_type); -if (!addr_size) +if (!addr_size) { +dbg_sec_uaccess ("bad addr_size in fde info %u\n" , fde->info);return -EFAULT ; + } -if (fre_addr + addr_size +1 > sec->fres_end) +if (fre_addr + addr_size +1 > sec->fres_end) { +dbg_sec_uaccess ("fre addr+info goes past end of subsection\n" );return -EFAULT ; + }UNSAFE_GET_USER_INC (ip_off, cur, addr_size, Efault); -if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size) +if (fde_type ==SFRAME_FDE_TYPE_PCINC && ip_off > fde->func_size) { +dbg_sec_uaccess ("fre starts past end of function: ip_off=0x%x, func_size=0x%x\n" , + ip_off, fde->func_size);return -EFAULT ; + }UNSAFE_GET_USER_INC (info, cur,1 , Efault); offset_count =SFRAME_FRE_OFFSET_COUNT (info); offset_size =offset_size_enum_to_size (SFRAME_FRE_OFFSET_SIZE (info)); -if (!offset_count || !offset_size) +if (!offset_count || !offset_size) { +dbg_sec_uaccess ("zero offset_count or size in fre info %u\n" ,info);return -EFAULT ; - -if (cur + (offset_count * offset_size) > sec->fres_end) + } +if (cur + (offset_count * offset_size) > sec->fres_end) { +dbg_sec_uaccess ("fre goes past end of subsection\n" );return -EFAULT ; + } fre->size = addr_size +1 + (offset_count * offset_size);
@@ -184,8 +205,10 @@ static __always_inline int __read_fre(struct sframe_section *sec, ra_off = sec->ra_off;if (!ra_off) { -if (!offset_count--) +if (!offset_count--) { +dbg_sec_uaccess ("zero offset_count, can't find ra_off\n" );return -EFAULT ; + }UNSAFE_GET_USER_INC (ra_off, cur, offset_size, Efault); }
@@ -196,8 +219,10 @@ static __always_inline int __read_fre(struct sframe_section *sec,UNSAFE_GET_USER_INC (fp_off, cur, offset_size, Efault); } -if (offset_count) +if (offset_count) { +dbg_sec_uaccess ("non-zero offset_count after reading fre\n" );return -EFAULT ; + } fre->ip_off = ip_off; fre->cfa_off = cfa_off;
@@ -208,6 +233,7 @@ static __always_inline int __read_fre(struct sframe_section *sec,return 0 ; Efault: +dbg_sec_uaccess ("fre usercopy failed\n" );return -EFAULT ; }
@@ -241,13 +267,20 @@ static __always_inline int __find_fre(struct sframe_section *sec, which = !which; ret =__read_fre (sec, fde, fre_addr, fre); -if (ret) +if (ret) { +dbg_sec_uaccess ("fde addr 0x%x: __read_fre(%u) failed\n" , + fde->start_addr, i); +dbg_print_fde_uaccess (sec, fde);return ret; + } fre_addr += fre->size; -if (prev_fre && fre->ip_off <= prev_fre->ip_off) +if (prev_fre && fre->ip_off <= prev_fre->ip_off) { +dbg_sec_uaccess ("fde addr 0x%x: fre %u not sorted\n" , + fde->start_addr, i);return -EFAULT ; + }if (fre->ip_off > ip_off)break ;
kernel/unwind/sframe_debug.h +31 -0
@@ -13,6 +13,26 @@#define dbg_sec (fmt, ...) \ dbg("%s: " fmt, sec->filename, ##__VA_ARGS__) +#define __dbg_sec_descriptor (fmt, ...) \ + __dynamic_pr_debug(&descriptor, "sframe: %s: " fmt, \ + sec->filename, ##__VA_ARGS__) + +/* +* To avoid breaking uaccess rules, temporarily disable uaccess +* before calling printk. +*/ +#define dbg_sec_uaccess (fmt, ...) \ +({ \ + DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt); \ + if (DYNAMIC_DEBUG_BRANCH(descriptor)) { \ + user_read_access_end(); \ + __dbg_sec_descriptor(fmt, ##__VA_ARGS__); \ + BUG_ON(!user_read_access_begin( \ + (void __user *)sec->sframe_start, \ + sec->sframe_end - sec->sframe_start)); \ + } \ +}) + static __always_inline void dbg_print_header(struct sframe_section *sec) {unsigned long fdes_end;
@@ -27,6 +47,15 @@ static __always_inline void dbg_print_header(struct sframe_section *sec) sec->ra_off, sec->fp_off); } +static __always_inline void dbg_print_fde_uaccess(struct sframe_section *sec, +struct sframe_fde *fde) +{ +dbg_sec_uaccess ("FDE: start_addr:0x%x func_size:0x%x " +"fres_off:0x%x fres_num:%d info:%u rep_size:%u\n" , + fde->start_addr, fde->func_size, + fde->fres_off, fde->fres_num, fde->info, fde->rep_size); +} +static inline void dbg_init (struct sframe_section *sec) {struct mm_struct *mm = current->mm;
@@ -53,8 +82,10 @@ static inline void dbg_free(struct sframe_section *sec) #define dbg(args...) no_printk(args) #define dbg_sec(args... ) no_printk(args) +#define dbg_sec_uaccess(args...) no_printk(args)static inline void dbg_print_header (struct sframe_section *sec) {} +static inline void dbg_print_fde_uaccess (struct sframe_section *sec,struct sframe_fde *fde) {}static inline void dbg_init (struct sframe_section *sec) {}static inline void dbg_free (struct sframe_section *sec) {}
Signature · 1 line
2.48.1
Re: [PATCH v4 26/39] unwind_user/sframe: Enable debugging in uaccess regions
Quoted 15 lines · Josh Poimboeuf
Objtool warns about calling
pr_debug()from uaccess-enabled regions, and rightfully so. Add adbg_sec_uaccess()macro which temporarily disables uaccess before doing the dynamic printk, and use that to add debug messages throughout the uaccess-enabled regions.Signed-off-by Josh Poimboeuf <[email protected]>kernel/unwind/sframe.c +1
-1@@ -53,12 +53,15 @@ static __always_inline int __read_fde(struct sframe_section *sec, sizeof(struct sframe_fde), Efault); ip = sec->sframe_start + fde->start_addr; -if (ip < sec->text_start || ip > sec->text_end) +if (ip < sec->text_start || ip > sec->text_end) {
kernel/unwind/sframe.c static __always_inline int __read_fde(struct sframe_section *sec, · line 66
Quoted 10 lines
kernel/unwind/sframe.c +3
-0+dbg_sec_uaccess ("bad fde num %d\n" , fde_num);return -EINVAL ; + }return 0 ; Efault: +dbg_sec_uaccess ("fde %d usercopy failed\n" , fde_num);return -EFAULT ; }
Add a similar debug message for SFRame FDE user copy failures?
kernel/unwind/sframe.c +0 -0
@@ -125,6 +125,7 @@ static __always_inline int __find_fde(struct sframe_section *sec,
return 0;
Efault:
+ dbg_sec_uaccess("fde usercopy failed\n");
return -EFAULT;}
Printing the IP is probably not an option due to security concerns? Printing the the CFA, FP, and RA offsets is too much traffic? To debug issues on s390 I had to add tons of additional debug messages to make sense of what was actually going on.
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 26/39] unwind_user/sframe: Enable debugging in uaccess regions
kernel/unwind/sframe.c static __always_inline int __find_fde(struct sframe_section *sec, · line 130
Quoted 11 lines · Jens Remus
Add a similar debug message for SFRame FDE user copy failures?
kernel/unwind/sframe.c +1
-0@@ -125,6 +125,7 @@ static __always_inline int __find_fde(struct sframe_section *sec,return 0 ; Efault: +dbg_sec_uaccess ("fde usercopy failed\n" );return -EFAULT ; }
Indeed.
Quoted 4 lines
Printing the IP is probably not an option due to security concerns? Printing the the CFA, FP, and RA offsets is too much traffic? To debug issues on s390 I had to add tons of additional debug messages to make sense of what was actually going on.
I guess it depends on what you're trying to debug. These messages are intended to help diagnose problems with the section format. It gets unloaded if any of these errors are detected, so it helps to try to communicate why that happened.
So yeah, they're intended to be very low traffic, only used for errors where an .sframe section is getting unloaded (i.e., all the -EFAULTs).
Corrupt CFA/FP/RA offset values are harder to detect, that could also be related to some other issue like stack corruption.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 27/39 unwind_user/sframe: Add .sframe validation option
In reply to Josh PoimboeufAdd a debug feature to validate all .sframe sections when first loading the file rather than on demand.
--- arch/Kconfig | 19 ++++++++++ kernel/unwind/sframe.c | 81 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+)
arch/Kconfig +19 -0
@@ -453,6 +453,25 @@ config HAVE_UNWIND_USER_SFRAME config AS_SFRAME def_bool $(as-instr,.cfi_sections .sframe\n.cfi_startproc\n.cfi_endproc) +config SFRAME_VALIDATION + bool "Enable .sframe section debugging" + depends on HAVE_UNWIND_USER_SFRAME + depends on DYNAMIC_DEBUG + help + When adding an .sframe section for a task, validate the entire + section immediately rather than on demand. + + This is a debug feature which is helpful for rooting out .sframe + section issues. If the .sframe section is corrupt, it will fail to + load immediately, with more information provided in dynamic printks. + + This has a significant page cache footprint due to its reading of the + entire .sframe section for every loaded executable and shared + library. Also, it's done for all processes, even those which don't + get stack traced by the kernel. Not recommended for general use. + + If unsure, say N. + config HAVE_PERF_REGS bool help
kernel/unwind/sframe.c +81 -0
93 lines not on this page · raw message
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 28/39 unwind_user/deferred: Add deferred unwinding interface
In reply to Josh PoimboeufAdd an interface for scheduling 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 be running in NMI or interrupt context.
- Avoid duplicate unwinds, whether called multiple times by the same caller or by different callers.
- Create a "context cookie" which allows trace post-processing to correlate kernel unwinds/traces with the user unwind.
--- include/linux/entry-common.h | 2 + include/linux/sched.h | 5 + include/linux/unwind_deferred.h | 46 +++++++ include/linux/unwind_deferred_types.h | 10 ++ kernel/fork.c | 4 + kernel/unwind/Makefile | 2 +- kernel/unwind/deferred.c | 178 ++++++++++++++++++++++++++ 7 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 include/linux/unwind_deferred.h create mode 100644 include/linux/unwind_deferred_types.h create mode 100644 kernel/unwind/deferred.c
include/linux/entry-common.h +2 -0
@@ -12,6 +12,7 @@#include <linux/resume_user_mode.h> #include <linux/tick.h> #include <linux/kmsan.h> +#include <linux/unwind_deferred.h> #include <asm/entry-common.h>
@@ -111,6 +112,7 @@ 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);
include/linux/sched.h +5 -0
@@ -46,6 +46,7 @@#include <linux/rv.h> #include <linux/livepatch_sched.h> #include <linux/uidgid_types.h> +#include <linux/unwind_deferred_types.h> #include <asm/kmap_size.h> /* task_struct member predeclarations (sorted alphabetically): */
@@ -1603,6 +1604,10 @@ struct task_struct {struct user_event_mm *user_event_mm;#endif +#ifdef CONFIG_UNWIND_USER +struct unwind_task_info unwind_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_deferred.h new file +46 -0
46 lines not on this page · raw message
include/linux/unwind_deferred_types.h new file +10 -0
@@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_UNWIND_USER_DEFERRED_TYPES_H +#define _LINUX_UNWIND_USER_DEFERRED_TYPES_H + +struct unwind_task_info { +unsigned long *entries; +u64 cookie; +}; + +#endif /* _LINUX_UNWIND_USER_DEFERRED_TYPES_H */
kernel/fork.c +4 -0
@@ -106,6 +106,7 @@#include <linux/pidfs.h> #include <linux/tick.h> #include <linux/sframe.h> +#include <linux/unwind_deferred.h> #include <asm/pgalloc.h> #include <linux/uaccess.h>
@@ -973,6 +974,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);
@@ -2370,6 +2372,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/Makefile +1 -1
@@ -1,2 +1,2 @@ - obj-$(CONFIG_UNWIND_USER) += user.o + obj-$(CONFIG_UNWIND_USER) += user.o deferred.o obj-$(CONFIG_HAVE_UNWIND_USER_SFRAME) += sframe.o
kernel/unwind/deferred.c new file +178 -0
178 lines not on this page · raw message
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 3 lines · Josh Poimboeuf
+/*
+ * The context cookie is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds. The high 12 bits are the CPU
s/12/16/ ?
Quoted 7 lines
+ * id; the lower 48 bits are a per-CPU entry counter.
+ */kernel/unwind/deferred.c new file +5
-0+static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 ); +return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 ); +}
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 7 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
+/*
+ * The context cookie is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds. The high 12 bits are the CPUs/12/16/ ?
Quoted 7 lines
kernel/unwind/deferred.c line 28
Quoted 7 lines
+ * id; the lower 48 bits are a per-CPU entry counter.
+ */kernel/unwind/deferred.c new file +5
-0+static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 ); +return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 ); +}
Also, I have to note that 0 is a valid return value here, which will give a ton of fun.
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 8 lines · Peter Zijlstra
Quoted 7 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
+/*
+ * The context cookie is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds. The high 12 bits are the CPUs/12/16/ ?
Quoted 10 lines
Quoted 7 lines
kernel/unwind/deferred.c line 28
Quoted 7 lines
+ * id; the lower 48 bits are a per-CPU entry counter.
+ */kernel/unwind/deferred.c new file +5
-0+static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 ); +return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 ); +}Also, I have to note that 0 is a valid return value here, which will give a ton of fun.
The ctx_ctr is always incremented before calling this, so 0 isn't a valid cookie.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 20 lines · Josh Poimboeuf
Quoted 8 lines · Peter Zijlstra
Quoted 7 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
+/*
+ * The context cookie is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds. The high 12 bits are the CPUs/12/16/ ?
Quoted 10 lines
Quoted 7 lines
kernel/unwind/deferred.c line 28
Quoted 7 lines
+ * id; the lower 48 bits are a per-CPU entry counter.
+ */kernel/unwind/deferred.c new file +5
-0+static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 ); +return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 ); +}Also, I have to note that 0 is a valid return value here, which will give a ton of fun.
Quoted 2 lines
The
ctx_ctris always incremented before calling this, so 0 isn't a valid cookie.
Right, so that's the problem. You're considering 0 an invalid cookie, but ctx_to_cookie(0, 1<<48) will be a 0 cookie.
That thing *will* wrap.
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 9 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
On Wed, Jan 22, 2025 at 03:16:16PM +0100, Peter Zijlstra wrote:
Thectx_ctris always incremented before calling this, so 0 isn't a valid cookie.Right, so that's the problem. You're considering 0 an invalid cookie, but
ctx_to_cookie(0, 1<<48)will be a 0 cookie.That thing *will* wrap.
Well, yes, after N years of sustained very high syscall activity on CPU 0, with stack tracing enabled, in which multiple tracer unwind requests happen to occur in the same entry context where ctx_ctr wrapped, one of the tracers might get an invalid cookie.
I can double-increment the counter when it's (1UL << 48) - 1). Or use some other bit for "cookie valid".
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 11 lines · Josh Poimboeuf
Quoted 9 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
On Wed, Jan 22, 2025 at 03:16:16PM +0100, Peter Zijlstra wrote:
Thectx_ctris always incremented before calling this, so 0 isn't a valid cookie.Right, so that's the problem. You're considering 0 an invalid cookie, but
ctx_to_cookie(0, 1<<48)will be a 0 cookie.That thing *will* wrap.
Quoted 7 lines
Well, yes, after N years of sustained very high syscall activity on CPU 0, with stack tracing enabled, in which multiple tracer unwind requests happen to occur in the same entry context where
ctx_ctrwrapped, one of the tracers might get an invalid cookie.I can double-increment the counter when it's (1UL << 48) - 1). Or use some other bit for "cookie valid".
Right, steal one bit from counter and make it always 1. 47 bit wrap around should be fine.
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
In reply to Peter ZijlstraQuoted 6 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf
+/*
+ * The context cookie is a unique identifier which allows post-processing to
+ * correlate kernel trace(s) with user unwinds. The high 12 bits are the CPUs/12/16/ ?
Oops. Code is right, comment is wrong.
Quoted 8 lines
Quoted 8 lines
+ * id; the lower 48 bits are a per-CPU entry counter.
+ */kernel/unwind/deferred.c new file +5
-0+static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 ); +return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 ); +}
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
In reply to Josh Poimboeufkernel/unwind/deferred.c line 17
Quoted 2 lines · Josh Poimboeuf
kernel/unwind/deferred.c new file +2
-0+/* entry-from-user counter */ +static DEFINE_PER_CPU(u64, unwind_ctx_ctr);
AFAICT from the below, this thing does *not* count entry-from-user. It might count a subset, but I need to stare longer.
Quoted 39 lines
kernel/unwind/deferred.c new file +34
-0+/* +* Read the task context cookie, first initializing it if this is the first +* call to get_cookie() since the most recent entry from user. +*/ +static u64 get_cookie (struct unwind_task_info *info) +{ +u64 ctx_ctr; +u64 cookie; +u64 cpu; + +guard (irqsave)(); + + cookie = info->cookie; +if (cookie) +return cookie; + + + cpu =raw_smp_processor_id (); + ctx_ctr =__this_cpu_inc_return (unwind_ctx_ctr); + info->cookie =ctx_to_cookie (cpu, ctx_ctr); + +return cookie; + +} + +static void unwind_deferred_task_work (struct callback_head *head) +{ + cookie =get_cookie (info); +} + +int unwind_deferred_request (struct unwind_work *work,u64 *cookie) +{ + *cookie =get_cookie (info); +}
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 7 lines · Peter Zijlstra
kernel/unwind/deferred.c line 17
Quoted 2 lines · Josh Poimboeuf
kernel/unwind/deferred.c new file +2
-0+/* entry-from-user counter */ +static DEFINE_PER_CPU(u64, unwind_ctx_ctr);AFAICT from the below, this thing does *not* count entry-from-user. It might count a subset, but I need to stare longer.
Right, it's a subset. Something like so:
/*
* This is a unique percpu identifier for a given task entry context.
* Conceptually, it's incremented every time the CPU enters the kernel from
* user space, so that each "entry context" on the CPU gets a unique ID. In
* reality, as an optimization, it's only incremented on demand for the first
* deferred unwind request after a given entry-from-user.
*
* It's combined with the CPU id to make a systemwide-unique "context cookie". */
static DEFINE_PER_CPU(u64, unwind_ctx_ctr);
Signature · 1 line
Josh
Mathieu Desnoyers
reviewerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
In reply to Josh PoimboeufQuoted 83 lines · Josh Poimboeuf
Add an interface for scheduling 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 be running in NMI or interrupt context.
- Avoid duplicate unwinds, whether called multiple times by the same caller or by different callers.
- Create a "context cookie" which allows trace post-processing to correlate kernel unwinds/traces with the user unwind.
Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/entry-common.h | 2 + include/linux/sched.h | 5 + include/linux/unwind_deferred.h | 46 +++++++ include/linux/unwind_deferred_types.h | 10 ++ kernel/fork.c | 4 + kernel/unwind/Makefile | 2 +- kernel/unwind/deferred.c | 178 ++++++++++++++++++++++++++ 7 files changed, 246 insertions(+), 1 deletion(-)create mode 100644
include/linux/unwind_deferred.hcreate mode 100644include/linux/unwind_deferred_types.hcreate mode 100644kernel/unwind/deferred.cinclude/linux/entry-common.h +2
-0@@ -12,6 +12,7 @@#include <linux/resume_user_mode.h> #include <linux/tick.h> #include <linux/kmsan.h> +#include <linux/unwind_deferred.h> #include <asm/entry-common.h> @@ -111,6 +112,7 @@ 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);include/linux/sched.h +5
-0@@ -46,6 +46,7 @@#include <linux/rv.h> #include <linux/livepatch_sched.h> #include <linux/uidgid_types.h> +#include <linux/unwind_deferred_types.h> #include <asm/kmap_size.h> /* task_struct member predeclarations (sorted alphabetically): */ @@ -1603,6 +1604,10 @@ struct task_struct {struct user_event_mm *user_event_mm;#endif +#ifdef CONFIG_UNWIND_USER +struct unwind_task_info unwind_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_deferred.h new file +7
-0@@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_UNWIND_USER_DEFERRED_H +#define _LINUX_UNWIND_USER_DEFERRED_H + +#include <linux/task_work.h> +#include <linux/unwind_user.h> +#include <linux/unwind_deferred_types.h>
include/linux/unwind_deferred.h line 17
Quoted 10 lines
include/linux/unwind_deferred.h new file +10
-0+ +struct unwind_work ; + +typedef void (*unwind_callback_t )(struct unwind_work *work,struct unwind_stacktrace *trace,u64 cookie); + +struct unwind_work { +struct callback_head work; +unwind_callback_t func; +int pending; +};
This is a lot of information to keep around per instance.
I'm not sure it would be OK to have a single unwind_work per perf-event for perf. I suspect it may need to be per perf-event X per-task if a perf-event can be associated to more than a single task (not sure ?).
For LTTng, we'd have to consider something similar because of multi-session support. Either we'd have one unwind_work per-session X per-task, or we'd need to multiplex this internally within LTTng-modules. None of this is ideal in terms of memory footprint.
We should look at what part of this information can be made static/global and what part is task-local, so we minimize the amount of redundant data per-task (memory footprint).
AFAIU, most of that unwind_work information is global:
- work,
- func,
And could be registered dynamically by the tracer when it enables tracing with an interest on stack walking.
At registration, we can allocate a descriptor ID (with a limited bounded max number, configurable). This would associate a work+func to a given ID, and keep track of this in a global table (indexed by ID).
I suspect that the only thing we really want to keep track of per-task is the pending bit, and what is the ID of the unwind_work associated. This could be kept, per-task, in either:
- a bitmap of pending bits, indexed by ID, or
- an array of pending IDs.
Unregistration of unwind_work could iterate on all tasks and clear the pending bit or ID associated with the unregistered work, to make sure we don't trigger unrelated work after a re-use.
Quoted 104 lines
include/linux/unwind_deferred.h new file +29
-0+ +#ifdef CONFIG_UNWIND_USER + +void unwind_task_init (struct task_struct *task); +void unwind_task_free (struct task_struct *task); + +void unwind_deferred_init (struct unwind_work *work,unwind_callback_t func); +int unwind_deferred_request (struct unwind_work *work,u64 *cookie); +bool unwind_deferred_cancel (struct task_struct *task,struct unwind_work *work); + +static __always_inline void unwind_enter_from_user_mode(void) +{ + current->unwind_info.cookie =0 ; +} + +#else /* !CONFIG_UNWIND_USER */ + +static inline void unwind_task_init (struct task_struct *task) {} +static inline void unwind_task_free (struct task_struct *task) {} + +static inline void unwind_deferred_init (struct unwind_work *work,unwind_callback_t func) {} +static inline int unwind_deferred_request (struct task_struct *task,struct unwind_work *work,u64 *cookie) {return -ENOSYS ; } +static inline bool unwind_deferred_cancel (struct task_struct *task,struct unwind_work *work) {return false; } + +static inline void unwind_enter_from_user_mode (void ) {} + +#endif /* !CONFIG_UNWIND_USER */ + +#endif /* _LINUX_UNWIND_USER_DEFERRED_H */ include/linux/unwind_deferred_types.h new file +10
-0@@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_UNWIND_USER_DEFERRED_TYPES_H +#define _LINUX_UNWIND_USER_DEFERRED_TYPES_H + +struct unwind_task_info { +unsigned long *entries; +u64 cookie; +}; + +#endif /* _LINUX_UNWIND_USER_DEFERRED_TYPES_H */ kernel/fork.c +4
-0@@ -106,6 +106,7 @@#include <linux/pidfs.h> #include <linux/tick.h> #include <linux/sframe.h> +#include <linux/unwind_deferred.h> #include <asm/pgalloc.h> #include <linux/uaccess.h> @@ -973,6 +974,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);@@ -2370,6 +2372,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/Makefile +1
-1@@ -1,2 +1,2 @@ - obj-$(CONFIG_UNWIND_USER) += user.o + obj-$(CONFIG_UNWIND_USER) += user.o deferred.o obj-$(CONFIG_HAVE_UNWIND_USER_SFRAME) += sframe.okernel/unwind/deferred.c new file +16
-0@@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Deferred user space unwinding +*/ +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/sched/task_stack.h> +#include <linux/sframe.h> +#include <linux/slab.h> +#include <linux/task_work.h> +#include <linux/mm.h> +#include <linux/unwind_deferred.h> + +#define UNWIND_MAX_ENTRIES 512 + +/* entry-from-user counter */
kernel/unwind/deferred.c line 26
Quoted 10 lines
kernel/unwind/deferred.c new file +10
-0+static DEFINE_PER_CPU(u64, unwind_ctx_ctr); + +/* +* The context cookie is a unique identifier which allows post-processing to +* correlate kernel trace(s) with user unwinds. The high 12 bits are the CPU +* id; the lower 48 bits are a per-CPU entry counter. +*/ +static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 );
2^12 = 4k, not 64k. Perhaps you mean to reserve 16 bits for cpu numbers ?
kernel/unwind/deferred.c line 27
Quoted 1 line
kernel/unwind/deferred.c new file +1
-0+return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 );
Perhaps use ilog2(NR_CPUS) instead for the number of bits to use rather than hard code 12 ?
Quoted 59 lines
kernel/unwind/deferred.c new file +59
-0+} + +/* +* Read the task context cookie, first initializing it if this is the first +* call to get_cookie() since the most recent entry from user. +*/ +static u64 get_cookie (struct unwind_task_info *info) +{ +u64 ctx_ctr; +u64 cookie; +u64 cpu; + +guard (irqsave)(); + + cookie = info->cookie; +if (cookie) +return cookie; + + + cpu =raw_smp_processor_id (); + ctx_ctr =__this_cpu_inc_return (unwind_ctx_ctr); + info->cookie =ctx_to_cookie (cpu, ctx_ctr); + +return cookie; + +} + +static void unwind_deferred_task_work (struct callback_head *head) +{ + struct unwind_work *work = container_of(head, struct unwind_work, work); +struct unwind_task_info *info = ¤t->unwind_info; +struct unwind_stacktrace trace; +u64 cookie; + +if (WARN_ON_ONCE (!work->pending)) +return ; + +/* +* From here on out, the callback must always be called, even if it's +* just an empty trace. +*/ + + cookie =get_cookie (info); + +/* Check for task exit path. */ +if (!current->mm) + goto do_callback; + +if (!info->entries) { + info->entries =kmalloc (UNWIND_MAX_ENTRIES *sizeof (long ), +GFP_KERNEL ); +if (!info->entries) + goto do_callback; + } + + trace.entries = info->entries; + trace.nr =0 ; +unwind_user (&trace,UNWIND_MAX_ENTRIES ); +
kernel/unwind/deferred.c line 96
Quoted 10 lines
kernel/unwind/deferred.c new file +10
-0+do_callback: + work->func (work, &trace, cookie); + work->pending =0 ; +} + +/* +* Schedule a user space unwind to be done in task work before exiting the +* kernel. +* +* The returned cookie output is a unique identifer for the current task entry
identifier
Thanks,
Mathieu
Quoted 82 lines
kernel/unwind/deferred.c new file +82
-0+* context. Its value will also be passed to the callback function. It can be +* used to stitch kernel and user stack traces together in post-processing. +* +* It's valid to call this function multiple times for the same @work within +* the same task entry context. Each call will return the same cookie. If the +* callback is already pending, an error will be returned along with the +* cookie. If the callback is not pending because it has already been +* previously called for the same entry context, it will be called again with +* the same stack trace and cookie. +* +* Thus are three possible return scenarios: +* +* * return != 0, *cookie == 0: the operation failed, no pending callback. +* +* * return != 0, *cookie != 0: the callback is already pending. The cookie +* can still be used to correlate with the pending callback. +* +* * return == 0, *cookie != 0: the callback queued successfully. The +* callback is guaranteed to be called with the given cookie. +*/ +int unwind_deferred_request (struct unwind_work *work,u64 *cookie) +{ +struct unwind_task_info *info = ¤t->unwind_info; +int ret; + + *cookie =0 ; + +if (WARN_ON_ONCE (in_nmi ())) +return -EINVAL ; + +if (!current->mm || !user_mode (task_pt_regs (current))) +return -EINVAL ; + +guard (irqsave)(); + + *cookie =get_cookie (info); + +/* callback already pending? */ +if (work->pending) +return -EEXIST ; + + ret =task_work_add (current, &work->work,TWA_RESUME ); +if (WARN_ON_ONCE (ret)) +return ret; + + work->pending =1 ; + +return 0 ; +} + +bool unwind_deferred_cancel (struct task_struct *task,struct unwind_work *work) +{ +bool ret; + + ret =task_work_cancel (task, &work->work); +if (ret) + work->pending =0 ; + +return ret; +} + +void unwind_deferred_init (struct unwind_work *work,unwind_callback_t func) +{ +memset (work,0 ,sizeof (*work)); + +init_task_work (&work->work, unwind_deferred_task_work); + work->func = func; +} + +void unwind_task_init (struct task_struct *task) +{ +struct unwind_task_info *info = &task->unwind_info; + +memset (info,0 ,sizeof (*info)); +} + +void unwind_task_free (struct task_struct *task) +{ +struct unwind_task_info *info = &task->unwind_info; + +kfree (info->entries); +}
Signature · 3 lines
Mathieu Desnoyers EfficiOS Inc. https://www.efficios.com
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 11 lines · Mathieu Desnoyers
include/linux/unwind_deferred.h line 17
Quoted 5 lines
include/linux/unwind_deferred.h new file +5
-0+struct unwind_work { +struct callback_head work; +unwind_callback_t func; +int pending; +};This is a lot of information to keep around per instance.
I'm not sure it would be OK to have a single
unwind_workper perf-event for perf. I suspect it may need to be per perf-event X per-task if a perf-event can be associated to more than a single task (not sure ?).
For "perf record -g <command>", it seems to be one event per task. Incidentally this is the mode where I did my perf testing :-/
But looking at it now, a global "perf record -g" appears to use one event per CPU. So if a task requests an unwind and then schedules out before returning to user space, any subsequent tasks trying to unwind on that CPU would be blocked until the original task returned to user. So yeah, that's definitely a problem.
Actually a per-CPU unwind_work descriptor could conceivably work if we able to unwind at schedule() time.
But Steve pointed out that wouldn't work so well if the task isn't in RUNNING state.
However... would it be a horrible idea for 'next' to unwind 'prev' after the context switch???
Quoted 21 lines
For LTTng, we'd have to consider something similar because of multi-session support. Either we'd have one
unwind_workper-session X per-task, or we'd need to multiplex this internally within LTTng-modules. None of this is ideal in terms of memory footprint.We should look at what part of this information can be made static/global and what part is task-local, so we minimize the amount of redundant data per-task (memory footprint).
AFAIU, most of that
unwind_workinformation is global:- work,
- func,And could be registered dynamically by the tracer when it enables tracing with an interest on stack walking.
At registration, we can allocate a descriptor ID (with a limited bounded max number, configurable). This would associate a work+func to a given ID, and keep track of this in a global table (indexed by ID).
Quoted 6 lines
I suspect that the only thing we really want to keep track of per-task is the pending bit, and what is the ID of the
unwind_workassociated. This could be kept, per-task, in either:- a bitmap of pending bits, indexed by ID, or
- an array of pending IDs.
That's basically what I was doing before. The per-task state also had:
- 'struct callback_head work' for doing the task work. A single work function was used to multiplex the callbacks, as opposed to the current patches where each descriptor gets its own separate task_work.
- 'void *privs[UNWIND_MAX_CALLBACKS]' opaque data pointers. Maybe some callbacks don't need that, but perf needed it for the 'event' pointer. For 32 max callbacks that's 256 bytes per task.
- 'u64 last_cookies[UNWIND_MAX_CALLBACKS]' to prevent a callback from getting called twice. But actually that may have been overkill, it should be fine to call the callback again with the cached stack trace. The tracer could instead have its own policy for how to handle dupes.
- 'unsigned int work_pending' to designate whether the task_work is pending. Also probably not necessary, the pending bits could serve the same purpose.
So it had more concurrency to deal with, to handle the extra per-task state.
It also had a global array of callbacks, which used a mutex and SRCU to coordinate between the register/unregister and the task work.
Another major issue was that it wasn't NMI-safe due to all the shared state. So a tracer in NMI would have to schedule an IRQ to call unwind_deferred_request(). Not only is that a pain for the tracers, it's problematic in other ways:
- If the NMI occurred in schedule() with IRQs disabled, the IRQ would actually interrupt the 'next' task. So the caller would have to stash a 'task' pointer for the IRQ handler to read and pass to unwind_deferred_request(). (similar to the task_work bug I found)
- Thus the deferred unwind interface would need to handle requests from non-current, introducing a new set of concurrency issues.
- Also, while a tracer in NMI can unwind the kernel stack and send that to a ring buffer immediately, it can't store the cookie along with it, so there lie more tracer headaches.
Once I changed the interface to get rid of the global nastiness, all those problems went away.
Of course that now introduces the new problem that each tracer (or tracing event) needs some kind of per-task state. But otherwise this new interface really simplifies things a *lot*.
Anyway, I don't have a good answer at the moment. Will marinate on it.
Maybe we could do something like allocate the unwind_work (or some equivalent) on demand at the time of unwind request using GFP_NOWAIT or GFP_ATOMIC or some such, then free it during the task work?
Quoted 3 lines
Unregistration of
unwind_workcould iterate on all tasks and clear the pending bit or ID associated with the unregistered work, to make sure we don't trigger unrelated work after a re-use.
What the old unregister code did was to remove it from the global callbacks array (with the careful use of mutex+SRCU to coordinate with the task work). Then synchronize_srcu() before returning.
Quoted 11 lines
kernel/unwind/deferred.c line 26
Quoted 8 lines
kernel/unwind/deferred.c new file +8
-0+/* +* The context cookie is a unique identifier which allows post-processing to +* correlate kernel trace(s) with user unwinds. The high 12 bits are the CPU +* id; the lower 48 bits are a per-CPU entry counter. +*/ +static u64 ctx_to_cookie (u64 cpu,u64 ctx) +{ +BUILD_BUG_ON (NR_CPUS >65535 );2^12 = 4k, not 64k. Perhaps you mean to reserve 16 bits for cpu numbers ?
Yeah, here the code is right but the comment is wrong. It actually does use 16 bits.
Quoted 4 lines
kernel/unwind/deferred.c line 27
Quoted 1 line
kernel/unwind/deferred.c new file +1
-0+return (ctx & ((1UL <<48 ) -1 )) | (cpu <<48 );Perhaps use
ilog2(NR_CPUS)instead for the number of bits to use rather than hard code 12 ?
I'm thinking I'd rather keep it simple by hard-coding the # of bits, so as to avoid any surprises caused by edge cases.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 2 lines · Josh Poimboeuf
However... would it be a horrible idea for 'next' to unwind 'prev' after the context switch???
The idea isn't terrible, but it will be all sorta of tricky.
The big immediate problem is that the CPU doing the context switch looses control over prev at:
__schedule()
context_switch()
finish_task_switch()
finish_task()
smp_store_release(&prev->on_cpu, 0);And this is before we drop rq->lock.
The instruction after that store another CPU is free to claim the task and run with it. Notably, another CPU might already be spin waiting on that state, trying to wake the task back up.
By the time we get to a schedulable context, @prev is completely out of bounds.
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 16 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
However... would it be a horrible idea for 'next' to unwind 'prev' after the context switch???
The idea isn't terrible, but it will be all sorta of tricky.
The big immediate problem is that the CPU doing the context switch looses control over prev at:
__schedule()context_switch() finish_task_switch() finish_task() smp_store_release(&prev->on_cpu, 0);
Quoted 8 lines
And this is before we drop
rq->lock.The instruction after that store another CPU is free to claim the task and run with it. Notably, another CPU might already be spin waiting on that state, trying to wake the task back up.
By the time we get to a schedulable context, @prev is completely out of bounds.
Could unwind_deferred_request() call migrate_disable() or so?
How bad would it be to set some bit in @prev to prevent it from getting rescheduled until the unwind from @next has been done? Unfortunately two tasks would be blocked on the unwind instead of one.
BTW, this might be useful for another reason. In Steve's sframe meeting yesterday there was some talk of BPF needing to unwind from sched-switch, without having to wait indefinitely for @prev to get rescheduled and return to user.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 19 lines · Josh Poimboeuf
Quoted 18 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
However... would it be a horrible idea for 'next' to unwind 'prev' after the context switch???
The idea isn't terrible, but it will be all sorta of tricky.
The big immediate problem is that the CPU doing the context switch looses control over prev at:
__schedule()context_switch() finish_task_switch() finish_task() smp_store_release(&prev->on_cpu, 0);And this is before we drop
rq->lock.
Quoted 8 lines
Quoted 6 lines
The instruction after that store another CPU is free to claim the task and run with it. Notably, another CPU might already be spin waiting on that state, trying to wake the task back up.
By the time we get to a schedulable context, @prev is completely out of bounds.
Could
unwind_deferred_request()callmigrate_disable()or so?
That's pretty vile... and might cause performance issues. You realy don't want things to magically start behaving differently just because you're tracing.
Quoted 3 lines
How bad would it be to set some bit in @prev to prevent it from getting rescheduled until the unwind from @next has been done? Unfortunately two tasks would be blocked on the unwind instead of one.
Yeah, not going to happen. Those paths are complicated enough as is.
Quoted 4 lines
BTW, this might be useful for another reason. In Steve's sframe meeting yesterday there was some talk of BPF needing to unwind from sched-switch, without having to wait indefinitely for @prev to get rescheduled and return to user.
-EPONIES, you cannot take faults from the middle of schedule(). They can always use the best effort FP unwind we have today.
Steven Rostedt
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 2 lines · On Thu, 23 Jan 2025 23:13:26 +0100 Peter Zijlstra <[email protected]> wrote:
-EPONIES, you cannot take faults from the middle ofschedule(). They can always use the best effort FP unwind we have today.
Agreed.
Now the only thing I could think of is a flag gets set where the task comes out of the scheduler and then does the stack trace. It doesn't need to do the stack trace before it schedules. As it did just schedule, where ever it scheduled must have been in a schedulable context.
That is, kind of like the task_work flag for entering user space and exiting the kernel, could we have a sched_work flag to run after after being scheduled back (exiting schedule()). Since the task has been picked to run, it will not cause latency for other tasks. The work will be done in its context. This is no different to the tasks accounting than if it does this going back to user space. Heck, it would only need to do this once if it didn't go back to user space, as the user space stack would be the same. That is, if it gets scheduled multiple times, this would only happen on the first instance until it leaves the kernel.
[ trigger stack trace - set sched_work ]
schedule() {
context_switch() -> CPU runs some other task
<- gets scheduled back onto the CPU
[..]
/* preemption enabled ... */
if (sched_work) {
do stack trace() // can schedule here but
// calls a schedule function that does not
// do sched_work to prevent recursion
}
}Could something like this work?
-- Steve
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 38 lines · Steven Rostedt
Quoted 2 lines · On Thu, 23 Jan 2025 23:13:26 +0100 Peter Zijlstra <[email protected]> wrote:
-EPONIES, you cannot take faults from the middle ofschedule(). They can always use the best effort FP unwind we have today.Agreed.
Now the only thing I could think of is a flag gets set where the task comes out of the scheduler and then does the stack trace. It doesn't need to do the stack trace before it schedules. As it did just schedule, where ever it scheduled must have been in a schedulable context.
That is, kind of like the
task_workflag for entering user space and exiting the kernel, could we have asched_workflag to run after after being scheduled back (exitingschedule()). Since the task has been picked to run, it will not cause latency for other tasks. The work will be done in its context. This is no different to the tasks accounting than if it does this going back to user space. Heck, it would only need to do this once if it didn't go back to user space, as the user space stack would be the same. That is, if it gets scheduled multiple times, this would only happen on the first instance until it leaves the kernel.[ trigger stack trace - set sched_work ] schedule() { context_switch() -> CPU runs some other task <- gets scheduled back onto the CPU [..] /* preemption enabled ... */ if (sched_work) { do stack trace() // can schedule here but // calls a schedule function that does not // do sched_work to prevent recursion } }
Quoted 1 line
Could something like this work?
Yeah, this is basically a more fleshed out version of what I was trying to propose.
One additional wrinkle is that if @prev wakes up on another CPU while @next is unwinding it, the unwind goes haywire. So that would maybe need to be prevented.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 31 lines · Josh Poimboeuf
Quoted 30 lines · Steven Rostedt
Now the only thing I could think of is a flag gets set where the task comes out of the scheduler and then does the stack trace. It doesn't need to do the stack trace before it schedules. As it did just schedule, where ever it scheduled must have been in a schedulable context.
That is, kind of like the
task_workflag for entering user space and exiting the kernel, could we have asched_workflag to run after after being scheduled back (exitingschedule()). Since the task has been picked to run, it will not cause latency for other tasks. The work will be done in its context. This is no different to the tasks accounting than if it does this going back to user space. Heck, it would only need to do this once if it didn't go back to user space, as the user space stack would be the same. That is, if it gets scheduled multiple times, this would only happen on the first instance until it leaves the kernel.[ trigger stack trace - set sched_work ] schedule() { context_switch() -> CPU runs some other task <- gets scheduled back onto the CPU [..] /* preemption enabled ... */ if (sched_work) { do stack trace() // can schedule here but // calls a schedule function that does not // do sched_work to prevent recursion } }
Quoted 8 lines
Quoted 1 line
Could something like this work?
Yeah, this is basically a more fleshed out version of what I was trying to propose.
One additional wrinkle is that if @prev wakes up on another CPU while @next is unwinding it, the unwind goes haywire. So that would maybe need to be prevented.
Hm, reading this again I'm wondering if you're actually proposing that the unwind happens on @prev after it gets rescheduled sometime in the future? Does that actually solve the issue? What if doesn't get rescheduled within a reasonable amount of time?
Signature · 1 line
Josh
Steven Rostedt
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 4 lines · On Fri, 24 Jan 2025 14:50:11 -0800 Josh Poimboeuf <[email protected]> wrote:
Hm, reading this again I'm wondering if you're actually proposing that the unwind happens on @prev after it gets rescheduled sometime in the future? Does that actually solve the issue? What if doesn't get rescheduled within a reasonable amount of time?
Correct, it would be prev that would be doing the unwinding and not next. But when prev is scheduled back onto the CPU. That way it's only blocking itself.
The use case that people were doing this with was measuring the time a task is off the CPU. It can't get that time until the task schedules back anyway. What the complaint was about was that it could be a very long system call, with lots of sleeps and they couldn't do the processing.
I can go back and ask, but I'm pretty sure doing the unwind when a task comes back to the CPU would be sufficient.
-- Steve
Steven Rostedt
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 12 lines · On Fri, 24 Jan 2025 18:57:44 -0500 Steven Rostedt <[email protected]> wrote:
Quoted 4 lines · On Fri, 24 Jan 2025 14:50:11 -0800 Josh Poimboeuf <[email protected]> wrote:
Hm, reading this again I'm wondering if you're actually proposing that the unwind happens on @prev after it gets rescheduled sometime in the future? Does that actually solve the issue? What if doesn't get rescheduled within a reasonable amount of time?
Correct, it would be prev that would be doing the unwinding and not next. But when prev is scheduled back onto the CPU. That way it's only blocking itself.
Quoted 8 lines
The use case that people were doing this with was measuring the time a task is off the CPU. It can't get that time until the task schedules back anyway. What the complaint was about was that it could be a very long system call, with lots of sleeps and they couldn't do the processing.
I can go back and ask, but I'm pretty sure doing the unwind when a task comes back to the CPU would be sufficient.
Coming back from this. It would be fine if we could do the back trace when we come back from the scheduler, so it should not be an issue if the task even has to schedule again to fault in the sframe information.
I was also wondering if the unwinder doesn't keep track of who requested the back trace, just that someone did. Then it would just take a single flag in the task struct to do the back trace. Return the "cookie" to the tracer that requested the back trace, and when you do the back trace, just call all callbacks with that cookie. Let the tracer decided if it wants to record the back trace or ignore it based on the cookie.
That is, the tracers would need to keep track of the cookies that it cares about, as if there's other tracers asking for stack traces on tasks that this tracer doesn't care about it needs to handle being called when it doesn't care about the stack trace. That said, if you want to trace all tasks, you can just ignore the cookies and record the traces.
-- Steve
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 3 lines · Steven Rostedt
Coming back from this. It would be fine if we could do the back trace when we come back from the scheduler, so it should not be an issue if the task even has to schedule again to fault in the sframe information.
So there would be two callback hook points:
- schedule() after enabling preemption
- task work
and first one wins?
Quoted 7 lines
I was also wondering if the unwinder doesn't keep track of who requested the back trace, just that someone did. Then it would just take a single flag in the task struct to do the back trace. Return the "cookie" to the tracer that requested the back trace, and when you do the back trace, just call all callbacks with that cookie. Let the tracer decided if it wants to record the back trace or ignore it based on the cookie.
Quoted 5 lines
That is, the tracers would need to keep track of the cookies that it cares about, as if there's other tracers asking for stack traces on tasks that this tracer doesn't care about it needs to handle being called when it doesn't care about the stack trace. That said, if you want to trace all tasks, you can just ignore the cookies and record the traces.
Easy enough for the unwinder, but IIUC each tracer would have to maintain a global list of pending cookies (and corresponding ptrs to perf_event, trace_array, etc)? Would that not create a lot of contention?
Seems like there really needs to be some kind of per-task or per-request state.
Signature · 1 line
Josh
Re: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
In reply to Josh PoimboeufQuoted 13 lines · Josh Poimboeuf
include/linux/unwind_deferred.h +11
-0+#ifdef CONFIG_UNWIND_USER + +void unwind_task_init (struct task_struct *task); +void unwind_task_free (struct task_struct *task); + +void unwind_deferred_init (struct unwind_work *work,unwind_callback_t func); +int unwind_deferred_request (struct unwind_work *work,u64 *cookie); +bool unwind_deferred_cancel (struct task_struct *task,struct unwind_work *work); + +static __always_inline void unwind_enter_from_user_mode(void) +{
include/linux/unwind_deferred.h line 39
Quoted 10 lines
include/linux/unwind_deferred.h +10
-0+ current->unwind_info.cookie = 0; +} + +#else /* !CONFIG_UNWIND_USER */ + +static inline void unwind_task_init (struct task_struct *task) {} +static inline void unwind_task_free (struct task_struct *task) {} + +static inline void unwind_deferred_init (struct unwind_work *work,unwind_callback_t func) {} +static inline int unwind_deferred_request (struct task_struct *task,struct unwind_work *work,u64 *cookie) {return -ENOSYS ; }
static inline int unwind_deferred_request(struct unwind_work *work, u64 *cookie) { return -ENOSYS; }
Otherwise this does not compile on architectures that do not enable UNWIND_USER.
include/linux/unwind_deferred.h line 44
Quoted 5 lines
include/linux/unwind_deferred.h +5
-0+static inline bool unwind_deferred_cancel (struct task_struct *task,struct unwind_work *work) {return false; } + +static inline void unwind_enter_from_user_mode (void ) {} + +#endif /* !CONFIG_UNWIND_USER */
Regards,
Jens
Signature · 9 lines
Jens Remus Linux on Z Development (D3303) +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/
Josh Poimboeuf
maintainerRe: [PATCH v4 28/39] unwind_user/deferred: Add deferred unwinding interface
Quoted 8 lines · Jens Remus
include/linux/unwind_deferred.h line 39
Quoted 2 lines · Josh Poimboeuf
include/linux/unwind_deferred.h new file +2
-0+static inline void unwind_deferred_init (struct unwind_work *work,unwind_callback_t func) {} +static inline int unwind_deferred_request (struct task_struct *task,struct unwind_work *work,u64 *cookie) {return -ENOSYS ; }static inline int
unwind_deferred_request(struct unwind_work *work, u64 *cookie){ return-ENOSYS; }Otherwise this does not compile on architectures that do not enable
UNWIND_USER.
Yeah, bots have reported that also, thanks.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 29/39 unwind_user/deferred: Add unwind cache
In reply to Josh PoimboeufCache the results of the unwind to ensure the unwind is only performed once, even when called by multiple tracers.
--- include/linux/unwind_deferred_types.h | 8 +++++++- kernel/unwind/deferred.c | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-)
include/linux/unwind_deferred_types.h +7 -1
@@ -2,8 +2,14 @@#ifndef _LINUX_UNWIND_USER_DEFERRED_TYPES_H#define _LINUX_UNWIND_USER_DEFERRED_TYPES_H -struct unwind_task_info { +struct unwind_cache {unsigned long *entries; +unsigned int nr_entries; +u64 cookie; +}; + +struct unwind_task_info { +struct unwind_cache cache;u64 cookie; };
kernel/unwind/deferred.c +20 -6
@@ -56,6 +56,7 @@ static void unwind_deferred_task_work(struct callback_head *head) { struct unwind_work *work = container_of(head, struct unwind_work, work);struct unwind_task_info *info = ¤t->unwind_info; +struct unwind_cache *cache = &info->cache;struct unwind_stacktrace trace;u64 cookie;
@@ -73,17 +74,30 @@ static void unwind_deferred_task_work(struct callback_head *head)if (!current->mm) goto do_callback; -if (!info->entries) { - info->entries =kmalloc (UNWIND_MAX_ENTRIES *sizeof (long ), -GFP_KERNEL ); -if (!info->entries) +if (!cache->entries) { + cache->entries =kmalloc (UNWIND_MAX_ENTRIES *sizeof (long ), +GFP_KERNEL ); +if (!cache->entries) goto do_callback; } - trace.entries = info->entries; + trace.entries = cache->entries; + +if (cookie == cache->cookie) { +/* +* The user stack has already been previously unwound in this +* entry context. Skip the unwind and use the cache. +*/ + trace.nr = cache->nr_entries; + goto do_callback; + } + trace.nr =0 ;unwind_user (&trace,UNWIND_MAX_ENTRIES ); + cache->cookie = cookie; + cache->nr_entries = trace.nr; + do_callback: work->func (work, &trace, cookie); work->pending =0 ;
@@ -174,5 +188,5 @@ void unwind_task_free(struct task_struct *task) {struct unwind_task_info *info = &task->unwind_info; -kfree (info->entries); +kfree (info->cache.entries); }
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 29/39] unwind_user/deferred: Add unwind cache
Quoted 14 lines · Josh Poimboeuf
Cache the results of the unwind to ensure the unwind is only performed once, even when called by multiple tracers.
Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/unwind_deferred_types.h | 8 +++++++- kernel/unwind/deferred.c | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-)include/linux/unwind_deferred_types.h +0
-0
include/linux/unwind_deferred_types.h line 9
Quoted 9 lines
include/linux/unwind_deferred_types.h +4
-1@@ -2,8 +2,14 @@#ifndef _LINUX_UNWIND_USER_DEFERRED_TYPES_H#define _LINUX_UNWIND_USER_DEFERRED_TYPES_H -struct unwind_task_info { +struct unwind_cache {unsigned long *entries; +unsigned int nr_entries; +u64 cookie; +};
If you make the return to user path clear nr_entries you don't need a second cookie field I think.
Josh Poimboeuf
maintainerRe: [PATCH v4 29/39] unwind_user/deferred: Add unwind cache
Quoted 18 lines · Peter Zijlstra
Quoted 17 lines · Josh Poimboeuf
Cache the results of the unwind to ensure the unwind is only performed once, even when called by multiple tracers.
Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/unwind_deferred_types.h | 8 +++++++- kernel/unwind/deferred.c | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-)include/linux/unwind_deferred_types.h +0
-0@@ -2,8 +2,14 @@#ifndef _LINUX_UNWIND_USER_DEFERRED_TYPES_H#define _LINUX_UNWIND_USER_DEFERRED_TYPES_H
Quoted 9 lines
include/linux/unwind_deferred_types.h line 9
Quoted 6 lines
include/linux/unwind_deferred_types.h +4
-1-struct unwind_task_info { +struct unwind_cache {unsigned long *entries; +unsigned int nr_entries; +u64 cookie; +};If you make the return to user path clear
nr_entriesyou don't need a second cookie field I think.
But if the NMI happens late in the exit-to-user path, with IRQs disabled, right before nr_entries gets cleared, the cache won't get used in the task work.
However I think we can clear it on entry-from-user.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 29/39] unwind_user/deferred: Add unwind cache
Quoted 29 lines · Josh Poimboeuf
Quoted 18 lines · Peter Zijlstra
Quoted 17 lines · Josh Poimboeuf
Cache the results of the unwind to ensure the unwind is only performed once, even when called by multiple tracers.
Signed-off-by Josh Poimboeuf <[email protected]>--- include/linux/unwind_deferred_types.h | 8 +++++++- kernel/unwind/deferred.c | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-)include/linux/unwind_deferred_types.h +0
-0@@ -2,8 +2,14 @@#ifndef _LINUX_UNWIND_USER_DEFERRED_TYPES_H#define _LINUX_UNWIND_USER_DEFERRED_TYPES_HQuoted 9 lines
include/linux/unwind_deferred_types.h line 9
Quoted 6 lines
include/linux/unwind_deferred_types.h +4
-1-struct unwind_task_info { +struct unwind_cache {unsigned long *entries; +unsigned int nr_entries; +u64 cookie; +};If you make the return to user path clear
nr_entriesyou don't need a second cookie field I think.
Quoted 5 lines
But if the NMI happens late in the exit-to-user path, with IRQs disabled, right before
nr_entriesgets cleared, the cache won't get used in the task work.However I think we can clear it on entry-from-user.
Return to user runs with interrupts disabled, if an NMI hits that, it will have to set TIF_NOTIFY_RESUME again and queue the IRQ work thing. That self-IPI will hit the moment we do IRET (which is what re-enables interrupts) and we're going back into the kernel.
Anyway, I suppose that is a long way of saying that you should be able to do this on return to user.
But yes, enter-from-user should work too.
Josh Poimboeuf
maintainerRe: [PATCH v4 29/39] unwind_user/deferred: Add unwind cache
Quoted 9 lines · Peter Zijlstra
Quoted 7 lines · Josh Poimboeuf
Quoted 1 line · Peter Zijlstra
On Tue, Jan 21, 2025 at 06:31:21PM -0800, Josh Poimboeuf wrote:
But if the NMI happens late in the exit-to-user path, with IRQs disabled, right before
nr_entriesgets cleared, the cache won't get used in the task work.However I think we can clear it on entry-from-user.
Quoted 7 lines
Return to user runs with interrupts disabled, if an NMI hits that, it will have to set
TIF_NOTIFY_RESUMEagain and queue the IRQ work thing. That self-IPI will hit the moment we do IRET (which is what re-enables interrupts) and we're going back into the kernel.Anyway, I suppose that is a long way of saying that you should be able to do this on return to user.
Indeed, I knew that but somehow overlooked the fact that the IRQ would clear the cookie so the cache wouldn't be usable anyway.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v4 30/39 unwind_user/deferred: Make unwind deferral requests NMI-safe
In reply to Josh PoimboeufMake unwind_deferred_request() NMI-safe so tracers in NMI context can call it to get the cookie immediately rather than have to do the fragile "schedule irq work and then call unwind_deferred_request()" dance.
--- include/linux/entry-common.h | 1 + include/linux/unwind_deferred.h | 6 ++ include/linux/unwind_deferred_types.h | 1 + kernel/unwind/deferred.c | 106 ++++++++++++++++++++++---- 4 files changed, 98 insertions(+), 16 deletions(-)
include/linux/entry-common.h +1 -0
@@ -363,6 +363,7 @@ static __always_inline void exit_to_user_mode(void)lockdep_hardirqs_on_prepare ();instrumentation_end (); +unwind_exit_to_user_mode ();user_enter_irqoff ();arch_exit_to_user_mode ();lockdep_hardirqs_on (CALLER_ADDR0 );
include/linux/unwind_deferred.h +6 -0
@@ -30,6 +30,11 @@ static __always_inline void unwind_enter_from_user_mode(void) current->unwind_info.cookie =0 ; } +static __always_inline void unwind_exit_to_user_mode(void) +{ + current->unwind_info.cookie =0 ; +} +#else /* !CONFIG_UNWIND_USER */ static inline void unwind_task_init (struct task_struct *task) {}
@@ -40,6 +45,7 @@ static inline int unwind_deferred_request(struct task_struct *task, struct unwinstatic inline bool unwind_deferred_cancel (struct task_struct *task,struct unwind_work *work) {return false; }static inline void unwind_enter_from_user_mode (void ) {} +static inline void unwind_exit_to_user_mode (void ) {}#endif /* !CONFIG_UNWIND_USER */
include/linux/unwind_deferred_types.h +1 -0
@@ -11,6 +11,7 @@ struct unwind_cache {struct unwind_task_info {struct unwind_cache cache;u64 cookie; +u64 nmi_cookie; };#endif /* _LINUX_UNWIND_USER_DEFERRED_TYPES_H */
kernel/unwind/deferred.c +90 -16
153 lines not on this page · raw message
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 49 lines · Josh Poimboeuf
kernel/unwind/deferred.c +23
-6@@ -29,27 +29,49 @@ static u64 ctx_to_cookie(u64 cpu, u64 ctx)/* * Read the task context cookie, first initializing it if this is the first -* call to get_cookie() since the most recent entry from user. +* call to get_cookie() since the most recent entry from user. This has to be +* done carefully to coordinate with unwind_deferred_request_nmi(). */ static u64 get_cookie (struct unwind_task_info *info) {u64 ctx_ctr;u64 cookie; -u64 cpu;guard (irqsave)(); - cookie = info->cookie; + cookie =READ_ONCE (info->cookie);if (cookie)return cookie; + ctx_ctr =__this_cpu_read (unwind_ctx_ctr); - cpu =raw_smp_processor_id (); - ctx_ctr =__this_cpu_inc_return (unwind_ctx_ctr); - info->cookie =ctx_to_cookie (cpu, ctx_ctr); +/* Read ctx_ctr before info->nmi_cookie */ +barrier (); + + cookie =READ_ONCE (info->nmi_cookie); +if (cookie) { +/* +* This is the first call to get_cookie() since an NMI handler +* first wrote it to info->nmi_cookie. Sync it. +*/ +WRITE_ONCE (info->cookie, cookie); +WRITE_ONCE (info->nmi_cookie,0 ); +return cookie; + } + +/* +* Write info->cookie. It's ok to race with an NMI here. The value of +* the cookie is based on ctx_ctr from before the NMI could have +* incremented it. The result will be the same even if cookie or +* ctx_ctr end up getting written twice.
kernel/unwind/deferred.c static u64 ctx_to_cookie(u64 cpu, u64 ctx) · line 75
Quoted 10 lines
kernel/unwind/deferred.c +6
-1+*/ + cookie =ctx_to_cookie (raw_smp_processor_id (), ctx_ctr +1 ); +WRITE_ONCE (info->cookie, cookie); +WRITE_ONCE (info->nmi_cookie,0 ); +barrier (); +__this_cpu_write (unwind_ctx_ctr, ctx_ctr +1 );return cookie; - }
Oh gawd. Can we please do something simple like:
guard(irqsave)();
cpu = raw_smp_processor_id();
ctr = __this_cpu_read(unwind_ctx_cnt);
cookie = READ_ONCE(current->unwind_info.cookie);
do {
if (cookie)
return cookie;
cookie = ctx_to_cookie(cpu, ctr+1);
} while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, cookie));
__this_cpu_write(unwind_ctx_ctr, ctr+1);
return cookie;Josh Poimboeuf
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 6 lines · Peter Zijlstra
On Tue, Jan 21, 2025 at 06:31:22PM -0800, Josh Poimboeuf wrote:
Oh gawd. Can we please do something simple like:guard(irqsave)(); cpu = raw_smp_processor_id(); ctr = __this_cpu_read(unwind_ctx_cnt);
Don't you need a compiler barrier here? __this_cpu_read() doesn't have one.
Quoted 8 lines
cookie = READ_ONCE(current->unwind_info.cookie);
do {
if (cookie)
return cookie;
cookie = ctx_to_cookie(cpu, ctr+1);
} while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, cookie));
__this_cpu_write(unwind_ctx_ctr, ctr+1);
return cookie;I was trying to avoid the overhead of the cmpxchg.
But also, the nmi_cookie is still needed for the case where the NMI arrives before info->cookie gets cleared by early entry-from-user.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 10 lines · Josh Poimboeuf
Quoted 6 lines · Peter Zijlstra
On Tue, Jan 21, 2025 at 06:31:22PM -0800, Josh Poimboeuf wrote:
Oh gawd. Can we please do something simple like:guard(irqsave)(); cpu = raw_smp_processor_id(); ctr = __this_cpu_read(unwind_ctx_cnt);Don't you need a compiler barrier here?
__this_cpu_read()doesn't have one.
What for?
Quoted 10 lines
Quoted 8 lines
cookie = READ_ONCE(current->unwind_info.cookie); do { if (cookie) return cookie; cookie = ctx_to_cookie(cpu, ctr+1); } while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, cookie)); __this_cpu_write(unwind_ctx_ctr, ctr+1); return cookie;I was trying to avoid the overhead of the cmpxchg.
We're going to be doing userspace stack unwinding, I don't think overhead is a real concern.
Quoted 2 lines
But also, the
nmi_cookieis still needed for the case where the NMI arrives beforeinfo->cookiegets cleared by early entry-from-user.
So how about we clear cookie (and set nr_entries to -1) at return-to-user, after we've done the work loop and have interrupts disabled until we hit userspace.
Any NMI that hits there will have to cause another entry anyway.
Josh Poimboeuf
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Josh Poimboeuf
Quoted 3 lines · Peter Zijlstra
On Tue, Jan 21, 2025 at 06:31:22PM -0800, Josh Poimboeuf wrote:
Oh gawd. Can we please do something simple like:
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
guard(irqsave)(); cpu = raw_smp_processor_id(); ctr = __this_cpu_read(unwind_ctx_cnt);Don't you need a compiler barrier here?
__this_cpu_read()doesn't have one.What for?
Hm, I guess it's not needed for this one.
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
cookie = READ_ONCE(current->unwind_info.cookie); do { if (cookie) return cookie; cookie = ctx_to_cookie(cpu, ctr+1); } while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, cookie));
Should not the 2nd argument be &zero?
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
__this_cpu_write(unwind_ctx_ctr, ctr+1); return cookie;But also, the
nmi_cookieis still needed for the case where the NMI arrives beforeinfo->cookiegets cleared by early entry-from-user.So how about we clear cookie (and set
nr_entriesto -1) at
I think we could set nr_entries to 0 instead of -1?
Quoted 4 lines
return-to-user, after we've done the work loop and have interrupts disabled until we hit userspace.
Any NMI that hits there will have to cause another entry anyway.
But there's a cookie mismatch:
// return-to-user: IRQs disabled
<NMI>
current->unwind_info.cookie = 0x1234
</NMI>
unwind_exit_to_user_mode()
current->unwind_info.cookie = 0
IRET
<IRQ>
task_work()
callback(@cookie=WRONG)Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 16 lines · Josh Poimboeuf
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
But also, the
nmi_cookieis still needed for the case where the NMI arrives beforeinfo->cookiegets cleared by early entry-from-user.So how about we clear cookie (and set
nr_entriesto -1) atI think we could set
nr_entriesto 0 instead of -1?Quoted 4 lines
return-to-user, after we've done the work loop and have interrupts disabled until we hit userspace.
Any NMI that hits there will have to cause another entry anyway.
But there's a cookie mismatch:
Quoted 10 lines
// return-to-user: IRQs disabled
<NMI>
current->unwind_info.cookie = 0x1234
</NMI>
unwind_exit_to_user_mode()
current->unwind_info.cookie = 0
IRET
<IRQ>
task_work()
callback(@cookie=WRONG)Though, assuming we're keeping the unwind_work struct, there's a simpler alternative to nmi_cookie: store the cookie in the unwind_work. Then the task work can just use that instead of current->unwind_info.cookie.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
In reply to Josh PoimboeufQuoted 8 lines · Josh Poimboeuf
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
cookie = READ_ONCE(current->unwind_info.cookie); do { if (cookie) return cookie; cookie = ctx_to_cookie(cpu, ctr+1); } while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, cookie));Should not the 2nd argument be &zero?
This I suppose
cookie = READ_ONCE(current->unwind_info.cookie);
do {
if (cookie)
return cookie;
} while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie,
ctx_to_cookie(cpu, ctr+1)));Josh Poimboeuf
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 10 lines · Peter Zijlstra
Quoted 8 lines · Josh Poimboeuf
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
cookie = READ_ONCE(current->unwind_info.cookie); do { if (cookie) return cookie; cookie = ctx_to_cookie(cpu, ctr+1); } while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, cookie));Should not the 2nd argument be &zero?
Quoted 8 lines
This I suppose
cookie = READ_ONCE(current->unwind_info.cookie); do { if (cookie) return cookie; } while (!try_cmpxchg64(¤t->unwind_info.cookie, &cookie, ctx_to_cookie(cpu, ctr+1)));
Here's what I have at the moment. unwind_deferred_request() is vastly simplified thanks to your suggestions, no more NMI-specific muck. work->pending is replaced with work->cookie so the task work can use it in case it crossed the cookie-clearing boundary (return-to-user with IRQs disabled).
static inline bool nmi_supported(void)
{
return IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG);
}/*
* Read the task context cookie, initializing it if this is the first request
* since the most recent entry from user.
*/
static u64 get_cookie(void)
{
struct unwind_task_info *info = ¤t->unwind_info;
u64 ctr, cookie, old_cookie = 0;
unsigned int cpu;
cookie = READ_ONCE(info->cookie);
if (cookie)
return cookie;
guard(irqsave)();
cpu = raw_smp_processor_id();
ctr = __this_cpu_read(unwind_ctx_ctr);
cookie = ctx_to_cookie(cpu, ++ctr);
/* Make sure the cookie is non-zero. */
if (!cookie)
cookie = ctx_to_cookie(cpu, ++ctr);
/* Write the task cookie unless an NMI just now swooped in to do so. */
if (!try_cmpxchg64(&info->cookie, &old_cookie, cookie))
return old_cookie;
/* Success, update the context counter. */
__this_cpu_write(unwind_ctx_ctr, ctr);
return cookie;
}/*
* Schedule a user space unwind to be done in task work before exiting the
* kernel.
*
* The returned cookie output is a unique identifer for the current task entry
* context. Its value will also be passed to the callback function. It can be
* used to stitch kernel and user stack traces together in post-processing. *
* It's valid to call this function multiple times for the same @work within
* the same task entry context. Each call will return the same cookie. If the
* callback is already pending, an error will be returned along with the
* cookie. If the callback is not pending because it has already been
* previously called for the same entry context, it will be called again with
* the same stack trace and cookie.
*
* Thus are three possible return scenarios:
*
* * return != 0, *cookie == 0: the operation failed, no pending callback.
*
* * return != 0, *cookie != 0: the callback is already pending. The cookie
* can still be used to correlate with the pending callback.
*
* * return == 0, *cookie != 0: the callback queued successfully. The
* callback is guaranteed to be called with the given cookie.
*/
int unwind_deferred_request(struct unwind_work *work, u64 *cookie)
{
enum task_work_notify_mode mode = in_nmi() ? TWA_NMI_CURRENT : TWA_RESUME;
u64 zero = 0;
int ret;
*cookie = 0;
if (!current->mm || !user_mode(task_pt_regs(current)))
return -EINVAL;
if (!nmi_supported() && in_nmi())
return -EINVAL;
/* Return a valid @cookie even if the callback is already pending. */
*cookie = READ_ONCE(work->cookie);
if (*cookie)
return -EEXIST;
*cookie = get_cookie();
/* Claim the work unless an NMI just now swooped in to do so. */
if (!try_cmpxchg64(&work->cookie, &zero, *cookie))
return -EEXIST;
/* The work has been claimed, now schedule it. */
ret = task_work_add(current, &work->work, mode);
if (WARN_ON_ONCE(ret)) {
WRITE_ONCE(work->cookie, 0);
return ret;
}
return 0;
}Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 5 lines · Josh Poimboeuf
Here's what I have at the moment.
unwind_deferred_request()is vastly simplified thanks to your suggestions, no more NMI-specific muck.work->pendingis replaced withwork->cookieso the task work can use it in case it crossed the cookie-clearing boundary (return-to-user with IRQs disabled).
That looks a lot saner, thanks! I'll be waiting for the next cycle, not sure I can track all the changes in me head.
Peter Zijlstra
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
In reply to Josh PoimboeufQuoted 12 lines · Josh Poimboeuf
kernel/unwind/deferred.c +12
-0+static int unwind_deferred_request_nmi (struct unwind_work *work,u64 *cookie) +{ +struct unwind_task_info *info = ¤t->unwind_info; +bool inited_cookie = false; +int ret; + + *cookie = info->cookie; +if (!*cookie) { +/* +* This is the first unwind request since the most recent entry +* from user. Initialize the task cookie. +*
kernel/unwind/deferred.c static void unwind_deferred_task_work(struct callback_head *head) · line 149
Quoted 10 lines
kernel/unwind/deferred.c +10
-0+* Don't write to info->cookie directly, otherwise it may get +* cleared if the NMI occurred in the kernel during early entry +* or late exit before the task work gets to run. Instead, use +* info->nmi_cookie which gets synced later by get_cookie(). +*/ +if (!info->nmi_cookie) { +u64 cpu =raw_smp_processor_id (); +u64 ctx_ctr; + + ctx_ctr =__this_cpu_inc_return (unwind_ctx_ctr);
__this_cpu_inc_return() is *NOT* NMI safe IIRC.
Josh Poimboeuf
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 17 lines · Peter Zijlstra
Quoted 16 lines · Josh Poimboeuf
kernel/unwind/deferred.c +16
-0+static int unwind_deferred_request_nmi (struct unwind_work *work,u64 *cookie) +{ +struct unwind_task_info *info = ¤t->unwind_info; +bool inited_cookie = false; +int ret; + + *cookie = info->cookie; +if (!*cookie) { +/* +* This is the first unwind request since the most recent entry +* from user. Initialize the task cookie. +* +* Don't write to info->cookie directly, otherwise it may get +* cleared if the NMI occurred in the kernel during early entry +* or late exit before the task work gets to run. Instead, use +* info->nmi_cookie which gets synced later by get_cookie().
Quoted 8 lines
kernel/unwind/deferred.c static void unwind_deferred_task_work(struct callback_head *head) · line 149
Quoted 6 lines
kernel/unwind/deferred.c +6
-0+*/ +if (!info->nmi_cookie) { +u64 cpu =raw_smp_processor_id (); +u64 ctx_ctr; + + ctx_ctr =__this_cpu_inc_return (unwind_ctx_ctr);
__this_cpu_inc_return()is *NOT* NMI safe IIRC.
Hm, I guess I was only looking at x86.
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 30/39] unwind_user/deferred: Make unwind deferral requests NMI-safe
Quoted 27 lines · Josh Poimboeuf
Quoted 17 lines · Peter Zijlstra
Quoted 16 lines · Josh Poimboeuf
kernel/unwind/deferred.c +16
-0+static int unwind_deferred_request_nmi (struct unwind_work *work,u64 *cookie) +{ +struct unwind_task_info *info = ¤t->unwind_info; +bool inited_cookie = false; +int ret; + + *cookie = info->cookie; +if (!*cookie) { +/* +* This is the first unwind request since the most recent entry +* from user. Initialize the task cookie. +* +* Don't write to info->cookie directly, otherwise it may get +* cleared if the NMI occurred in the kernel during early entry +* or late exit before the task work gets to run. Instead, use +* info->nmi_cookie which gets synced later by get_cookie(). Quoted 8 lines
kernel/unwind/deferred.c static void unwind_deferred_task_work(struct callback_head *head) · line 149
Quoted 6 lines
kernel/unwind/deferred.c +6
-0+*/ +if (!info->nmi_cookie) { +u64 cpu =raw_smp_processor_id (); +u64 ctx_ctr; + + ctx_ctr =__this_cpu_inc_return (unwind_ctx_ctr);
__this_cpu_inc_return()is *NOT* NMI safe IIRC.
Quoted 1 line
Hm, I guess I was only looking at x86.
:-), right so x86 is rather special here, the various RISC platforms are what you should be looking at, eg. arm64.
Josh Poimboeuf
maintainerPATCH v4 31/39 perf: Remove get_perf_callchain() 'init_nr' argument
In reply to Josh PoimboeufThe '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
@@ -1589,7 +1589,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
@@ -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
@@ -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
@@ -7801,7 +7801,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.48.1
Josh Poimboeuf
maintainerPATCH v4 32/39 perf: Remove get_perf_callchain() 'crosstask' argument
In reply to Josh Poimboeufget_perf_callchain() doesn't support cross-task unwinding, so it doesn't make much sense to have 'crosstask' as an argument.
--- 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
@@ -1590,7 +1590,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark); +u32 max_stack,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);
kernel/bpf/stackmap.c +4 -8
@@ -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
@@ -217,7 +217,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entrstruct 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
@@ -7793,16 +7793,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.48.1
Re: [PATCH v4 32/39] perf: Remove get_perf_callchain() 'crosstask' argument
Quoted 41 lines · Josh Poimboeuf
get_perf_callchain()doesn't support cross-task unwinding, so it doesn't make much sense to have 'crosstask' as an argument.Acked-by Namhyung Kim <[email protected]>Signed-off-by Josh Poimboeuf <[email protected]>--- 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@@ -1590,7 +1590,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool crosstask,bool add_mark); +u32 max_stack,bool add_mark);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);kernel/bpf/stackmap.c +1
-2@@ -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 */
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 433
Quoted 9 lines
kernel/bpf/stackmap.c +2
-4@@ -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) {
crosstask stack trace is supported for kernel stack traces (see get_callchain_entry_for_task() call), so this is breaking that case
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 451
Quoted 10 lines
kernel/bpf/stackmap.c +1
-2err = -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);
nit: missing space
Quoted 7 lines
kernel/bpf/stackmap.c +0
-0if (unlikely (!trace) || trace->nr < skip) {if (may_fault)kernel/events/callchain.c +0
-0
[...]
Josh Poimboeuf
maintainerRe: [PATCH v4 32/39] perf: Remove get_perf_callchain() 'crosstask' argument
Quoted 5 lines · Andrii Nakryiko
Quoted 4 lines · Josh Poimboeuf
kernel/bpf/stackmap.c +0
-0@@ -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;
Quoted 9 lines
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 433
Quoted 6 lines
kernel/bpf/stackmap.c +2
-4-/* 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) {crosstask stack trace is supported for kernel stack traces (see
get_callchain_entry_for_task()call), so this is breaking that case
Oh I see, thanks.
BTW, that seems dubious, does it do anything to ensure the task isn't
running? Otherwise the unwind is going to be a wild ride.
Signature · 1 line
Josh
Re: [PATCH v4 32/39] perf: Remove get_perf_callchain() 'crosstask' argument
Quoted 17 lines · Josh Poimboeuf
Quoted 5 lines · Andrii Nakryiko
Quoted 4 lines · Josh Poimboeuf
kernel/bpf/stackmap.c +0
-0@@ -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;Quoted 9 lines
kernel/bpf/stackmap.c static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, · line 433
Quoted 6 lines
kernel/bpf/stackmap.c +2
-4-/* 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) {crosstask stack trace is supported for kernel stack traces (see
get_callchain_entry_for_task()call), so this is breaking that case
Quoted 4 lines
Oh I see, thanks.
BTW, that seems dubious, does it do anything to ensure the task isn't
running? Otherwise the unwind is going to be a wild ride.
Yeah, I think it's very speculative and doesn't pause the task in any way (just makes sure it doesn't go away). We just rely on stack_trace_save_tsk() -> arch_stack_walk(), which just optimistically tries to unwind, it seems.
It's still useful and if the user is prepared to handle a potentially garbage stack trace, why not. People do similar thing for user space stack trace (with custom BPF code), and it's very useful (even if not "reliable" by any means).
Quoted 3 lines
--
Josh
Josh Poimboeuf
maintainerPATCH v4 33/39 perf: Simplify get_perf_callchain() user logic
In reply to Josh PoimboeufSimplify the get_perf_callchain() user logic a bit. task_pt_regs() should never be NULL.
--- kernel/events/callchain.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-)
kernel/events/callchain.c +9 -11
@@ -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.48.1
Josh Poimboeuf
maintainerPATCH v4 34/39 perf: Skip user unwind if !current->mm
In reply to Josh PoimboeufIf the task doesn't have any memory, there's no stack to unwind.
--- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/events/core.c +1 -1
@@ -7792,7 +7792,7 @@ struct perf_callchain_entry * perf_callchain(struct perf_event *event, struct pt_regs *regs) { bool kernel = !event->attr.exclude_callchain_kernel; -bool user = !event->attr.exclude_callchain_user; +bool user = !event->attr.exclude_callchain_user && current->mm;const u32 max_stack = event->attr.sample_max_stack;struct perf_callchain_entry *callchain;
Signature · 1 line
2.48.1
Peter Zijlstra
maintainerRe: [PATCH v4 34/39] perf: Skip user unwind if !current->mm
Quoted 12 lines · Josh Poimboeuf
If the task doesn't have any memory, there's no stack to unwind.
Signed-off-by Josh Poimboeuf <[email protected]>--- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)kernel/events/core.c +0
-0
kernel/events/core.c struct perf_callchain_entry * · line 7795
Quoted 5 lines
kernel/events/core.c +1
-1@@ -7792,7 +7792,7 @@ struct perf_callchain_entry * perf_callchain(struct perf_event *event, struct pt_regs *regs) { bool kernel = !event->attr.exclude_callchain_kernel; -bool user = !event->attr.exclude_callchain_user; +bool user = !event->attr.exclude_callchain_user && current->mm;
What about things like io_uring helpers, don't they keep ->mm but are never in userspace?
Josh Poimboeuf
maintainerRe: [PATCH v4 34/39] perf: Skip user unwind if !current->mm
Quoted 8 lines · Peter Zijlstra
Quoted 7 lines · Josh Poimboeuf
If the task doesn't have any memory, there's no stack to unwind.
Signed-off-by Josh Poimboeuf <[email protected]>--- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Quoted 13 lines
kernel/events/core.c struct perf_callchain_entry * · line 7795
Quoted 10 lines
kernel/events/core.c +1
-1@@ -7792,7 +7792,7 @@ struct perf_callchain_entry * perf_callchain(struct perf_event *event, struct pt_regs *regs) { bool kernel = !event->attr.exclude_callchain_kernel; -bool user = !event->attr.exclude_callchain_user; +bool user = !event->attr.exclude_callchain_user && current->mm;What about things like
io_uringhelpers, don't they keep ->mm but are never in userspace?
Hm, that's news to me. At least this patch doesn't make things worse in that regard.
IIRC, user_regs(task_pt_regs(current)) returns false for kthreads because the regs area is cleared. We could add in a user_regs() check?
Signature · 1 line
Josh
Peter Zijlstra
maintainerRe: [PATCH v4 34/39] perf: Skip user unwind if !current->mm
Quoted 23 lines · Josh Poimboeuf
Quoted 8 lines · Peter Zijlstra
Quoted 7 lines · Josh Poimboeuf
If the task doesn't have any memory, there's no stack to unwind.
Signed-off-by Josh Poimboeuf <[email protected]>--- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)Quoted 13 lines
kernel/events/core.c struct perf_callchain_entry * · line 7795
Quoted 10 lines
kernel/events/core.c +1
-1@@ -7792,7 +7792,7 @@ struct perf_callchain_entry * perf_callchain(struct perf_event *event, struct pt_regs *regs) { bool kernel = !event->attr.exclude_callchain_kernel; -bool user = !event->attr.exclude_callchain_user; +bool user = !event->attr.exclude_callchain_user && current->mm;What about things like
io_uringhelpers, don't they keep ->mm but are never in userspace?
Quoted 5 lines
Hm, that's news to me. At least this patch doesn't make things worse in that regard.
IIRC, user_regs(task_pt_regs(current)) returns false for kthreads because the regs area is cleared. We could add in a
user_regs()check?
Possibly, I've not yet dug into the io_worker thing in much detail yet.
Josh Poimboeuf
maintainerPATCH v4 35/39 perf: Support deferred user callchains
In reply to Josh PoimboeufUse the new unwind_deferred_request() interface (if available) to defer unwinds to task context. This allows the use of .sframe (if available) and also prevents duplicate userspace unwinds.
--- arch/Kconfig | 3 + include/linux/perf_event.h | 13 +++- include/uapi/linux/perf_event.h | 19 ++++- kernel/bpf/stackmap.c | 6 +- kernel/events/callchain.c | 11 ++- kernel/events/core.c | 103 +++++++++++++++++++++++++- tools/include/uapi/linux/perf_event.h | 19 ++++- 7 files changed, 166 insertions(+), 8 deletions(-)
arch/Kconfig +3 -0
@@ -472,6 +472,9 @@ config SFRAME_VALIDATION If unsure, say N. +config HAVE_PERF_CALLCHAIN_DEFERRED + bool + config HAVE_PERF_REGS bool help
include/linux/perf_event.h +12 -1
@@ -62,6 +62,7 @@ struct perf_guest_info_callbacks {#include <linux/security.h> #include <linux/static_call.h> #include <linux/lockdep.h> +#include <linux/unwind_deferred.h> #include <asm/local.h> struct perf_callchain_entry {
@@ -833,6 +834,10 @@ struct perf_event {unsigned int pending_work;struct rcuwait pending_work_wait; +struct unwind_work pending_unwind_work; +struct rcuwait pending_unwind_wait; +unsigned int pending_unwind_callback; +atomic_t event_limit;/* address range filters */
@@ -1590,12 +1595,18 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pextern void perf_callchain_kernel (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs);extern struct perf_callchain_entry *get_perf_callchain (struct pt_regs *regs,bool kernel,bool user, -u32 max_stack,bool add_mark); +u32 max_stack,bool add_mark,bool defer_user);extern int get_callchain_buffers (int max_stack);extern void put_callchain_buffers (void );extern struct perf_callchain_entry *get_callchain_entry (int *rctx);extern void put_callchain_entry (int rctx); +#ifdef CONFIG_HAVE_PERF_CALLCHAIN_DEFERRED +extern void perf_callchain_user_deferred (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs); +#else +static inline void perf_callchain_user_deferred (struct perf_callchain_entry_ctx *entry,struct pt_regs *regs) {} +#endif +extern int sysctl_perf_event_max_stack;extern int sysctl_perf_event_max_contexts_per_stack;
include/uapi/linux/perf_event.h +18 -1
@@ -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 */
@@ -1226,6 +1227,21 @@ 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. +* +* struct { +* struct perf_event_header header; +* u64 nr; +* u64 ips[nr]; +* struct sample_id sample_id; +* }; +*/ +PERF_RECORD_CALLCHAIN_DEFERRED =22 , + PERF_RECORD_MAX, /* non-ABI */ };
@@ -1256,6 +1272,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
@@ -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
@@ -217,7 +217,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entrstruct 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 +102 -1
144 lines not on this page · raw message
tools/include/uapi/linux/perf_event.h +18 -1
@@ -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 */
@@ -1226,6 +1227,21 @@ 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. +* +* struct { +* struct perf_event_header header; +* u64 nr; +* u64 ips[nr]; +* struct sample_id sample_id; +* }; +*/ +PERF_RECORD_CALLCHAIN_DEFERRED =22 , + PERF_RECORD_MAX, /* non-ABI */ };
@@ -1256,6 +1272,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.48.1
Josh Poimboeuf
maintainerPATCH v4 36/39 perf tools: Minimal CALLCHAIN_DEFERRED support
In reply to Josh PoimboeufFrom: 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
@@ -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
@@ -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
@@ -2817,6 +2817,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 ;
@@ -2947,6 +2959,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
@@ -2087,6 +2087,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
@@ -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
@@ -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
@@ -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;
@@ -1337,6 +1351,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
@@ -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
@@ -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.48.1
Josh Poimboeuf
maintainerPATCH v4 37/39 perf record: Enable defer_callchain for user callchains
In reply to Josh PoimboeufFrom: 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 | 24 ++++++++++++++++++++++++ tools/perf/util/evsel.h | 1 + 2 files changed, 25 insertions(+)
tools/perf/util/evsel.c +24 -0
@@ -998,6 +998,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 ;
@@ -2038,6 +2046,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.inherit_sample_read && evsel->core.attr.inherit && (evsel->core.attr.sample_type &PERF_SAMPLE_READ )) evsel->core.attr.inherit =0 ;
@@ -2244,6 +2254,15 @@ static bool evsel__detect_missing_features(struct evsel *evsel)/* Please add new feature detection here. */ + attr.defer_callchain = true; + attr.sample_type =PERF_SAMPLE_CALLCHAIN ; +if (has_attr_feature (&attr,/*flags=*/ 0 )) + goto found; + perf_missing_features.defer_callchain = true; +pr_debug2 ("switching off deferred callchain support\n" ); + attr.defer_callchain = false; + attr.sample_type =0 ; + attr.inherit = true; attr.sample_type =PERF_SAMPLE_READ ;if (has_attr_feature (&attr,/*flags=*/ 0 ))
@@ -2355,6 +2374,11 @@ static bool evsel__detect_missing_features(struct evsel *evsel) errno = old_errno; check: +if (evsel->core.attr.defer_callchain && + evsel->core.attr.sample_type &PERF_SAMPLE_CALLCHAIN && + perf_missing_features.defer_callchain) +return true; +if (evsel->core.attr.inherit && (evsel->core.attr.sample_type &PERF_SAMPLE_READ ) && perf_missing_features.inherit_sample_read)
tools/perf/util/evsel.h +1 -0
@@ -206,6 +206,7 @@ struct perf_missing_features {bool read_lost;bool branch_counters;bool inherit_sample_read; +bool defer_callchain; };extern struct perf_missing_features perf_missing_features;
Signature · 1 line
2.48.1
Josh Poimboeuf
maintainerPATCH v4 38/39 perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED
In reply to Josh PoimboeufFrom: 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
@@ -2541,6 +2541,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;
@@ -4326,6 +4413,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;
@@ -4352,6 +4440,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.48.1
Josh Poimboeuf
maintainerPATCH v4 39/39 perf tools: Merge deferred user callchains
In reply to Josh PoimboeufFrom: 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
@@ -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
@@ -4032,6 +4032,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;
@@ -4185,6 +4186,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() };
@@ -4440,7 +4443,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
@@ -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
@@ -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
@@ -82,6 +82,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
@@ -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
@@ -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
@@ -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.48.1
Josh Poimboeuf
maintainerQuoted 7 lines · Josh Poimboeuf
For testing with user space, here are the latest binutils fixes:
1785837a2570("ld: fix PR/32297")
938fb512184d("ld: fix wrong SFrame info for lazy IBT PLT")47c88752f9ad("ld: generate SFrame stack trace info for .plt.got")An out-of-tree glibc patch is also needed -- will attach in a reply.
Latest out-of-tree glibc patch below:
elf/dl-load.c +29 -0
@@ -29,6 +29,7 @@#include <bits/wordsize.h> #include <sys/mman.h> #include <sys/param.h> +#include <sys/prctl.h> #include <sys/stat.h> #include <sys/types.h> #include <gnu/lib-names.h>
@@ -87,6 +88,9 @@ struct filebuf#define STRING (x) __STRING (x) +#ifndef PT_GNU_SFRAME +#define PT_GNU_SFRAME 0x6474e554 +#endif /* This is the decomposed LD_LIBRARY_PATH search path. */ struct r_search_path_struct __rtld_env_path_list attribute_relro;
@@ -1186,6 +1190,11 @@ _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_start = ph->p_vaddr; + l->l_sframe_end = ph->p_vaddr + ph->p_memsz; +break ; }if (__glibc_unlikely (nloadcmds ==0 ))
@@ -1236,6 +1245,26 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, l->l_map_start = l->l_map_end =0 ; goto lose; } + +#define PR_ADD_SFRAME 77 +if (l->l_sframe_start !=0 ) + { + l->l_sframe_start += l->l_addr; + l->l_sframe_end += l->l_addr; + +for (size_t i =0 ; i < nloadcmds; i++) + { +struct loadcmd *c = &loadcmds[i]; + +if (c->prot &PROT_EXEC ) + { +ElfW (Addr) text_start = l->l_addr + c->mapstart; +ElfW (Addr) text_end = l->l_addr + c->mapend; + +__prctl (PR_ADD_SFRAME , l->l_sframe_start, l->l_sframe_end, text_start, text_end); + } + } + } }if (l->l_ld !=NULL )
elf/dl-unmap-segments.h +6 -0
@@ -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 78 + static __always_inline void_dl_unmap_segments (struct link_map *l) { +if (l->l_sframe_start !=0 ) +__prctl (PR_REMOVE_SFRAME , l->l_sframe_start,NULL ,NULL ,NULL ); +__munmap ((void *) l->l_map_start, l->l_map_end - l->l_map_start); }
elf/setup-vdso.h +22 -0
@@ -16,6 +16,11 @@ License along with the GNU C Library; if not, see <https://www.gnu.org/licenses/>. */ +#include <sys/prctl.h> +#ifndef PT_GNU_SFRAME +#define PT_GNU_SFRAME 0x6474e554 +#endif +static inline void __attribute__ ((always_inline))setup_vdso (struct link_map *main_map __attribute__ ((unused)),struct link_map ***first_preload __attribute__ ((unused)))
@@ -52,6 +57,14 @@ setup_vdso (struct link_map *main_map __attribute__ ((unused)),if (ph->p_vaddr + ph->p_memsz >= l->l_map_end) l->l_map_end = ph->p_vaddr + ph->p_memsz; } +else if (ph->p_type ==PT_GNU_SFRAME ) + { +if (! l->l_sframe_start) + { + l->l_sframe_start = ph->p_vaddr; + l->l_sframe_end = ph->p_vaddr + ph->p_memsz; + } + }else /* There must be no TLS segment. */ assert (ph->p_type !=PT_TLS );
@@ -74,6 +87,15 @@ setup_vdso (struct link_map *main_map __attribute__ ((unused)), l->l_local_scope[0 ]->r_nlist =1 ; l->l_local_scope[0 ]->r_list = &l->l_real; +#define PR_ADD_SFRAME 77 +if (l->l_sframe_start !=0 ) + { + l->l_sframe_start += l->l_addr; + l->l_sframe_end += l->l_addr; + +__prctl (PR_ADD_SFRAME , l->l_sframe_start, l->l_sframe_end, l->l_addr, l->l_map_end); + } +/* Now that we have the info handy, use the DSO image's soname so this object can be looked up by name. */ if (l->l_info[DT_SONAME ] !=NULL )
include/link.h +3 -0
@@ -345,6 +345,9 @@ struct link_mapElfW (Addr) l_relro_addr;size_t l_relro_size; +ElfW (Addr) l_sframe_start; +ElfW (Addr) l_sframe_end; +unsigned long long int l_serial; };
Steven Rostedt
maintainerQuoted 11 lines · On Tue, 21 Jan 2025 18:30:52 -0800 Josh Poimboeuf <[email protected]> wrote:
The interface is similar to {task,irq}_work. The caller owns an
unwind_workstruct:struct unwind_work { struct callback_head work; unwind_callback_t func; int pending;};
For perf, struct
unwind_workis embedded in structperf_event. For ftrace maybe it would live intask_struct?
Hmm, this is going to be difficult, as I don't want to add more to a task struct as it's already too bloated as is. I'll have to think about this a bit.
-- Steve