Hi Matt,

On Mon, Oct 12, 2026 at 8:40 PM Matt Turner <[email protected]> wrote:
>
> The proposed Linux patch "alpha: add HAVE_PERF_REGS and
> HAVE_PERF_USER_STACK_DUMP support" adds perf register sampling for
> alpha, with the register numbering in
> arch/alpha/include/uapi/asm/perf_regs.h. Implement the ebl hooks to
> unwind from perf samples using it.
>
> Perf register numbers 0-30 match the DWARF numbers and 31 is the pc, so
> the default sample_perf_regs_mapping works, with index 31 standing for
> the pc. Alpha's frame_nregs is 65, which is too large for the generic
> ebl_set_initial_registers_sample, so provide a backend implementation.
>
> The callee-saved $9-$15 are not in pt_regs on the interrupt path, so the
> kernel cannot sample them. alpha_set_initial_registers_sample leaves
> them unset, which means frames whose CFA is based on $15 (-O0 code, or
> functions using alloca) cannot be unwound from a perf sample.
>
> <asm/perf_regs.h> is only in newer kernel headers on alpha, so check for
> it in configure and only allow --enable-stackprof on alpha when it is
> present.
>
>         * backends/alpha_initreg_sample.c: New file.
>         * backends/Makefile.am (alpha_SRCS): Add alpha_initreg_sample.c.
>         * backends/alpha_init.c: Include libebl_PERF_FLAGS.h.
>         (alpha_init): Hook set_initial_registers_sample and sample_sp_pc.
>         Set perf_frame_regs_mask.
>         * backends/libebl_PERF_FLAGS.h: Include <asm/perf_regs.h> on alpha
>         if present.
>         (PERF_FRAME_REGISTERS_ALPHA): New define.
>         * configure.ac: Check for asm/perf_regs.h. Set
>         stackprof_arch_supported for alpha from it.
>         * libdwfl_stacktrace/dwflst_sample_frame.c (dwflst_arch_from_uname):
>         Handle alpha.
>         (dwflst_arch_expected_frame_nregs): Likewise.
>         (dwflst_arch_sp_dwarf_reg): Likewise.
>         (dwflst_arch_sp_perf_reg): Likewise.
> ---
>  backends/Makefile.am                     |  3 +-
>  backends/alpha_init.c                    |  5 +++
>  backends/alpha_initreg_sample.c          | 73 
> ++++++++++++++++++++++++++++++++
>  backends/libebl_PERF_FLAGS.h             | 16 +++++++
>  configure.ac                             |  6 ++-
>  libdwfl_stacktrace/dwflst_sample_frame.c | 10 +++++
>  6 files changed, 111 insertions(+), 2 deletions(-)
>
> diff --git a/backends/Makefile.am b/backends/Makefile.am
> index dac2c060..0effaaf2 100644
> --- a/backends/Makefile.am
> +++ b/backends/Makefile.am
> @@ -53,7 +53,8 @@ x86_64_SRCS = x86_64_init.c x86_64_symbol.c 
> x86_64_corenote.c x86_64_cfi.c \
>  ia64_SRCS = ia64_init.c ia64_symbol.c ia64_regs.c ia64_retval.c
>
>  alpha_SRCS = alpha_init.c alpha_symbol.c alpha_retval.c alpha_regs.c \
> -            alpha_corenote.c alpha_auxv.c alpha_cfi.c alpha_initreg.c
> +            alpha_corenote.c alpha_auxv.c alpha_cfi.c alpha_initreg.c \
> +            alpha_initreg_sample.c
>
>  arm_SRCS = arm_init.c arm_symbol.c arm_regs.c arm_corenote.c \
>            arm_auxv.c arm_attrs.c arm_retval.c arm_cfi.c arm_initreg.c \
> diff --git a/backends/alpha_init.c b/backends/alpha_init.c
> index 0e46e92f..88ef1edc 100644
> --- a/backends/alpha_init.c
> +++ b/backends/alpha_init.c
> @@ -34,6 +34,7 @@
>  #define BACKEND                alpha_
>  #define RELOC_PREFIX   R_ALPHA_
>  #include "libebl_CPU.h"
> +#include "libebl_PERF_FLAGS.h"
>
>  /* This defines the common reloc hooks based on alpha_reloc.def.  */
>  #include "common-reloc.c"
> @@ -63,6 +64,10 @@ alpha_init (Elf *elf __attribute__ ((unused)),
>       as the return address column.  */
>    eh->frame_nregs = 65;
>    HOOK (eh, set_initial_registers_tid);
> +  HOOK (eh, set_initial_registers_sample);
> +  HOOK (eh, sample_sp_pc);
> +  /* sample_perf_regs_mapping is default ver  */
> +  eh->perf_frame_regs_mask = PERF_FRAME_REGISTERS_ALPHA;
>    eh->sysvhash_entrysize = sizeof (Elf64_Xword);
>
>    return eh;
> diff --git a/backends/alpha_initreg_sample.c b/backends/alpha_initreg_sample.c
> new file mode 100644
> index 00000000..0d34d099
> --- /dev/null
> +++ b/backends/alpha_initreg_sample.c
> @@ -0,0 +1,73 @@
> +/* Populate process registers from a linux perf_events sample.
> +   Copyright (C) 2026 Matt Turner
> +   This file is part of elfutils.
> +
> +   This file is free software; you can redistribute it and/or modify
> +   it under the terms of either
> +
> +     * the GNU Lesser General Public License as published by the Free
> +       Software Foundation; either version 3 of the License, or (at
> +       your option) any later version
> +
> +   or
> +
> +     * the GNU General Public License as published by the Free
> +       Software Foundation; either version 2 of the License, or (at
> +       your option) any later version
> +
> +   or both in parallel, as here.
> +
> +   elfutils is distributed in the hope that it will be useful, but
> +   WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   General Public License for more details.
> +
> +   You should have received copies of the GNU General Public License and
> +   the GNU Lesser General Public License along with this program.  If
> +   not, see <http://www.gnu.org/licenses/>.  */
> +
> +
> +#ifdef HAVE_CONFIG_H
> +# include <config.h>
> +#endif
> +
> +#define BACKEND alpha_
> +#include "libebl_CPU.h"
> +#include "libebl_PERF_FLAGS.h"
> +
> +/* The default sample_perf_regs_mapping maps perf register numbers 1:1,
> +   which gives the DWARF number for $0-$30.  PERF_REG_ALPHA_PC is 31,
> +   which cannot be mistaken for $31 since the kernel does not sample
> +   the zero register.  */
> +#define SAMPLE_PC 31
> +
> +bool
> +alpha_sample_sp_pc (const Dwarf_Word *regs, uint32_t n_regs,
> +                   const int *regs_mapping, size_t n_regs_mapping,
> +                   Dwarf_Word *sp, Dwarf_Word *pc)
> +{
> +  return generic_sample_sp_pc (regs, n_regs, regs_mapping, n_regs_mapping,
> +                              sp, 30 /* index of sp in dwarf_regs */,
> +                              pc, SAMPLE_PC);
> +}
> +
> +bool
> +alpha_set_initial_registers_sample (const Dwarf_Word *regs, uint32_t n_regs,
> +                                   const int *regs_mapping, size_t 
> n_regs_mapping,
> +                                   ebl_tid_registers_t *setfunc,
> +                                   void *arg)
> +{
> +  Dwarf_Word dwarf_regs[32] = { 0 };
> +  for (size_t i = 0; i < n_regs && i < n_regs_mapping; i++)
> +    if (regs_mapping[i] >= 0 && regs_mapping[i] < 32)
> +      dwarf_regs[regs_mapping[i]] = regs[i];
> +
> +  Dwarf_Word pc = dwarf_regs[SAMPLE_PC];
> +  dwarf_regs[31] = 0;
> +
> +  /* The kernel cannot sample the callee-saved $9-$15.  Leave them
> +     unset so that the unwinder does not use a bogus value.  */
> +  return (setfunc (0, 9, &dwarf_regs[0], arg)
> +         && setfunc (16, 16, &dwarf_regs[16], arg)
> +         && setfunc (-1, 1, &pc, arg));
> +}
> diff --git a/backends/libebl_PERF_FLAGS.h b/backends/libebl_PERF_FLAGS.h
> index 07ffc38c..ebc7362b 100644
> --- a/backends/libebl_PERF_FLAGS.h
> +++ b/backends/libebl_PERF_FLAGS.h
> @@ -37,6 +37,10 @@
>  /* || defined(other_architecture)... */
>  # include <asm/perf_regs.h>
>  #endif
> +/* Alpha's asm/perf_regs.h is only in newer kernel headers.  */
> +#if defined(__alpha__) && defined(HAVE_ASM_PERF_REGS_H)
> +# include <asm/perf_regs.h>
> +#endif
>  #endif
>
>  #if defined(_ASM_X86_PERF_REGS_H)
> @@ -100,6 +104,18 @@
>  #define PERF_FRAME_REGISTERS_AARCH64 0
>  #endif /* _ASM_ARM64_PERF_REGS_H */
>
> +#if defined(_ASM_ALPHA_PERF_REGS_H)

This should be _UAPI_ASM_ALPHA_PERF_REGS_H, matching your kernel
header, the #endif comment needs updating too. Otherwise,
PERF_FRAME_REGISTERS_ALPHA is zero.

My earlier perf test passed because perf supplies its own register
mapping and initialization callback. Elfutils' sample API uses this
mask. Without the fix, dwflst_perf_sample_getframes() failed on all
194 optimized samples on my ES40.

With the guard corrected, all 194 recovered the expected caller
chain. r9-r15 remained unknown, and all 199 r15-dependent samples
stopped at the leaf as documented.

> +/* Everything the kernel can sample.  The callee-saved $9-$15 are not
> +   in pt_regs.  */
> +#define PERF_FRAME_REGISTERS_ALPHA (((1ULL << PERF_REG_ALPHA_MAX) - 1) \
> +  & ~(0x7fULL << PERF_REG_ALPHA_R9))
> +/* Register ordering defined in linux 
> arch/alpha/include/uapi/asm/perf_regs.h.  */
> +#else
> +/* Since asm/perf_regs.h is absent, or gives the register layout for a
> +   different arch, we can't unwind alpha perf sample frames.  */
> +#define PERF_FRAME_REGISTERS_ALPHA 0
> +#endif /* _ASM_ALPHA_PERF_REGS_H */
> +
>  static inline bool
>  generic_sample_sp_pc (const Dwarf_Word *regs, uint32_t n_regs,
>                       const int *regs_mapping, size_t n_regs_mapping,
> diff --git a/configure.ac b/configure.ac
> index b0d1b48d..536fa72a 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -937,7 +937,7 @@ AC_CHECK_HEADERS([perfmon/pfmlib_perf_event.h])
>  AM_CONDITIONAL([HAVE_LIBPFM], [test 
> "x${ac_cv_header_perfmon_pfmlib_perf_event_h}" = "xyes" ])
>  AC_CHECK_LIB(pfm, pfm_get_os_event_encoding, [AC_SUBST(libpfm_LIBS, 
> '-lpfm')])
>  # required:
> -AC_CHECK_HEADERS([linux/perf_event.h])
> +AC_CHECK_HEADERS([linux/perf_event.h asm/perf_regs.h])
>  AC_CHECK_MEMBER([struct perf_event_attr.build_id],
>    [have_perf_event_attr_build_id=yes],
>    [have_perf_event_attr_build_id=no],
> @@ -957,6 +957,10 @@ case $host_cpu in
>    i?86|x86_64|amd64|arm*|aarch64*)
>      stackprof_arch_supported=yes
>      ;;
> +  alpha*)
> +    # Alpha's <asm/perf_regs.h> is only in newer kernel headers.
> +    stackprof_arch_supported=${ac_cv_header_asm_perf_regs_h}
> +    ;;
>    *)
>      stackprof_arch_supported=no
>      ;;
> diff --git a/libdwfl_stacktrace/dwflst_sample_frame.c 
> b/libdwfl_stacktrace/dwflst_sample_frame.c
> index cbd5a79a..a555fccf 100644
> --- a/libdwfl_stacktrace/dwflst_sample_frame.c
> +++ b/libdwfl_stacktrace/dwflst_sample_frame.c
> @@ -71,6 +71,8 @@ dwflst_arch_from_uname (const char *umachine)
>      return EM_AARCH64;
>    else if (strncmp(umachine, "armv7l", 6) == 0)
>      return EM_ARM;
> +  else if (strncmp(umachine, "alpha", 5) == 0)
> +    return EM_ALPHA;
>    /* XXX Other architectures not yet supported. */
>    return EM_NONE;
>  }
> @@ -87,6 +89,9 @@ dwflst_arch_expected_frame_nregs (GElf_Half machine)
>      return 14;
>    if (machine == EM_ARM)
>      return 16;
> +  /* On alpha, expect everything the kernel can sample:  */
> +  if (machine == EM_ALPHA)
> +    return 25;
>    /* 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
> @@ -112,6 +117,8 @@ dwflst_arch_sp_dwarf_reg (GElf_Half machine, bool 
> force_abi32)
>      return 13;
>    case EM_AARCH64:
>      return force_abi32 ? 13 : 31;
> +  case EM_ALPHA:
> +    return 30;
>    default:
>      /* XXX Other architectures are not supported yet.  */
>      return -1;
> @@ -130,6 +137,9 @@ dwflst_arch_sp_perf_reg (GElf_Half machine,
>    else if (machine == EM_ARM || machine == EM_AARCH64)
>      sp_perf_index = (is_abi32 ? 13 : 31);
>      /* basic linear mapping of perf_regs<->dwarf_regs */
> +  else if (machine == EM_ALPHA)
> +    sp_perf_index = 30;
> +    /* PERF_REG_ALPHA_SP */
>    else
>      /* XXX Other architectures are not supported yet.  */
>      return -1;
>
> --
> 2.55.0
>

Regards,
Magnus

Reply via email to