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
>

Reply via email to