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) [email protected] / [email protected] 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 <[email protected]> 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/[email protected]/ [ This patch is for test purposes only. ] Signed-off-by: Jens Remus <[email protected]> --- 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
