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)
+/* 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