summaryrefslogtreecommitdiffstats
path: root/backends
diff options
context:
space:
mode:
authorSerhei Makarov <[email protected]>2025-10-24 10:26:03 -0400
committerAaron Merey <[email protected]>2025-10-24 11:05:01 -0400
commit3ce0d5ed6f878be84ff6a9962885fbe8101b1acb (patch)
tree4601605a6bea267e4e60372b0eb98a81c519e34a /backends
parent42823ca610647db13ff6b65603ea00333a1d2634 (diff)
libdwfl_stacktrace + libebl: dwflst_sample_getframes non-perf api
This patch adds a generic dwflst_sample_getframes() API that does not depend on perf_events concepts, in particular the linux-kernel-specific enum defining the perf_regs_mask register order. This involves reworking the register-handling backend to use regs_mapping arrays rather than perf_regs_mask, and provide a way to translate perf_regs_mask to regs_mapping. A regs_mapping array, for each item in a provided regs[] array, specifies its position in the full register file expected by the DWARF functionality. * libdwfl_stacktrace/Makefile.am: Rename dwflst_sample_frame.c from dwflst_perf_frame.c. * libdwfl_stacktrace/libdwfl_stacktrace.h (dwflst_sample_getframes): New function providing unwinding functionality with a regs_mapping array rather than a linux-kernel-dependent perf_regs_mask. * libdw/libdw.map (ELFUTILS_0.194_EXPERIMENTAL): Add dwflst_sample_getframes. * libdwfl_stacktrace/dwflst_sample_frame.c: Renamed from dwflst_perf_frame.c. Remove linux/perf_event.h dependency. (struct sample_info): Rename from perf_sample_info, include regs_mapping field, replace abi with elfclass field. (sample_next_thread): Renamed struct sample_info. (sample_getthread): Renamed struct sample_info. (copy_word): Use elfclass instead of perf abi field. (elf_memory_read): Renamed struct sample_info, use elfclass. (sample_memory_read): Renamed struct sample_info, use elfclass. (sample_set_initial_registers): Renamed struct sample_info, pass regs_mapping to ebl_set_initial_registers_sample. (dwflst_sample_getframes): New function. (dwflst_perf_sample_getframes): Reimplement in terms of dwflst_sample_getframes and ebl_sample_perf_regs_mapping. * libebl/ebl-hooks.h (set_initial_registers_sample): Now takes regs_mapping instead of regs_mask. (sample_base_addr): Removed. (sample_pc): Removed. (sample_sp_pc): New function combining the removed functions for efficiency. (sample_perf_regs_mapping): New function translating perf_regs_mask to regs_mapping array. * libebl/eblinitreg_sample.c (ebl_sample_base_addr): Removed. (ebl_sample_pc): Removed. (ebl_sample_sp_pc): New function. (ebl_set_initial_registers_sample): Take regs_mapping, provide a default implementation for contiguous dwarf_regs array. (ebl_sample_perf_regs_mapping): New function. * libebl/eblclosebackend.c (ebl_closebackend): Free cached_regs_mapping. * libebl/libebl.h (ebl_set_initial_registers_sample): Now takes regs_mapping instead of regs_mask. (ebl_sample_base_addr): Removed. (ebl_sample_pc): Removed. (ebl_sample_sp_pc): New function. (ebl_sample_perf_regs_mapping): New function. * libebl/libeblP.h (struct ebl): Add caching fields to remove the need to repeat a sample_perf_regs_mapping() computation for every frame when the perf_regs_mask is consistent. * backends/Makefile.am: Remove no-longer-needed linux-perf-regs.c. * backends/i386_init.c (i386_init): Renamed sample_* functions, added cached_regs_mapping and related fields/functions. * backends/i386_initreg_sample.c (i386_sample_base_addr): Removed. (i386_sample_pc): Removed. (i386_sample_sp_pc): New function combining the removed functions. (i386_set_initial_registers_sample): Removed. (i386_sample_perf_regs_mapping): New function translating perf_regs_mask to regs_mapping array. * backends/linux-perf-regs.c: Removed as perf_sample_find_reg is no longer needed. * backends/x86_64_init.c (x86_64_init): Renamed sample_* functions, added cached_regs_mapping and related fields/functions. * backends/x86_64_initreg_sample.c (x86_64_sample_base_addr): Removed. (x86_64_sample_pc): Removed. (x86_64_sample_sp_pc): New function combining the removed functions. (x86_64_set_initial_registers_sample): Removed. (x86_64_sample_perf_regs_mapping): New function translating perf_regs_mask to regs_mapping array. * backends/x86_initreg_sample.c (x86_set_initial_registers_sample): Removed. (x86_sample_sp_pc): New function. (x86_sample_perf_regs_mapping): New function translating perf_regs_mask to regs_mapping array. Signed-off-by: Serhei Makarov <[email protected]>
Diffstat (limited to 'backends')
-rw-r--r--backends/Makefile.am2
-rw-r--r--backends/i386_init.c9
-rw-r--r--backends/i386_initreg_sample.c72
-rw-r--r--backends/linux-perf-regs.c48
-rw-r--r--backends/x86_64_init.c9
-rw-r--r--backends/x86_64_initreg_sample.c70
-rw-r--r--backends/x86_initreg_sample.c108
7 files changed, 126 insertions, 192 deletions
diff --git a/backends/Makefile.am b/backends/Makefile.am
index 8ccbdb50..7a820df0 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -121,7 +121,7 @@ am_libebl_backends_pic_a_OBJECTS = $(libebl_backends_a_SOURCES:.c=.os)
noinst_HEADERS = libebl_CPU.h libebl_PERF_FLAGS.h common-reloc.c \
linux-core-note.c x86_corenote.c \
- linux-perf-regs.c x86_initreg_sample.c
+ x86_initreg_sample.c
EXTRA_DIST = $(modules:=_reloc.def)
diff --git a/backends/i386_init.c b/backends/i386_init.c
index e64ef6ed..a980e71a 100644
--- a/backends/i386_init.c
+++ b/backends/i386_init.c
@@ -60,10 +60,13 @@ i386_init (Elf *elf __attribute__ ((unused)),
(Likely an artifact of reusing that header between i386/x86_64.) */
eh->frame_nregs = 9;
HOOK (eh, set_initial_registers_tid);
- HOOK (eh, set_initial_registers_sample);
- HOOK (eh, sample_base_addr);
- HOOK (eh, sample_pc);
+ /* set_initial_registers_sample is default ver */
+ HOOK (eh, sample_sp_pc);
+ HOOK (eh, sample_perf_regs_mapping);
eh->perf_frame_regs_mask = PERF_FRAME_REGISTERS_I386;
+ eh->cached_perf_regs_mask = 0;
+ eh->cached_regs_mapping = NULL;
+ eh->cached_n_regs_mapping = -1;
HOOK (eh, unwind);
return eh;
diff --git a/backends/i386_initreg_sample.c b/backends/i386_initreg_sample.c
index 677393c9..94955191 100644
--- a/backends/i386_initreg_sample.c
+++ b/backends/i386_initreg_sample.c
@@ -31,6 +31,7 @@
#endif
#include <stdlib.h>
+#include <assert.h>
#if (defined __i386__ || defined __x86_64__) && defined(__linux__)
# include <linux/perf_event.h>
# include <asm/perf_regs.h>
@@ -40,69 +41,26 @@
#include "libebl_CPU.h"
#include "libebl_PERF_FLAGS.h"
#if (defined __i386__ || defined __x86_64__) && defined(__linux__)
-# include "linux-perf-regs.c"
# include "x86_initreg_sample.c"
#endif
-/* Register ordering cf. linux arch/x86/include/uapi/asm/perf_regs.h,
- enum perf_event_x86_regs: */
-Dwarf_Word
-i386_sample_base_addr (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask,
- /* XXX hypothetically needed if abi varies
- between samples in the same process;
- not needed on x86 */
- uint32_t abi __attribute__((unused)))
-{
-#if (!defined __i386__ && !defined __x86_64__) || !defined(__linux__)
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- return 0;
-#else /* __i386__ || __x86_64__ */
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- return perf_sample_find_reg (regs, n_regs, regs_mask,
- 7 /* index into perf_event_x86_regs */);
-#endif
-}
-
-Dwarf_Word
-i386_sample_pc (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask,
- uint32_t abi __attribute__((unused)))
+bool
+i386_sample_sp_pc (const Dwarf_Word *regs, uint32_t n_regs,
+ const int *regs_mapping, uint32_t n_regs_mapping,
+ Dwarf_Word *sp, Dwarf_Word *pc)
{
-#if (!defined __i386__ && !defined __x86_64__) || !defined(__linux__)
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- return 0;
-#else /* __i386__ || __x86_64__ */
- return perf_sample_find_reg (regs, n_regs, regs_mask,
- 8 /* index into perf_event_x86_regs */);
-#endif
+ /* XXX for dwarf_regs indices, compare i386_initreg.c */
+ return x86_sample_sp_pc (regs, n_regs, regs_mapping, n_regs_mapping,
+ sp, 4 /* index of sp in dwarf_regs */,
+ pc, 8 /* index of pc in dwarf_regs */);
}
bool
-i386_set_initial_registers_sample (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask, uint32_t abi,
- ebl_tid_registers_t *setfunc,
- void *arg)
+i386_sample_perf_regs_mapping (Ebl *ebl,
+ uint64_t perf_regs_mask, uint32_t abi,
+ const int **regs_mapping,
+ size_t *n_regs_mapping)
{
-#if (!defined __i386__ && !defined __x86_64__) || !defined(__linux__)
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- (void)abi;
- (void)setfunc;
- (void)arg;
- return false;
-#else /* __i386__ || __x86_64__ */
- Dwarf_Word dwarf_regs[9];
- if (!x86_set_initial_registers_sample (regs, n_regs, regs_mask,
- abi, dwarf_regs, 9))
- return false;
- return setfunc (0, 9, dwarf_regs, arg);
-#endif
+ return x86_sample_perf_regs_mapping (ebl, perf_regs_mask, abi,
+ regs_mapping, n_regs_mapping);
}
diff --git a/backends/linux-perf-regs.c b/backends/linux-perf-regs.c
deleted file mode 100644
index 22ad67c6..00000000
--- a/backends/linux-perf-regs.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Common pieces for handling registers in a linux perf_events sample.
- Copyright (C) 2025 Red Hat, Inc.
- 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/>. */
-
-static Dwarf_Word
-perf_sample_find_reg (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask,
- int target)
-{
- int j, k; uint64_t bit;
- for (j = 0, k = 0, bit = 1; k < PERF_REG_X86_64_MAX; k++, bit <<= 1)
- {
- if (bit & regs_mask) {
- if (n_regs <= (uint32_t) j)
- return 0; /* regs_mask count doesn't match n_regs */
- if (k == target)
- return regs[j];
- if (k > target)
- return 0; /* regs_mask doesn't include desired reg */
- j++;
- }
- }
- return 0;
-}
diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c
index 6a1cbc4b..5f929758 100644
--- a/backends/x86_64_init.c
+++ b/backends/x86_64_init.c
@@ -63,10 +63,13 @@ x86_64_init (Elf *elf __attribute__ ((unused)),
/* gcc/config/ #define DWARF_FRAME_REGISTERS. */
eh->frame_nregs = 17;
HOOK (eh, set_initial_registers_tid);
- HOOK (eh, set_initial_registers_sample);
- HOOK (eh, sample_base_addr);
- HOOK (eh, sample_pc);
+ /* set_initial_registers_sample is default ver */
+ HOOK (eh, sample_sp_pc);
+ HOOK (eh, sample_perf_regs_mapping);
eh->perf_frame_regs_mask = PERF_FRAME_REGISTERS_X86_64;
+ eh->cached_perf_regs_mask = 0;
+ eh->cached_regs_mapping = NULL;
+ eh->cached_n_regs_mapping = -1;
HOOK (eh, unwind);
HOOK (eh, check_reloc_target_type);
diff --git a/backends/x86_64_initreg_sample.c b/backends/x86_64_initreg_sample.c
index 48d14bc8..9dd708c9 100644
--- a/backends/x86_64_initreg_sample.c
+++ b/backends/x86_64_initreg_sample.c
@@ -31,6 +31,7 @@
#endif
#include <stdlib.h>
+#include <assert.h>
#if defined(__x86_64__) && defined(__linux__)
# include <linux/perf_event.h>
# include <asm/perf_regs.h>
@@ -40,67 +41,26 @@
#include "libebl_CPU.h"
#include "libebl_PERF_FLAGS.h"
#if defined(__x86_64__) && defined(__linux__)
-# include "linux-perf-regs.c"
# include "x86_initreg_sample.c"
#endif
-/* Register ordering cf. linux arch/x86/include/uapi/asm/perf_regs.h,
- enum perf_event_x86_regs: */
-Dwarf_Word
-x86_64_sample_base_addr (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask,
- /* XXX hypothetically needed if abi varies
- between samples in the same process;
- not needed on x86*/
- uint32_t abi __attribute__((unused)))
-{
-#if !defined(__x86_64__) || !defined(__linux__)
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- return 0;
-#else /* __x86_64__ */
- return perf_sample_find_reg (regs, n_regs, regs_mask,
- 7 /* index into perf_event_x86_regs */);
-#endif
-}
-
-Dwarf_Word
-x86_64_sample_pc (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask,
- uint32_t abi __attribute__((unused)))
+bool
+x86_64_sample_sp_pc (const Dwarf_Word *regs, uint32_t n_regs,
+ const int *regs_mapping, uint32_t n_regs_mapping,
+ Dwarf_Word *sp, Dwarf_Word *pc)
{
-#if !defined(__x86_64__) || !defined(__linux__)
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- return 0;
-#else /* __x86_64__ */
- return perf_sample_find_reg (regs, n_regs, regs_mask,
- 8 /* index into perf_event_x86_regs */);
-#endif
+ /* XXX for dwarf_regs indices, compare x86_64_initreg.c */
+ return x86_sample_sp_pc (regs, n_regs, regs_mapping, n_regs_mapping,
+ sp, 7 /* index of sp in dwarf_regs */,
+ pc, 16 /* index of pc in dwarf_regs */);
}
bool
-x86_64_set_initial_registers_sample (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask, uint32_t abi,
- ebl_tid_registers_t *setfunc,
- void *arg)
+x86_64_sample_perf_regs_mapping (Ebl *ebl,
+ uint64_t perf_regs_mask, uint32_t abi,
+ const int **regs_mapping,
+ size_t *n_regs_mapping)
{
-#if !defined(__x86_64__) || !defined(__linux__)
- (void)regs;
- (void)n_regs;
- (void)regs_mask;
- (void)abi;
- (void)setfunc;
- (void)arg;
- return false;
-#else /* __x86_64__ */
- Dwarf_Word dwarf_regs[17];
- if (!x86_set_initial_registers_sample (regs, n_regs, regs_mask,
- abi, dwarf_regs, 9))
- return false;
- return setfunc (0, 17, dwarf_regs, arg);
-#endif
+ return x86_sample_perf_regs_mapping (ebl, perf_regs_mask, abi,
+ regs_mapping, n_regs_mapping);
}
-
diff --git a/backends/x86_initreg_sample.c b/backends/x86_initreg_sample.c
index 8d6b471b..47cd91c2 100644
--- a/backends/x86_initreg_sample.c
+++ b/backends/x86_initreg_sample.c
@@ -1,4 +1,4 @@
-/* x86 linux perf_events register handling, pieces common to x86-64 and i386.
+/* x86 stack sample register handling, pieces common to x86-64 and i386.
Copyright (C) 2025 Red Hat, Inc.
This file is part of elfutils.
@@ -27,13 +27,52 @@
not, see <http://www.gnu.org/licenses/>. */
static bool
-x86_set_initial_registers_sample (const Dwarf_Word *regs, uint32_t n_regs,
- uint64_t regs_mask, uint32_t abi,
- Dwarf_Word *dwarf_regs, int expected_regs)
+x86_sample_sp_pc (const Dwarf_Word *regs, uint32_t n_regs,
+ const int *regs_mapping, uint32_t n_regs_mapping,
+ Dwarf_Word *sp, uint sp_index /* into dwarf_regs */,
+ Dwarf_Word *pc, uint pc_index /* into dwarf_regs */)
{
-#if (!defined __i386__ && !defined __x86_64__) || !defined(__linux__)
+ if (sp != NULL) *sp = 0;
+ if (pc != NULL) *pc = 0;
+#if !defined(__x86_64__)
+ (void)regs;
+ (void)n_regs;
+ (void)regs_mapping;
+ (void)n_regs_mapping;
return false;
-#else /* __i386__ || __x86_64__ */
+#else /* __x86_64__ */
+ /* TODO: Register locations could be cached and rechecked on a
+ fastpath without needing to loop? */
+ int j, need_sp = (sp != NULL), need_pc = (pc != NULL);
+ for (j = 0; (need_sp || need_pc) && n_regs_mapping > (uint32_t)j; j++)
+ {
+ if (n_regs < (uint32_t)j) break;
+ if (need_sp && regs_mapping[j] == (int)sp_index)
+ {
+ *sp = regs[j]; need_sp = false;
+ }
+ if (need_pc && regs_mapping[j] == (int)pc_index)
+ {
+ *pc = regs[j]; need_pc = false;
+ }
+ }
+ return (!need_sp && !need_pc);
+#endif
+}
+
+static bool
+x86_sample_perf_regs_mapping (Ebl *ebl,
+ uint64_t perf_regs_mask, uint32_t abi,
+ const int **regs_mapping,
+ size_t *n_regs_mapping)
+{
+ if (perf_regs_mask != 0 && ebl->cached_perf_regs_mask == perf_regs_mask)
+ {
+ *regs_mapping = ebl->cached_regs_mapping;
+ *n_regs_mapping = ebl->cached_n_regs_mapping;
+ return true;
+ }
+
/* The following facts are needed to translate x86 registers correctly:
- perf register order seen in linux arch/x86/include/uapi/asm/perf_regs.h
The registers array is built in the same order as the enum!
@@ -52,39 +91,58 @@ x86_set_initial_registers_sample (const Dwarf_Word *regs, uint32_t n_regs,
bool is_abi32 = (abi == PERF_SAMPLE_REGS_ABI_32);
/* Locations of dwarf_regs in the perf_event_x86_regs enum order,
- not the regs[i] array (which will include a subset of the regs): */
+ not the regs[] array (which will include a subset of the regs): */
static const int regs_i386[] = {0, 2, 3, 1, 7/*sp*/, 6, 4, 5, 8/*ip*/};
static const int regs_x86_64[] = {0, 3, 2, 1, 4, 5, 6, 7/*sp*/,
16/*r8 after flags+segment*/, 17, 18, 19, 20, 21, 22, 23,
8/*ip*/};
const int *dwarf_to_perf = is_abi32 ? regs_i386 : regs_x86_64;
- /* Locations of perf_regs in the regs[] array, according to regs_mask: */
- int perf_to_regs[PERF_REG_X86_64_MAX];
- uint64_t expected_mask = is_abi32 ? PERF_FRAME_REGISTERS_I386 : PERF_FRAME_REGISTERS_X86_64;
- int j, k; uint64_t bit;
- /* TODO: Is it worth caching this perf_to_regs computation as long
- as regs_mask is kept the same across repeated calls? */
- for (j = 0, k = 0, bit = 1; k < PERF_REG_X86_64_MAX; k++, bit <<= 1)
+ /* Count bits and allocate regs_mapping: */
+ int j, k, kmax, count; uint64_t bit;
+ for (k = 0, kmax = -1, count = 0, bit = 1;
+ k < PERF_REG_X86_64_MAX; k++, bit <<= 1)
{
- if ((bit & expected_mask) && (bit & regs_mask)) {
- if (n_regs <= (uint32_t)j)
- return false; /* regs_mask count doesn't match n_regs */
- perf_to_regs[k] = j;
- j++;
- } else {
- perf_to_regs[k] = -1;
+ if ((bit & perf_regs_mask)) {
+ count++;
+ kmax = k;
}
}
+ ebl->cached_perf_regs_mask = perf_regs_mask;
+ ebl->cached_regs_mapping = (int *)calloc (count, sizeof(int));
+ ebl->cached_n_regs_mapping = count;
- for (int i = 0; i < expected_regs; i++)
+ /* Locations of perf_regs in the regs[] array, according to
+ perf_regs_mask: */
+ int perf_to_regs[PERF_REG_X86_64_MAX];
+ uint64_t expected_mask = is_abi32 ?
+ PERF_FRAME_REGISTERS_I386 : PERF_FRAME_REGISTERS_X86_64;
+ for (j = 0, k = 0, bit = 1; k <= kmax; k++, bit <<= 1)
+ {
+ if ((bit & expected_mask) && (bit & perf_regs_mask))
+ {
+ perf_to_regs[k] = j;
+ j++;
+ }
+ else
+ {
+ perf_to_regs[k] = -1;
+ }
+ }
+ if (j > (int)ebl->cached_n_regs_mapping)
+ return false;
+
+ /* Locations of perf_regs in the dwarf_regs array, according to
+ perf_regs_mask and perf_to_regs[]: */
+ for (size_t i = 0; i < ebl->frame_nregs; i++)
{
k = dwarf_to_perf[i];
j = perf_to_regs[k];
if (j < 0) continue;
- if (n_regs <= (uint32_t)j) continue;
- dwarf_regs[i] = regs[j];
+ ebl->cached_regs_mapping[j] = i;
}
+
+ *regs_mapping = ebl->cached_regs_mapping;
+ *n_regs_mapping = ebl->cached_n_regs_mapping;
return true;
-#endif /* __i386__ || __x86_64__ */
}