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

Reply via email to