MAINTAINERS | 5 + arch/Kconfig | 35 + arch/s390/Kconfig | 1 + arch/s390/include/asm/ptrace.h | 6 + arch/s390/include/asm/unwind_user.h | 71 + arch/s390/include/asm/unwind_user_eh_frame.h | 24 + arch/x86/Kconfig | 1 + arch/x86/include/asm/mmu.h | 2 +- arch/x86/include/asm/uaccess.h | 39 +- arch/x86/include/asm/unwind_user.h | 77 +- arch/x86/include/asm/unwind_user_eh_frame.h | 153 ++ fs/binfmt_elf.c | 49 +- include/asm-generic/Kbuild | 1 + include/asm-generic/unwind_user_eh_frame.h | 84 + include/linux/eh_frame.h | 104 + include/linux/mm_types.h | 3 + include/linux/unwind_user.h | 20 + include/linux/unwind_user_eh_frame_types.h | 41 + include/linux/unwind_user_types.h | 51 +- include/uapi/linux/eh_frame.h | 14 + include/uapi/linux/prctl.h | 4 + kernel/fork.c | 10 + kernel/sys.c | 11 + kernel/unwind/Makefile | 3 +- kernel/unwind/eh_frame.c | 1771 ++++++++++++++++++ kernel/unwind/eh_frame.h | 83 + kernel/unwind/eh_frame_debug.h | 71 + kernel/unwind/user.c | 142 +- mm/init-mm.c | 2 + mm/mmap.c | 5 + 30 files changed, 2842 insertions(+), 41 deletions(-) create mode 100644 arch/s390/include/asm/unwind_user.h create mode 100644 arch/s390/include/asm/unwind_user_eh_frame.h create mode 100644 arch/x86/include/asm/unwind_user_eh_frame.h create mode 100644 include/asm-generic/unwind_user_eh_frame.h create mode 100644 include/linux/eh_frame.h create mode 100644 include/linux/unwind_user_eh_frame_types.h create mode 100644 include/uapi/linux/eh_frame.h create mode 100644 kernel/unwind/eh_frame.c create mode 100644 kernel/unwind/eh_frame.h create mode 100644 kernel/unwind/eh_frame_debug.h
This series adds support for parsing DWARF Call Frame Information (CFI)
from the .eh_frame_hdr and .eh_frame sections of user space ELF files.
The code is based on the deferred unwind user work originally done for
SFrame by Josh, Steven, and myself:
v4 : https://lore.kernel.org/all/cover.1737511963.git.jpoimboe@kernel.org/
v10: https://lore.kernel.org/all/20250827201548.448472904@kernel.org/
v16: https://lore.kernel.org/all/20260521142546.3908498-1-jremus@linux.ibm.com/
The goal is to make user space stack traces available in-kernel without
requiring frame pointers and without copying large parts of the user
stack for later processing.
Today, reliable user stack traces from the kernel generally requires
frame pointers. Otherwise, profilers such as perf have to copy large
amounts of user space stack into the kernel ring buffer and process it
later. Frame pointers are simple and robust, but enabling them for
all executables and libraries has a performance cost.
Another issue is that the frame layout can vary between compilers and
architectures, and on architectures such as s390 there is no defined
frame layout which allows reliable frame-pointer based stack tracing.
The only way to perform user space profiling on there architectures is
to copy the user space into the kernel buffer.
The .eh_frame section is already emitted by most toolchains on most
architectures unless explicitly disabled. It contains DWARF CFI
describing how to recover the caller state at any point in a function.
The .eh_frame_hdr section provides a binary search table for looking
up the Frame Description Entry (FDE) for a given instruction pointer
(IP).
Because the .eh_frame_hdr and .eh_frame sections live in the ELF file,
they need to be faulted in when used. This means that walking the user
space stack requires being in a faultable context. As profilers like
perf request a stack trace in interrupt or NMI context, the walking
cannot be done when requested. This series reuses the deferred unwind
user framework, that performed the actual user stack trace is later in
a faultable context, before the task returns to user space.
This series implements .eh_frame[_hdr] support for the deferred unwind
user code and enables it for x86-64 and s390.
It intentionally not implement a complete DWARF unwinder. It evaluates
only the subset of DWARF CFI needed for stack tracing:
- Call Frame Address (CFA): Using rule from DWARF CFI.
- Stack pointer (SP): Using an implicit rule based on the CFA
definition (SP = CFA for most architectures).
- Frame pointer (FP): Using rule from DWARF CFI.
- Return address (RA): Using rule from DWARF CFI.
Unsupported CFI instructions, unsupported expressions, invalid data, or
user memory faults stop the stack tracing safely and results in a partial
stack trace.
This series applies on top of v7.2 tag:
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git v7.2
The to be stack-traced user space executables and libraries need to
contain .eh_frame_hdr and .eh_frame sections as well as a GNU_EH_FRAME
PHDR.
Namhyung Kim's related perf tools deferred callchain support can be used
for testing, for example:
perf record --call-graph fp,defer ...
perf report
perf script
Why .eh_frame?
This series is not meant to replace or undermine the SFrame work.
SFrame remains the simpler and more purpose-built format for user stack
tracing. The motivation for .eh_frame support is pragmatic: .eh_frame
is already widely deployed today.
- Availability and maturity: .eh_frame is already present in most ELF
binaries for C++ exception handling. It has been used in production
for decades for exception handling and debugger stack unwinding.
- Toolchain support: .eh_frame is supported across all major compilers
and architectures today, whereas .sframe adoption is still emerging.
- Size: .sframe would be added in addition to existing .eh_frame[_hdr]
rather than replacing it, increasing the ELF file size. [1]
Addressing historical DWARF concerns:
Using DWARF for kernel unwinding has a bad history. Previous attempts
were complex, fragile, slow, and hard to maintain. Hand-written
assembly and the complexity of the DWARF state machine were among the
reasons the simpler ORC kernel unwind format was developed. [2,3,4]
However, this implementation for user space stack tracing differs from
those problematic kernel unwinding attempts:
- It stack traces user space, not kernel.
- It runs in a deferred, faultable context, not in NMI, interrupt, or
oops context.
- It may return partial stack traces. Bad CFI, unsupported operations,
invalid user memory, or faults are allowed to terminate the unwind.
- It implements only the CFI subset needed for stack tracing, not a
general DWARF unwinder.
- It does not include a general-purpose DWARF expression VM. Expression
size is bounded. Only a small number of pattern-matched expressions
is supported (e.g. DRAP and PLT expressions on x86). Unsupported
expressions cause stack tracing to fail safely.
- All user memory access uses [unsafe_]get_user() with proper bounds
checking and fault handling.
- Corruption detection with automatic section removal on invalid
.eh_frame prevents further stack tracing attempts.
Limitations and future work:
- CIE version 1 support only and no DWARF64 support, as I have not run
into either during my testing.
- Signal frames are not handled yet. An architecture hook could support
unwinding through FDEs whose CIE augmentation contains 'S' (signal
frame), similar to Glibc's SFrame backtrace() support. See also my
"[RFC PATCH v1 0/5] s390: Signal frame user space unwinding". [5]
- x86-32, x86-x32, and 32-bit compat mode support not implemented yet.
- CIE caching would be useful. Reading an FDE requires reading its
referenced CIE first to obtain the FDE encoding. Most .eh_frame
sections have only a very small number of CIEs, often one default
CIE shared by most FDEs and possibly one signal frame CIE. Caching
the last CIE per section, together with the initial CFA, FP, and RA
rules, would avoid repeated CIE parsing and initial CFI instruction
processing.
[1]: https://lore.kernel.org/all/CAN30aBFVDxeoXApn_g_Hw0Ayhi4V=m7CcX8UDO6ZDTi6xA-3Pg@mail.gmail.com/
[2]: https://lwn.net/Articles/727553/
[3]: https://lkml.org/lkml/2012/2/10/356
[4]: https://lkml.org/lkml/2017/5/20/165
[5]: https://lore.kernel.org/all/20260127153331.2902504-1-jremus@linux.ibm.com/
Patches 1-6 add base functionality to unwind user to support .eh_frame-
based (or .sframe-based) unwinding. Patches originate from my latest
.sframe patch series.
Patches 7-10 add the basic infrastructure for reading .eh_frame_hdr and
.eh_frame sections and storing them in a per-mm maple tree.
Patches 11-14 wire up the eh_frame infrastructure to the unwind user
framework and add error handling and debugging support.
Patch 15 duplicates registered .eh_frame_hdr section data on clone/fork.
Patch 16 adds an experimental linear .eh_frame search fallback, for the
rare case, that .eh_frame_hdr does not contain a binary search table.
Patch 17 improves .eh_frame DWARF CFI instruction processing.
Patch 18 enables architectures to implement selected DWARF expressions
in CFI instructions.
Patches 19-22 enable .eh_frame unwinding on x86-64 with minimal DWARF
expression support for DRAP and PLT expressions.
Patches 23-24 enable .eh_frame unwinding on s390.
Patch 25 adds a prctl() interface for (un)registering .eh_frame_hdr
sections for shared libraries. I will send a related test-patch for
Glibc separately.
Regards,
Jens
Jens Remus (24):
unwind_user: Add generic and arch-specific headers to MAINTAINERS
unwind_user: Stop when reaching an outermost frame
unwind_user: Enable archs that pass RA in a register
unwind_user: Flexible FP/RA recovery rules
unwind_user: Flexible CFA recovery rules
unwind_user: Enable archs that define CFA = SP_callsite + offset
unwind_user/eh_frame: Add support for reading .eh_frame_hdr section
unwind_user/eh_frame: Store .eh_frame_hdr section data in per-mm maple
tree
unwind_user/eh_frame: Add support for reading .eh_frame section
unwind_user/eh_frame: Detect .eh_frame_hdr sections in executables
unwind_user/eh_frame: Wire up unwind_user to eh_frame
unwind_user/eh_frame: Remove .eh_frame[_hdr] section on detected
corruption
unwind_user/eh_frame: Show file name in debug output
unwind_user/eh_frame: Add .eh_frame[_hdr] validation option
unwind_user/eh_frame: Duplicate registered .eh_frame[_hdr] section
data on clone/fork
unwind_user/eh_frame: Add linear .eh_frame search fallback
unwind_user/eh_frame: Ignore DW_CFA_GNU_args_size
unwind_user/eh_frame: Add support for DWARF expressions
unwind_user/eh_frame/x86: Enable eh_frame unwinding on x86
unwind_user/eh_frame/x86: Handle PLT expressions
unwind_user/eh_frame/x86: Handle DRAP expressions
s390/ptrace: Provide frame_pointer()
unwind_user/eh_frame/s390: Enable eh_frame unwinding on s390
unwind_user/eh_frame: Add prctl() interface for (un)registering
.eh_frame_hdr sections
Josh Poimboeuf (1):
x86/uaccess: Add unsafe_copy_from_user() implementation
MAINTAINERS | 5 +
arch/Kconfig | 35 +
arch/s390/Kconfig | 1 +
arch/s390/include/asm/ptrace.h | 6 +
arch/s390/include/asm/unwind_user.h | 71 +
arch/s390/include/asm/unwind_user_eh_frame.h | 24 +
arch/x86/Kconfig | 1 +
arch/x86/include/asm/mmu.h | 2 +-
arch/x86/include/asm/uaccess.h | 39 +-
arch/x86/include/asm/unwind_user.h | 77 +-
arch/x86/include/asm/unwind_user_eh_frame.h | 153 ++
fs/binfmt_elf.c | 49 +-
include/asm-generic/Kbuild | 1 +
include/asm-generic/unwind_user_eh_frame.h | 84 +
include/linux/eh_frame.h | 104 +
include/linux/mm_types.h | 3 +
include/linux/unwind_user.h | 20 +
include/linux/unwind_user_eh_frame_types.h | 41 +
include/linux/unwind_user_types.h | 51 +-
include/uapi/linux/eh_frame.h | 14 +
include/uapi/linux/prctl.h | 4 +
kernel/fork.c | 10 +
kernel/sys.c | 11 +
kernel/unwind/Makefile | 3 +-
kernel/unwind/eh_frame.c | 1771 ++++++++++++++++++
kernel/unwind/eh_frame.h | 83 +
kernel/unwind/eh_frame_debug.h | 71 +
kernel/unwind/user.c | 142 +-
mm/init-mm.c | 2 +
mm/mmap.c | 5 +
30 files changed, 2842 insertions(+), 41 deletions(-)
create mode 100644 arch/s390/include/asm/unwind_user.h
create mode 100644 arch/s390/include/asm/unwind_user_eh_frame.h
create mode 100644 arch/x86/include/asm/unwind_user_eh_frame.h
create mode 100644 include/asm-generic/unwind_user_eh_frame.h
create mode 100644 include/linux/eh_frame.h
create mode 100644 include/linux/unwind_user_eh_frame_types.h
create mode 100644 include/uapi/linux/eh_frame.h
create mode 100644 kernel/unwind/eh_frame.c
create mode 100644 kernel/unwind/eh_frame.h
create mode 100644 kernel/unwind/eh_frame_debug.h
base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
--
2.53.0
On Tue, 18 Aug 2026 16:49:29 +0200
Jens Remus <jremus@linux.ibm.com> wrote:
> This series adds support for parsing DWARF Call Frame Information (CFI)
> from the .eh_frame_hdr and .eh_frame sections of user space ELF files.
Very nice! I don't have time this week to look at it, but I just
applied the series and tried it out with:
perf record --call-graph fp,defer trace-cmd report ~/trace.dat
And did a: perf -D script
And found this:
264990534603 0x19370 [0x78]: PERF_RECORD_CALLCHAIN_DEFERRED(IP, 0x2): 1165/1165: 0x105700000005
... FP chain: nr:10
..... 0: 00007f5feafd362a
..... 1: 00007f5feb249781
..... 2: 00007f5feb2498de
..... 3: 000055935526fbdc
..... 4: 00005593552649e6
..... 5: 000055935523c2d0
..... 6: 000055935523d24c
..... 7: 0000559355227bfe
..... 8: 00007f5feaef9f75
..... 9: 00007ffdaf879d38
... thread: trace-cmd:1165
...... dso: /proc/kcore
trace-cmd 1165 264.990473: 395990 cpu/cycles/P:
ffffffff8477d8d3 check_preemption_disabled+0x13 ([kernel.kallsyms])
ffffffff819aa495 rcu_is_watching+0x15 ([kernel.kallsyms])
ffffffff816dbcff unwind_next_frame+0x45f ([kernel.kallsyms])
ffffffff81642e91 arch_stack_walk+0xa1 ([kernel.kallsyms])
ffffffff81a2e633 stack_trace_save+0x93 ([kernel.kallsyms])
ffffffff82251210 kasan_save_stack+0x30 ([kernel.kallsyms])
ffffffff822541b0 kasan_record_aux_stack+0xb0 ([kernel.kallsyms])
ffffffff819c1e2a __call_rcu_common+0xca ([kernel.kallsyms])
ffffffff821be05f kmem_cache_free+0x2ef ([kernel.kallsyms])
ffffffff8233e4a7 vfs_fstatat+0x57 ([kernel.kallsyms])
ffffffff8233e573 __do_sys_newfstatat+0x83 ([kernel.kallsyms])
ffffffff84772a8e do_syscall_64+0x7e ([kernel.kallsyms])
ffffffff8100012f entry_SYSCALL_64_after_hwframe+0x76 ([kernel.kallsyms])
7f5feafd362a __GI___fstatat64+0xa (/usr/lib/x86_64-linux-gnu/libc.so.6)
7f5feb249781 tep_load_plugins_hook+0xd1 (/usr/local/lib64/libtraceevent.so.1.9.0)
7f5feb2498de tep_load_plugins+0x35 (/usr/local/lib64/libtraceevent.so.1.9.0)
55935526fbdc tcmd_load_plugins+0x85 (/usr/local/bin/trace-cmd)
5593552649e6 tracecmd_alloc_fd+0x2da (/usr/local/bin/trace-cmd)
55935523c2d0 read_trace_header+0x62 (/usr/local/bin/trace-cmd)
55935523d24c trace_report+0x869 (/usr/local/bin/trace-cmd)
559355227bfe main+0x90 (/usr/local/bin/trace-cmd)
7f5feaef9f75 __libc_start_call_main+0x75 (/usr/lib/x86_64-linux-gnu/libc.so.6)
7ffdaf879d38 [unknown] ([unknown])
I injected trace_printk() into the code to make sure it was using the
eh_frame unwinding:
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index 85fc82252af1..585f022bcabe 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -200,6 +200,7 @@ static int unwind_user_next(struct unwind_user_state *state)
case UNWIND_USER_TYPE_EH_FRAME:
switch (unwind_user_next_eh_frame(state)) {
case 0:
+ trace_printk("USE EH_FRAME\n");
return 0;
case -ENOENT:
continue; /* Try next method. */
@@ -208,8 +209,9 @@ static int unwind_user_next(struct unwind_user_state *state)
}
break;
case UNWIND_USER_TYPE_FP:
- if (!unwind_user_next_fp(state))
- return 0;
+ if (!unwind_user_next_fp(state)) {
+ trace_printk("USE FRAME POINTER\n");
+ return 0; }
continue;
default:
WARN_ONCE(1, "Undefined unwind bit %d", bit);
And have this:
# trace-cmd show
[..]
trace-cmd-1136 [001] ..... 239.482498: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482501: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482504: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482507: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482821: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482825: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482828: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482831: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.482834: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483004: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483007: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483010: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483013: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483016: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483230: unwind_user: USE FRAME POINTER
trace-cmd-1136 [001] ..... 239.483234: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483238: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483240: unwind_user: USE EH_FRAME
trace-cmd-1136 [001] ..... 239.483243: unwind_user: USE EH_FRAME
[..]
Note, the compile had one warning (with all patches applied):
vmlinux.o: warning: objtool: eh_frame_find+0x270c: call to eh_frame_do_expression.isra.0() with UACCESS enabled
I'll try to get time when I get back from my travels next week to look
at each of the patches.
I also do not think this makes sframe obsolete. I believe there's holes
with eh_frame and some may not like the complexity of it. But this
gives us an honest look at what options are available to us.
Thanks for doing this!
-- Steve
Hello Steven!
On 8/18/2026 7:21 PM, Steven Rostedt wrote:
> On Tue, 18 Aug 2026 16:49:29 +0200
> Jens Remus <jremus@linux.ibm.com> wrote:
>
>> This series adds support for parsing DWARF Call Frame Information (CFI)
>> from the .eh_frame_hdr and .eh_frame sections of user space ELF files.
>
> Very nice! I don't have time this week to look at it, but I just
> applied the series and tried it out with:
Thanks!
>
> perf record --call-graph fp,defer trace-cmd report ~/trace.dat
>
> And did a: perf -D script
>
> And found this:
>
> 264990534603 0x19370 [0x78]: PERF_RECORD_CALLCHAIN_DEFERRED(IP, 0x2): 1165/1165: 0x105700000005
> ... FP chain: nr:10
> ..... 0: 00007f5feafd362a
> ..... 1: 00007f5feb249781
> ..... 2: 00007f5feb2498de
> ..... 3: 000055935526fbdc
> ..... 4: 00005593552649e6
> ..... 5: 000055935523c2d0
> ..... 6: 000055935523d24c
> ..... 7: 0000559355227bfe
> ..... 8: 00007f5feaef9f75
> ..... 9: 00007ffdaf879d38
> ... thread: trace-cmd:1165
> ...... dso: /proc/kcore
> trace-cmd 1165 264.990473: 395990 cpu/cycles/P:
> ffffffff8477d8d3 check_preemption_disabled+0x13 ([kernel.kallsyms])
> ffffffff819aa495 rcu_is_watching+0x15 ([kernel.kallsyms])
> ffffffff816dbcff unwind_next_frame+0x45f ([kernel.kallsyms])
> ffffffff81642e91 arch_stack_walk+0xa1 ([kernel.kallsyms])
> ffffffff81a2e633 stack_trace_save+0x93 ([kernel.kallsyms])
> ffffffff82251210 kasan_save_stack+0x30 ([kernel.kallsyms])
> ffffffff822541b0 kasan_record_aux_stack+0xb0 ([kernel.kallsyms])
> ffffffff819c1e2a __call_rcu_common+0xca ([kernel.kallsyms])
> ffffffff821be05f kmem_cache_free+0x2ef ([kernel.kallsyms])
> ffffffff8233e4a7 vfs_fstatat+0x57 ([kernel.kallsyms])
> ffffffff8233e573 __do_sys_newfstatat+0x83 ([kernel.kallsyms])
> ffffffff84772a8e do_syscall_64+0x7e ([kernel.kallsyms])
> ffffffff8100012f entry_SYSCALL_64_after_hwframe+0x76 ([kernel.kallsyms])
> 7f5feafd362a __GI___fstatat64+0xa (/usr/lib/x86_64-linux-gnu/libc.so.6)
> 7f5feb249781 tep_load_plugins_hook+0xd1 (/usr/local/lib64/libtraceevent.so.1.9.0)
> 7f5feb2498de tep_load_plugins+0x35 (/usr/local/lib64/libtraceevent.so.1.9.0)
> 55935526fbdc tcmd_load_plugins+0x85 (/usr/local/bin/trace-cmd)
> 5593552649e6 tracecmd_alloc_fd+0x2da (/usr/local/bin/trace-cmd)
> 55935523c2d0 read_trace_header+0x62 (/usr/local/bin/trace-cmd)
> 55935523d24c trace_report+0x869 (/usr/local/bin/trace-cmd)
> 559355227bfe main+0x90 (/usr/local/bin/trace-cmd)
> 7f5feaef9f75 __libc_start_call_main+0x75 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> 7ffdaf879d38 [unknown] ([unknown])
>
> I injected trace_printk() into the code to make sure it was using the
> eh_frame unwinding:
>
> diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
> index 85fc82252af1..585f022bcabe 100644
> --- a/kernel/unwind/user.c
> +++ b/kernel/unwind/user.c
> @@ -200,6 +200,7 @@ static int unwind_user_next(struct unwind_user_state *state)
> case UNWIND_USER_TYPE_EH_FRAME:
> switch (unwind_user_next_eh_frame(state)) {
> case 0:
> + trace_printk("USE EH_FRAME\n");
> return 0;
> case -ENOENT:
> continue; /* Try next method. */
> @@ -208,8 +209,9 @@ static int unwind_user_next(struct unwind_user_state *state)
> }
> break;
> case UNWIND_USER_TYPE_FP:
> - if (!unwind_user_next_fp(state))
> - return 0;
> + if (!unwind_user_next_fp(state)) {
> + trace_printk("USE FRAME POINTER\n");
> + return 0; }
> continue;
> default:
> WARN_ONCE(1, "Undefined unwind bit %d", bit);
>
> And have this:
>
> # trace-cmd show
> [..]
> trace-cmd-1136 [001] ..... 239.482498: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482501: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482504: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482507: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482821: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482825: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482828: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482831: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.482834: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483004: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483007: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483010: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483013: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483016: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483230: unwind_user: USE FRAME POINTER
> trace-cmd-1136 [001] ..... 239.483234: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483238: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483240: unwind_user: USE EH_FRAME
> trace-cmd-1136 [001] ..... 239.483243: unwind_user: USE EH_FRAME
> [..]
Thanks for giving it a spin!
> Note, the compile had one warning (with all patches applied):
>
> vmlinux.o: warning: objtool: eh_frame_find+0x270c: call to eh_frame_do_expression.isra.0() with UACCESS enabled
I need to resolve those objtool warnings after my vacation. Any idea
how to get around the arch eh_frame_do_expression() callback issue?
Is there a mean to temporarily give up the UACCESS if it is within a
scoped guard region?
> I'll try to get time when I get back from my travels next week to look
> at each of the patches.
>
> I also do not think this makes sframe obsolete. I believe there's holes
> with eh_frame and some may not like the complexity of it. But this
> gives us an honest look at what options are available to us.
>
> Thanks for doing this!
It was fun and I learned quite a bit about .eh_frame, .eh_frame_hdr,
and GNU_EH_FRAME doing so.
Regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com
IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
On 8/18/2026 4:49 PM, Jens Remus wrote:
> This series adds support for parsing DWARF Call Frame Information (CFI)
> from the .eh_frame_hdr and .eh_frame sections of user space ELF files.
I am working on Sashiko's AI review feedback and will send a RFC v2
soon.
> Patch 25 adds a prctl() interface for (un)registering .eh_frame_hdr
> sections for shared libraries. I will send a related test-patch for
> Glibc separately.
Meanwhile find attached a Glibc 2.44 test patch that registers
.eh_frame_hdr sections referenced by GNU_EH_FRAME in shared libraries
with the kernel.
Regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com
IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
From 159003ef0ef2107ddc8ec8fd8e6ce06198ebbfdc Mon Sep 17 00:00:00 2001
From: Jens Remus <jremus@linux.ibm.com>
Date: Wed, 19 Aug 2026 10:15:53 +0200
Subject: [PATCH] TEST: glibc: Register GNU_EH_FRAME PHDRs to the kernel
The kernel does not have direct visibility to the ELF contents of
shared libraries. In the dynamic linker register and unregister
.eh_frame_hdr sections referenced by GNU_EH_FRAME program headers
via a proposed prctl() interface [1] to the kernel, so it knows
where to find these for stacktracing purposes.
Register .eh_frame_hdr sections during shared object loading and
during setup of VDSO. Unregister them during unmapping. Uses
proposed PR_{REGISTER|UNREGISTER}_EH_FRAME prctl.
Based on Josh Poimboeuf's respective GNU_SFRAME test patch.
[1]: [RFC PATCH v1 25/25] unwind_user/eh_frame: Add prctl() interface
for (un)registering .eh_frame_hdr sections,
https://lore.kernel.org/all/20260818144954.2320378-26-jremus@linux.ibm.com/
[ This patch is for test purposes only. ]
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
elf/dl-load.c | 46 ++++++++++++++++++++++++++++++++++++++++-
elf/dl-unmap-segments.h | 30 +++++++++++++++++++++++++++
elf/setup-vdso.h | 40 +++++++++++++++++++++++++++++++++++
include/link.h | 3 +++
4 files changed, 118 insertions(+), 1 deletion(-)
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 95404adae942..10ef365bc81b 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -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>
@@ -65,6 +66,23 @@
#define STRING(x) __STRING (x)
+#ifndef _FUTURE_LINUX_EH_FRAME
+#define _FUTURE_LINUX_EH_FRAME
+
+/* From <linux/prctl.h>. */
+#define PR_REGISTER_EH_FRAME 82
+#define PR_UNREGISTER_EH_FRAME 83
+
+/* From <linux/eh_frame.h>. */
+struct eh_frame_setup {
+ __u64 eh_frame_hdr_start;
+ __u64 eh_frame_hdr_size;
+ __u64 text_start;
+ __u64 text_size;
+};
+
+#endif /* _FUTURE_LINUX_EH_FRAME */
+
/* This is the decomposed LD_LIBRARY_PATH search path. */
struct r_search_path_struct __rtld_env_path_list attribute_relro;
@@ -1096,6 +1114,11 @@ _dl_map_object_scan_phdrs (struct dl_pt_load_iterator *it,
l->l_relro_addr = ph->p_vaddr;
l->l_relro_size = ph->p_memsz;
break;
+
+ case PT_GNU_EH_FRAME:
+ l->l_eh_frame_start = ph->p_vaddr;
+ l->l_eh_frame_size = ph->p_memsz;
+ break;
}
}
@@ -1384,13 +1407,34 @@ cannot enable executable stack as shared object requires");
if (l->l_tls_initimage != NULL)
l->l_tls_initimage = (void*)((uintptr_t)l->l_tls_initimage + l->l_addr);
+ /* Adjust the address of the .eh_frame_hdr start address. */
+ if (l->l_eh_frame_start != 0)
+ l->l_eh_frame_start += l->l_addr;
+
/* Process program headers again after load segments are mapped in
case processing requires accessing those segments. Scan program
headers backward since PT_GNU_PROPERTY is close to the end of
program headers. */
for (ph = &l->l_phdr[l->l_phnum]; ph != l->l_phdr; --ph)
- if (ph[-1].p_type == PT_GNU_PROPERTY)
+ switch (ph[-1].p_type)
{
+ case PT_LOAD:
+ if (l->l_eh_frame_start != 0
+ && ph[-1].p_flags & PF_X)
+ {
+ ElfW(Addr) text_start = l->l_addr + ph[-1].p_vaddr;
+ size_t text_size = ph[-1].p_memsz;
+ struct eh_frame_setup data = {
+ .eh_frame_hdr_start = l->l_eh_frame_start,
+ .eh_frame_hdr_size = l->l_eh_frame_size,
+ .text_start = text_start,
+ .text_size = text_size,
+ };
+
+ __prctl(PR_REGISTER_EH_FRAME, &data, sizeof(data), 0, 0);
+ }
+ break;
+ case PT_GNU_PROPERTY:
_dl_process_pt_gnu_property (l, fd, &ph[-1]);
break;
}
diff --git a/elf/dl-unmap-segments.h b/elf/dl-unmap-segments.h
index c3e46d50ec72..eae0843e85a8 100644
--- a/elf/dl-unmap-segments.h
+++ b/elf/dl-unmap-segments.h
@@ -21,6 +21,24 @@
#include <link.h>
#include <sys/mman.h>
+#include <sys/prctl.h>
+
+#ifndef _FUTURE_LINUX_EH_FRAME
+#define _FUTURE_LINUX_EH_FRAME
+
+/* From <linux/prctl.h>. */
+#define PR_REGISTER_EH_FRAME 82
+#define PR_UNREGISTER_EH_FRAME 83
+
+/* From <linux/eh_frame.h>. */
+struct eh_frame_setup {
+ __u64 eh_frame_hdr_start;
+ __u64 eh_frame_hdr_size;
+ __u64 text_start;
+ __u64 text_size;
+};
+
+#endif /* _FUTURE_LINUX_EH_FRAME */
/* _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
@@ -29,6 +47,18 @@
static __always_inline void
_dl_unmap_segments (struct link_map *l)
{
+ if (l->l_eh_frame_start != 0)
+ {
+ struct eh_frame_setup data = {
+ .eh_frame_hdr_start = l->l_eh_frame_start,
+ .eh_frame_hdr_size = 0,
+ .text_start = 0,
+ .text_size = 0,
+ };
+
+ __prctl(PR_UNREGISTER_EH_FRAME, &data, sizeof(data), 0, 0);
+ }
+
__munmap ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
}
diff --git a/elf/setup-vdso.h b/elf/setup-vdso.h
index 0dba7072d53b..83411c1aa396 100644
--- a/elf/setup-vdso.h
+++ b/elf/setup-vdso.h
@@ -16,6 +16,25 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
+#include <sys/prctl.h>
+
+#ifndef _FUTURE_LINUX_EH_FRAME
+#define _FUTURE_LINUX_EH_FRAME
+
+/* From <linux/prctl.h>. */
+#define PR_REGISTER_EH_FRAME 82
+#define PR_UNREGISTER_EH_FRAME 83
+
+/* From <linux/eh_frame.h>. */
+struct eh_frame_setup {
+ __u64 eh_frame_hdr_start;
+ __u64 eh_frame_hdr_size;
+ __u64 text_start;
+ __u64 text_size;
+};
+
+#endif /* _FUTURE_LINUX_EH_FRAME */
+
static inline void __attribute__ ((always_inline))
setup_vdso (struct link_map *main_map __attribute__ ((unused)),
struct link_map ***first_preload __attribute__ ((unused)))
@@ -56,6 +75,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_EH_FRAME)
+ {
+ if (! l->l_eh_frame_start)
+ {
+ l->l_eh_frame_start = ph->p_vaddr;
+ l->l_eh_frame_size = ph->p_memsz;
+ }
+ }
else
/* There must be no TLS segment. */
assert (ph->p_type != PT_TLS);
@@ -78,6 +105,19 @@ 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;
+ if (l->l_eh_frame_start != 0)
+ {
+ l->l_eh_frame_start += l->l_addr;
+
+ struct eh_frame_setup data = {
+ .eh_frame_hdr_start = l->l_eh_frame_start,
+ .eh_frame_hdr_size = l->l_eh_frame_size,
+ .text_start = l->l_map_start,
+ .text_size = l->l_map_end - l->l_map_start,
+ };
+ __prctl(PR_REGISTER_EH_FRAME, &data, sizeof(data), 0, 0);
+ }
+
/* Now that we have the info handy, use the DSO image's soname
so this object can be looked up by name. */
{
diff --git a/include/link.h b/include/link.h
index 8f851d2212df..b9ee4674ac69 100644
--- a/include/link.h
+++ b/include/link.h
@@ -345,6 +345,9 @@ struct link_map
ElfW(Addr) l_relro_addr;
size_t l_relro_size;
+ ElfW(Addr) l_eh_frame_start;
+ size_t l_eh_frame_size;
+
unsigned long long int l_serial;
};
--
2.53.0
© 2016 - 2026 Red Hat, Inc.