Support building stackprof on LoongArch, which does have the necessary perf infrastructure in place.
Relevant ABI specifications: * https://github.com/loongson/la-abi-specs/blob/v2.50/ladwarf.adoc * https://github.com/loongson/la-abi-specs/blob/v2.50/lapcs.adoc The RPM architecture macro for 64-bit LoongArch (which is the case for all non-embedded use cases so far) is defined as `%loongarch64` since RPM 4.18.0; see https://github.com/rpm-software-management/rpm/pull/1872 for the introducing change. ChangeLog: * NEWS: Announce the change. * backends/Makefile.am: Add loongarch_initreg_sample.c to the list of source files for LoongArch, and re-tab the paragraph. * backends/libebl_PERF_FLAGS.h (defined): Add LoongArch branch. (REG): New local helper macro. (PERF_FRAME_REGISTERS_LOONGARCH): Define the register mask for preserved registers according to the LoongArch procedure calling convention. * backends/loongarch_init.c (loongarch_init): Hook up set_initial_registers_sample, sample_sp_pc, and perf_frame_regs_mask. * configure.ac: Mark loongarch as supporting stackprof. * libdwfl_stacktrace/dwflst_sample_frame.c (dwflst_arch_from_uname): Recognize "loongarch" as supported. (dwflst_arch_expected_frame_nregs): Add LoongArch information. (dwflst_arch_sp_dwarf_reg): Likewise. (dwflst_arch_sp_perf_reg): Likewise. * backends/loongarch_initreg_sample.c: New file based on the AArch64 implementation. config/ChangeLog: * elfutils.spec.in: Enable building stackprof on LoongArch64. Signed-off-by: WANG Xuerui <[email protected]> --- NEWS | 4 + backends/Makefile.am | 5 +- backends/libebl_PERF_FLAGS.h | 23 +++++- backends/loongarch_init.c | 7 ++ backends/loongarch_initreg_sample.c | 97 ++++++++++++++++++++++++ config/elfutils.spec.in | 4 +- configure.ac | 4 +- libdwfl_stacktrace/dwflst_sample_frame.c | 9 +++ 8 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 backends/loongarch_initreg_sample.c diff --git a/NEWS b/NEWS index 5e55d1a83544..129f927fb01f 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +Version 0.198 + +stackprof: Now supports collecting traces on LoongArch. + Version 0.197 "Anti-Corruption" THREAD-SAFETY: New document describing the thread safety model for elfutils diff --git a/backends/Makefile.am b/backends/Makefile.am index 0effaaf2e759..aa76cb3ead52 100644 --- a/backends/Makefile.am +++ b/backends/Makefile.am @@ -98,8 +98,9 @@ csky_SRCS = csky_attrs.c csky_init.c csky_symbol.c csky_cfi.c \ csky_regs.c csky_initreg.c csky_corenote.c loongarch_SRCS = loongarch_init.c loongarch_symbol.c loongarch_cfi.c \ - loongarch_regs.c loongarch_retval.c loongarch_initreg.c \ - loongarch_unwind.c loongarch_corenote.c + loongarch_regs.c loongarch_retval.c loongarch_initreg.c \ + loongarch_initreg_sample.c loongarch_unwind.c \ + loongarch_corenote.c arc_SRCS = arc_init.c arc_symbol.c diff --git a/backends/libebl_PERF_FLAGS.h b/backends/libebl_PERF_FLAGS.h index ebc7362b1caa..9bb45af4e9b4 100644 --- a/backends/libebl_PERF_FLAGS.h +++ b/backends/libebl_PERF_FLAGS.h @@ -37,8 +37,11 @@ /* || 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) +/* These architectures only have <asm/perf_regs.h> in newer kernel headers. + - alpha: v7.4. + - loongarch: v6.1. */ +#if (defined(__alpha__) || defined(__loongarch__)) && \ + defined(HAVE_ASM_PERF_REGS_H) # include <asm/perf_regs.h> #endif #endif @@ -116,6 +119,22 @@ #define PERF_FRAME_REGISTERS_ALPHA 0 #endif /* _ASM_ALPHA_PERF_REGS_H */ +#if defined(_ASM_LOONGARCH_PERF_REGS_H) +#define REG(R) (1ULL << PERF_REG_LOONGARCH_ ## R) +/* Proper unwind set: PC, $ra (r1), $sp (r3), $fp/$s9 (r22), and callee-saved + $s* registers (r23 through r31). $r21 is reserved and unused in the ABI + so ABI-conformant code does not need it to properly unwind. + Collecting all regs would also be feasible. */ +#define PERF_FRAME_REGISTERS_LOONGARCH (REG(PC) | REG(R1) | REG(R3) \ + | REG(R22) | REG(R23) | REG(R24) | REG(R25) | REG(R26) | REG(R27) \ + | REG(R28) | REG(R29) | REG(R30) | REG(R31)) +/* Register ordering defined in linux arch/loongarch/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 LoongArch perf sample frames. */ +#define PERF_FRAME_REGISTERS_LOONGARCH 0 +#endif /* _ASM_LOONGARCH_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/backends/loongarch_init.c b/backends/loongarch_init.c index 9485ca38a3a4..0dee224e4298 100644 --- a/backends/loongarch_init.c +++ b/backends/loongarch_init.c @@ -35,6 +35,7 @@ #define BACKEND loongarch_ #define RELOC_PREFIX R_LARCH_ #include "libebl_CPU.h" +#include "libebl_PERF_FLAGS.h" /* This defines the common reloc hooks based on loongarch_reloc.def. */ #include "common-reloc.c" @@ -60,6 +61,12 @@ loongarch_init (Elf *elf __attribute__ ((unused)), HOOK (eh, set_initial_registers_tid); HOOK (eh, return_value_location); HOOK (eh, unwind); + HOOK (eh, set_initial_registers_sample); + HOOK (eh, sample_sp_pc); + /* sample_perf_regs_mapping is default ver: after reusing r0's DWARF + register slot for PC, the DWARF register mapping happens to match the + perf one */ + eh->perf_frame_regs_mask = PERF_FRAME_REGISTERS_LOONGARCH; return eh; } diff --git a/backends/loongarch_initreg_sample.c b/backends/loongarch_initreg_sample.c new file mode 100644 index 000000000000..c20886dbefb9 --- /dev/null +++ b/backends/loongarch_initreg_sample.c @@ -0,0 +1,97 @@ +/* Populate process registers from a register sample. + Copyright (C) 2026 WANG Xuerui. + 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 loongarch_ +#include "libebl_CPU.h" +#include "libebl_PERF_FLAGS.h" + +bool +loongarch_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, 3 /* index of sp in dwarf_regs */, + pc, 0 /* index of pc in dwarf_regs */); +} + +bool +loongarch_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) +{ + /* LoongArch defines no PC in DWARF so here we reuse r0 (constant zero) as + PC. */ +#define N_GPRS 32 + Dwarf_Word dwarf_regs[N_GPRS]; + bool scratch_present = false; + size_t i; + for (i = 0; i < N_GPRS; i++) + dwarf_regs[i] = 0x0; + for (i = 0; i < n_regs; i++) + { + if (i >= n_regs_mapping) + break; + if (regs_mapping[i] < 0 || regs_mapping[i] >= N_GPRS) + continue; + if (regs_mapping[i] >= 4 && regs_mapping[i] < 21) + /* $a0 to $t8 inclusive. */ + scratch_present = true; + dwarf_regs[regs_mapping[i]] = regs[i]; + } + + /* $a0 (r4) through $t8 (r20) only if present. */ + if (scratch_present && ! setfunc (4, 21 - 4, &dwarf_regs[4], arg)) + return false; + + /* $fp/$s9 (r22) plus $s0 (r23)..$s8 (r31). */ + if (! setfunc (22, 32 - 22, &dwarf_regs[22], arg)) + return false; + + /* RA. */ + if (! setfunc (1, 1, &dwarf_regs[1], arg)) + return false; + + /* SP. */ + if (! setfunc (3, 1, &dwarf_regs[3], arg)) + return false; + + /* PC. */ + if (! setfunc (-1, 1, &dwarf_regs[0], arg)) + return false; + + /* XXX Skip FP registers. */ + return true; +} diff --git a/config/elfutils.spec.in b/config/elfutils.spec.in index 9ff89ee19ada..072ea24f2281 100644 --- a/config/elfutils.spec.in +++ b/config/elfutils.spec.in @@ -7,8 +7,8 @@ License: GPLv3+ and (GPLv2+ or LGPLv3+) and GFDL Source: ftp://sourceware.org/pub/elfutils/%{version}/elfutils-%{version}.tar.bz2 Summary: A collection of utilities and DSOs to handle ELF files and DWARF data -# eu-stackprof currently only supports x86/ARM -%ifarch %{ix86} %{x86_64} %{arm} %{arm64} +# eu-stackprof currently only supports x86/ARM/LoongArch +%ifarch %{ix86} %{x86_64} %{arm} %{arm64} %{loongarch64} %global enable_stackprof 1 %else %global enable_stackprof 0 diff --git a/configure.ac b/configure.ac index d3c0292273c5..e1a8f98e9042 100644 --- a/configure.ac +++ b/configure.ac @@ -959,8 +959,8 @@ 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. + alpha*|loongarch*) + # <asm/perf_regs.h> is only in newer kernel headers for these architectures. stackprof_arch_supported=${ac_cv_header_asm_perf_regs_h} ;; *) diff --git a/libdwfl_stacktrace/dwflst_sample_frame.c b/libdwfl_stacktrace/dwflst_sample_frame.c index a555fccf7e07..396d5108acb9 100644 --- a/libdwfl_stacktrace/dwflst_sample_frame.c +++ b/libdwfl_stacktrace/dwflst_sample_frame.c @@ -73,6 +73,8 @@ dwflst_arch_from_uname (const char *umachine) return EM_ARM; else if (strncmp(umachine, "alpha", 5) == 0) return EM_ALPHA; + else if (strncmp(umachine, "loongarch", 9) == 0) + return EM_LOONGARCH; /* XXX Other architectures not yet supported. */ return EM_NONE; } @@ -99,6 +101,8 @@ dwflst_arch_expected_frame_nregs (GElf_Half machine) constant directly rather than initializing a new Ebl. */ return machine == EM_X86_64 ? 17 : 9; /* return ebl_frame_nregs(ebl); */ + if (machine == EM_LOONGARCH) + return 13; /* See libebl_PERF_FLAGS.h for the list. */ /* XXX Other architectures are not supported yet. In general, it's fine to be on the permissive side here for a minimum expected number of registers. */ @@ -119,6 +123,8 @@ dwflst_arch_sp_dwarf_reg (GElf_Half machine, bool force_abi32) return force_abi32 ? 13 : 31; case EM_ALPHA: return 30; + case EM_LOONGARCH: + return 3; default: /* XXX Other architectures are not supported yet. */ return -1; @@ -140,6 +146,9 @@ dwflst_arch_sp_perf_reg (GElf_Half machine, else if (machine == EM_ALPHA) sp_perf_index = 30; /* PERF_REG_ALPHA_SP */ + else if (machine == EM_LOONGARCH) + sp_perf_index = 3; + /* perf_regs<->dwarf_regs mapping also linear */ else /* XXX Other architectures are not supported yet. */ return -1; -- 2.56.0
