Hi Serhei,
On Wed, Aug 12, 2026 at 2:44 PM Serhei Makarov <[email protected]> wrote:
>
> One need identified from implementing patches for downstream profiling
> tools to adopt libdwfl_stacktrace is that libebl per-architecture
> constants are not exposed to external projects.
>
> This makes handling stack pointer especially tricky, as shown
> by the incorrect prior eu-stacktrace logic cf PR34498,
> since the profiler has to account for how perf_regs mask
> might alter the position.
>
> v2: This now incorporates a necessary fix for PR34498.
>
> * libdwfl_stacktrace/libdwfl_stacktrace.h
> (dwflst_arch_from_uname): New API, translates umachine str obtained
> from uname() -> struct utsname -> machine to ELF machine ID.
> (dwflst_arch_sp_dwarf_reg): New API, identifies index of stack ptr.
> (dwflst_arch_sp_perf_reg): New API, identifies index of stack ptr
> in perf_events regs[] array given a perf_regs_mask (cf PR34498).
> (dwflst_arch_expected_frame_nregs): New API, minimal number of regs
> for unwinding (useful for sanity-checking incoming stack samples).
> * libdwfl_stacktrace/dwflst_sample_frame.c: Format in two sections.
> (dwflst_arch_from_uname): Implementation.
> (dwflst_arch_expected_frame_nregs): Implementation.
> (dwflst_arch_sp_dwarf_reg): Implementation.
> (dwflst_arch_sp_perf_reg): Implementation.
> * libdw/libdw.map (ELFUTILS_0.196_EXPERIMENTAL): Add the new functions.
>
> Signed-off-by: Serhei Makarov <[email protected]>
> ---
> libdw/libdw.map | 8 ++
> libdwfl_stacktrace/dwflst_sample_frame.c | 102 ++++++++++++++++++++++-
> libdwfl_stacktrace/libdwfl_stacktrace.h | 32 +++++++
> 3 files changed, 141 insertions(+), 1 deletion(-)
>
> diff --git a/libdw/libdw.map b/libdw/libdw.map
> index b45647e6..37715d4e 100644
> --- a/libdw/libdw.map
> +++ b/libdw/libdw.map
> @@ -411,3 +411,11 @@ ELFUTILS_0.194_EXPERIMENTAL {
> global:
> dwflst_sample_getframes;
> } ELFUTILS_0.193_EXPERIMENTAL;
> +
> +ELFUTILS_0.196_EXPERIMENTAL {
> + global:
> + dwflst_arch_from_uname;
> + dwflst_arch_expected_frame_nregs;
> + dwflst_arch_sp_dwarf_reg;
> + dwflst_arch_sp_perf_reg;
> +} ELFUTILS_0.194_EXPERIMENTAL;
> diff --git a/libdwfl_stacktrace/dwflst_sample_frame.c
> b/libdwfl_stacktrace/dwflst_sample_frame.c
> index cf33a439..95cfb572 100644
> --- a/libdwfl_stacktrace/dwflst_sample_frame.c
> +++ b/libdwfl_stacktrace/dwflst_sample_frame.c
> @@ -32,10 +32,13 @@
>
> #include "libdwfl_stacktraceP.h"
>
> +/* Various functions providing arch-specific info: */
> +
> Ebl *default_ebl = NULL;
> GElf_Half default_ebl_machine = EM_NONE;
>
> -uint64_t dwflst_perf_sample_preferred_regs_mask (GElf_Half machine)
> +uint64_t
> +dwflst_perf_sample_preferred_regs_mask (GElf_Half machine)
> {
> /* XXX The most likely case is that this will only be called once,
> for the current architecture. So we keep one Ebl* around for
> @@ -56,6 +59,103 @@ uint64_t dwflst_perf_sample_preferred_regs_mask
> (GElf_Half machine)
> return 0;
> }
>
> +GElf_Half
> +dwflst_arch_from_uname (const char *umachine)
> +{
> + if (strncmp(umachine, "x86_64", 6) == 0)
> + return EM_X86_64;
> + else if (strncmp(umachine, "i686", 4) == 0
> + || strncmp(umachine, "i386", 4) == 0)
> + return EM_386;
> + else if (strncmp(umachine, "aarch64", 7) == 0)
> + return EM_AARCH64;
> + else if (strncmp(umachine, "armv7l", 6) == 0)
> + return EM_ARM;
> + /* XXX Other architectures not yet supported. */
> + return EM_NONE;
> +}
> +
> +/* XXX The per-machine switches suggest the following implementations
> + could be folded into backends/, but we would need to create a Ebl
> + to handle the dispatch: */
Agreed that folding into backends/ would be better but this can be done later.
> +
> +uint32_t
> +dwflst_arch_expected_frame_nregs (GElf_Half machine)
> +{
> + /* For aarch64, we actually use fewer than ebl->frame_nregs to unwind: */
> + if (machine == EM_AARCH64)
> + return 14;
> + if (machine == EM_ARM)
> + return 16;
> + /* On x86, expect everything except FLAGS: */
> + if (machine == EM_X86_64 || machine == EM_386)
> + /* XXX An external user of the library can't access the Ebl, hence
> + can't conveniently provide it to us it here. We provide the
> + constant directly rather than initializing a new Ebl. */
> + return machine == EM_X86_64 ? 17 : 9;
> + /* return ebl_frame_nregs(ebl); */
> + /* XXX Other architectures are not supported yet.
> + In general, it's fine to be on the permissive side here. */
> + return 1;
I believe 0 should be returned here instead of 1. libdwfl_stacktrace.h
comment for this function states that 0 is returned when the arch is
not supported. LGTM otherwise.
Aaron
> +}
> +
> +int
> +dwflst_arch_sp_dwarf_reg (GElf_Half machine, bool force_abi32)
> +{
> + switch (machine) {
> + case EM_X86_64:
> + return force_abi32 ? 4 : 7;
> + case EM_386:
> + return 4;
> + case EM_ARM:
> + return 13;
> + case EM_AARCH64:
> + return force_abi32 ? 13 : 31;
> + default:
> + /* XXX Other architectures are not supported yet. */
> + return -1;
> + }
> +}
> +
> +int
> +dwflst_arch_sp_perf_reg (GElf_Half machine,
> + uint64_t perf_regs_mask, bool is_abi32)
> +{
> + int sp_perf_index;
> + if (machine == EM_X86_64 || machine == EM_386)
> + sp_perf_index = 7; /* uniform on 32/64-bit abi */
> + /* compare backends/x86_initreg_sample.c;
> + for dwarf_index, would be (is_abi32 ? 4 : 7) */
> + else if (machine == EM_ARM || machine == EM_AARCH64)
> + sp_perf_index = (is_abi32 ? 13 : 31);
> + /* basic linear mapping of perf_regs<->dwarf_regs */
> + else
> + /* XXX Other architectures are not supported yet. */
> + return -1;
> +
> + if (perf_regs_mask == 0)
> + /* Assume all registers present: */
> + return sp_perf_index;
> +
> + /* Iterate perf_regs_mask to find
> + k == index of sp in perf_regs_mask
> + j == index of sp in regs[] */
> + int j, k; uint64_t bit;
> + for (k = 0, j = -1, bit = 1;
> + k <= sp_perf_index; k++, bit <<= 1)
> + {
> + if ((bit & perf_regs_mask))
> + j++;
> + else
> + continue; /* sp may not be present */
> + if (k == sp_perf_index)
> + return j;
> + }
> + return -2;
> +}
> +
> +/* Stack sample handling: */
> +
> struct sample_info {
> pid_t pid;
> pid_t tid;
> diff --git a/libdwfl_stacktrace/libdwfl_stacktrace.h
> b/libdwfl_stacktrace/libdwfl_stacktrace.h
> index 84cb69a3..2f4e9164 100644
> --- a/libdwfl_stacktrace/libdwfl_stacktrace.h
> +++ b/libdwfl_stacktrace/libdwfl_stacktrace.h
> @@ -154,6 +154,38 @@ int dwflst_perf_sample_getframes (Dwfl *dwfl, Elf *elf,
> pid_t pid, pid_t tid,
> opened later. */
> uint64_t dwflst_perf_sample_preferred_regs_mask (GElf_Half machine);
>
> +/* Returns the correct ELF machine identifier for the arch identifier
> + string UMACHINE from struct utsname, if libdwflst handles stack
> + samples for MACHINE. Returns EM_NONE if stack sample handling is
> + not supported. */
> +GElf_Half dwflst_arch_from_uname (const char *umachine);
> +
> +/* Returns the minimum required number of registers for unwinding
> + for MACHINE, which should be at most the number of bits set in
> + dwflst_perf_sample_preferred_regs_mask(MACHINE). Returns 0
> + if libdwfl does not handle stack samples for MACHINE. */
> +uint32_t dwflst_arch_expected_frame_nregs (GElf_Half machine);
> +
> +/* Returns the index of the stack pointer register within the
> + dwarf_regs ordering for MACHINE. If FORCE_ABI32 is true, returns
> + the index within the dwarf_regs ordering for the 32-bit variant of
> + MACHINE. Returns -1 if libdwfl does not handle stack samples for
> + MACHINE. */
> +int dwflst_arch_sp_dwarf_reg (GElf_Half machine, bool force_abi32);
> +
> +/* Returns the index of the stack pointer register within the
> + perf_regs ordering for MACHINE, assuming only the registers within
> + PERF_REGS_MASK are included. (If PERF_REGS_MASK is 0, assumes all
> + registers are included.) If IS_ABI32 is true, returns the index
> + within the perf_regs ordering for the 32-bit variant of MACHINE,
> + to allow handling of mixed-architecture perf_events data; if
> + IS_ABI32 is false, returns the index for the 64-bit variant in
> + accordance to perf_events conventions. Returns -1 if libdwfl does
> + not handle stack samples for MACHINE, -2 if sp is not present in
> + PERF_REGS_MASK. */
> +int dwflst_arch_sp_perf_reg (GElf_Half machine,
> + uint64_t perf_regs_mask, bool is_abi32);
> +
> #ifdef __cplusplus
> }
> #endif
> --
> 2.54.0
>