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