Changes for v2: - Merged commit 11 into commit 1 (both sample_base_addr/sample_pc and set_initial_registers_sample make sense to introduce in the same commit).
- Added i386 backend and factored out common code. * * * First patch of a series that reworks eu-stacktrace functionality into a library interface for other profiling tools. * * * As it happens, Linux perf_events and DWARF can prescribe completely different layouts for the register file, requiring non-obvious code for translation. This makes sense to put in libebl if we want profilers to handle perf sample data with elfutils. Start with the x86_64/i386 implementation taken from eu-stacktrace. The code has been generalized to accept other perf register masks besides the 'preferred' one. * backends/Makefile.am (i386_SRCS): Add i386_initreg_sample.c. (x86_64_SRCS): Add x86_64_initreg_sample.c. (noinst_HEADERS): Add libebl_PERF_FLAGS.h, linux-perf-regs.c, x86_initreg_sample.c. * backends/libebl_PERF_FLAGS.h: New file. * backends/linux-perf-regs.c: New file. (perf_sample_find_reg): Index into a perf register array whose contents are described by regs_mask. The target index is the index of the register in the enum defined by linux arch/N/include/uapi/asm/perf_regs.h on arch N. * backends/x86_initreg_sample.c: New file, implements a generalized version of the eu-stacktrace register shuffling for x86-64/i386. * backends/x86_64_initreg_sample.c: New file, specializes x86_initreg_sample.c for x86-64. * backends/i386_initreg_sample.c: New file, specializes i386_initreg_sample.c for i386. * backends/x86_64_init.c (x86_64_init): Add initialization for set_initial_registers_sample, perf_frame_regs_mask, sample_base_addr, sample_pc. * backends/i386_init.c (i386_init): Add initialization for set_initial_registers_sample, perf_frame_regs_mask, sample_base_addr, sample_pc. * libebl/Makefile.am (libebl_a_SOURCES): Add eblinitreg_sample.c. * libebl/ebl-hooks.h (set_initial_registers_sample): New hook. (sample_base_addr): Ditto. (sample_pc): Ditto. * libebl/eblinitreg_sample.c: New file, implements ebl interface to set_initial_registers_sample, sample_base_addr, sample_pc backend hooks. * libebl/libebl.h (ebl_set_initial_registers_sample): New function. (ebl_perf_frame_regs_mask): New function. (ebl_sample_base_addr): New function. (ebl_sample_pc): New function. * libebl/libeblP.h (struct ebl): Add perf_frame_regs_mask field giving the preferred register mask. --- backends/Makefile.am | 10 +-- backends/i386_init.c | 10 ++- backends/i386_initreg_sample.c | 108 +++++++++++++++++++++++++++++++ backends/libebl_PERF_FLAGS.h | 58 +++++++++++++++++ backends/linux-perf-regs.c | 48 ++++++++++++++ backends/x86_64_init.c | 7 +- backends/x86_64_initreg_sample.c | 106 ++++++++++++++++++++++++++++++ backends/x86_initreg_sample.c | 90 ++++++++++++++++++++++++++ libebl/Makefile.am | 4 +- libebl/ebl-hooks.h | 18 +++++- libebl/eblinitreg_sample.c | 72 +++++++++++++++++++++ libebl/libebl.h | 31 ++++++++- libebl/libeblP.h | 7 +- 13 files changed, 557 insertions(+), 12 deletions(-) create mode 100644 backends/i386_initreg_sample.c create mode 100644 backends/libebl_PERF_FLAGS.h create mode 100644 backends/linux-perf-regs.c create mode 100644 backends/x86_64_initreg_sample.c create mode 100644 backends/x86_initreg_sample.c create mode 100644 libebl/eblinitreg_sample.c diff --git a/backends/Makefile.am b/backends/Makefile.am index 540d0c6c..8ccbdb50 100644 --- a/backends/Makefile.am +++ b/backends/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to create Makefile.in ## -## Copyright (C) 2000-2010, 2013, 2014 Red Hat, Inc. +## Copyright (C) 2000-2010, 2013, 2014, 2025 Red Hat, Inc. ## Copyright (C) 2012 Tilera Corporation ## This file is part of elfutils. ## @@ -41,13 +41,13 @@ modules = i386 sh x86_64 ia64 alpha arm aarch64 sparc ppc ppc64 s390 \ i386_SRCS = i386_init.c i386_symbol.c i386_corenote.c i386_cfi.c \ i386_retval.c i386_regs.c i386_auxv.c \ - i386_initreg.c i386_unwind.c + i386_initreg.c i386_initreg_sample.c i386_unwind.c sh_SRCS = sh_init.c sh_symbol.c sh_corenote.c sh_regs.c sh_retval.c x86_64_SRCS = x86_64_init.c x86_64_symbol.c x86_64_corenote.c x86_64_cfi.c \ x86_64_retval.c x86_64_regs.c x86_64_initreg.c \ - x86_64_unwind.c x32_corenote.c + x86_64_initreg_sample.c x86_64_unwind.c x32_corenote.c ia64_SRCS = ia64_init.c ia64_symbol.c ia64_regs.c ia64_retval.c @@ -119,7 +119,9 @@ libebl_backends_a_SOURCES = $(i386_SRCS) $(sh_SRCS) $(x86_64_SRCS) \ libebl_backends_pic_a_SOURCES = am_libebl_backends_pic_a_OBJECTS = $(libebl_backends_a_SOURCES:.c=.os) -noinst_HEADERS = libebl_CPU.h common-reloc.c linux-core-note.c x86_corenote.c +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 EXTRA_DIST = $(modules:=_reloc.def) diff --git a/backends/i386_init.c b/backends/i386_init.c index 579e5fad..e64ef6ed 100644 --- a/backends/i386_init.c +++ b/backends/i386_init.c @@ -1,5 +1,5 @@ /* Initialization of i386 specific backend library. - Copyright (C) 2000-2009, 2013, 2017 Red Hat, Inc. + Copyright (C) 2000-2009, 2013, 2017, 2025 Red Hat, Inc. This file is part of elfutils. Written by Ulrich Drepper <drep...@redhat.com>, 2000. @@ -34,6 +34,7 @@ #define BACKEND i386_ #define RELOC_PREFIX R_386_ #include "libebl_CPU.h" +#include "libebl_PERF_FLAGS.h" /* This defines the common reloc hooks based on i386_reloc.def. */ #include "common-reloc.c" @@ -55,9 +56,14 @@ i386_init (Elf *elf __attribute__ ((unused)), HOOK (eh, auxv_info); HOOK (eh, disasm); HOOK (eh, abi_cfi); - /* gcc/config/ #define DWARF_FRAME_REGISTERS. For i386 it is 17, why? */ + /* gcc/config/ #define DWARF_FRAME_REGISTERS. For i386 it is 17, why? + (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); + eh->perf_frame_regs_mask = PERF_FRAME_REGISTERS_I386; HOOK (eh, unwind); return eh; diff --git a/backends/i386_initreg_sample.c b/backends/i386_initreg_sample.c new file mode 100644 index 00000000..3df74412 --- /dev/null +++ b/backends/i386_initreg_sample.c @@ -0,0 +1,108 @@ +/* Populate process registers from 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/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdlib.h> +#if (defined __i386__ || defined __x86_64__) && defined(__linux__) +# include <linux/perf_event.h> +# include <asm/perf_regs.h> +#endif + +#define BACKEND i386_ +#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))) +{ +#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 +} + +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) +{ +#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 +} diff --git a/backends/libebl_PERF_FLAGS.h b/backends/libebl_PERF_FLAGS.h new file mode 100644 index 00000000..2ed45f0f --- /dev/null +++ b/backends/libebl_PERF_FLAGS.h @@ -0,0 +1,58 @@ +/* Linux perf_events sample_regs_user flags required for unwinding. + Internal only; elfutils library users should use ebl_perf_frame_regs_mask(). + + 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/>. */ + +#ifndef _LIBEBL_PERF_FLAGS_H +#define _LIBEBL_PERF_FLAGS_H 1 + +#if defined(__linux__) +# include <asm/perf_regs.h> +#endif + +#if defined(_ASM_X86_PERF_REGS_H) +/* See the code in x86_initreg_sample.c for list of required regs and + linux arch/.../include/asm/ptrace.h for matching pt_regs struct. */ +#define REG(R) (1ULL << PERF_REG_X86_ ## R) +/* FLAGS and segment regs are excluded from the following masks, + since they're not needed for unwinding. */ +#define PERF_FRAME_REGISTERS_I386 (REG(AX) | REG(BX) | REG(CX) | REG(DX) \ + | REG(SI) | REG(DI) | REG(BP) | REG(SP) | REG(IP)) +#define PERF_FRAME_REGISTERS_X86_64 (PERF_FRAME_REGISTERS_I386 | REG(R8) \ + | REG(R9) | REG(R10) | REG(R11) | REG(R12) | REG(R13) | REG(R14) | REG(R15)) +/* Register ordering defined in linux arch/x86/include/uapi/asm/perf_regs.h; + see the code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs() + and note how regs are added in the same order as the perf_regs.h enum. */ +#else +/* Since asm/perf_regs.h gives the register layout for a different arch, + we can't unwind x86_64 frames. */ +#define PERF_FRAME_REGISTERS_I386 0 +#define PERF_FRAME_REGISTERS_X86_64 0 +#endif + +#endif /* libebl_PERF_FLAGS.h */ diff --git a/backends/linux-perf-regs.c b/backends/linux-perf-regs.c new file mode 100644 index 00000000..22ad67c6 --- /dev/null +++ b/backends/linux-perf-regs.c @@ -0,0 +1,48 @@ +/* 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 be965fa6..6a1cbc4b 100644 --- a/backends/x86_64_init.c +++ b/backends/x86_64_init.c @@ -1,5 +1,5 @@ /* Initialization of x86-64 specific backend library. - Copyright (C) 2002-2009, 2013, 2018 Red Hat, Inc. + Copyright (C) 2002-2009, 2013, 2018, 2025 Red Hat, Inc. Copyright (C) H.J. Lu <hjl.to...@gmail.com>, 2015. This file is part of elfutils. Written by Ulrich Drepper <drep...@redhat.com>, 2002. @@ -35,6 +35,7 @@ #define BACKEND x86_64_ #define RELOC_PREFIX R_X86_64_ #include "libebl_CPU.h" +#include "libebl_PERF_FLAGS.h" /* This defines the common reloc hooks based on x86_64_reloc.def. */ #include "common-reloc.c" @@ -62,6 +63,10 @@ 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); + eh->perf_frame_regs_mask = PERF_FRAME_REGISTERS_X86_64; 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 new file mode 100644 index 00000000..71d594bd --- /dev/null +++ b/backends/x86_64_initreg_sample.c @@ -0,0 +1,106 @@ +/* Populate process registers from 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/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdlib.h> +#if defined(__x86_64__) && defined(__linux__) +# include <linux/perf_event.h> +# include <asm/perf_regs.h> +#endif + +#define BACKEND x86_64_ +#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))) +{ +#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 +} + +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) +{ +#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 +} + diff --git a/backends/x86_initreg_sample.c b/backends/x86_initreg_sample.c new file mode 100644 index 00000000..e323bada --- /dev/null +++ b/backends/x86_initreg_sample.c @@ -0,0 +1,90 @@ +/* x86 linux perf_events register handling, pieces common to x86-64 and i386. + 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 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) +{ +#if (!defined __i386__ && !defined __x86_64__) || !defined(__linux__) + return false; +#else /* __i386__ || __x86_64__ */ + /* 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! + (See the code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs().) + - EBL PERF_FRAME_REGS_MASK specifies all registers except segment and + flags. However, regs_mask might be a different set of registers. + Again, regs_mask bits are in asm/perf_regs.h enum order. + - dwarf register order seen in elfutils backends/{x86_64,i386}_initreg.c + (matching pt_regs struct in linux arch/x86/include/asm/ptrace.h) + and it's a fairly different register order! + + For comparison, you can study codereview.qt-project.org/gitweb?p=qt-creator/perfparser.git;a=blob;f=app/perfregisterinfo.cpp;hb=HEAD + and follow the code which uses those tables of magic numbers. + But it's better to follow original sources of truth for this. */ + + 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): */ + 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(REVIEW): 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) + { + 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; + } + } + + for (int i = 0; i < expected_regs; 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]; + } + return true; +#endif /* __i386__ || __x86_64__ */ +} diff --git a/libebl/Makefile.am b/libebl/Makefile.am index ea092b5a..3df12ce2 100644 --- a/libebl/Makefile.am +++ b/libebl/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to create Makefile.in ## -## Copyright (C) 2000-2010, 2013, 2016, 2017 Red Hat, Inc. +## Copyright (C) 2000-2010, 2013, 2016, 2017, 2025 Red Hat, Inc. ## This file is part of elfutils. ## ## This file is free software; you can redistribute it and/or modify @@ -51,7 +51,7 @@ libebl_a_SOURCES = eblopenbackend.c eblclosebackend.c eblreloctypename.c \ eblbsspltp.c eblretval.c eblreginfo.c eblnonerelocp.c \ eblrelativerelocp.c eblsysvhashentrysize.c eblauxvinfo.c \ eblcheckobjattr.c ebl_check_special_section.c \ - eblabicfi.c eblstother.c eblinitreg.c \ + eblabicfi.c eblstother.c eblinitreg.c eblinitreg_sample.c \ ebldwarftoregno.c eblnormalizepc.c eblunwind.c \ eblresolvesym.c eblcheckreloctargettype.c \ ebl_data_marker_symbol.c diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h index d6437e53..05474fbc 100644 --- a/libebl/ebl-hooks.h +++ b/libebl/ebl-hooks.h @@ -1,5 +1,5 @@ /* Backend hook signatures internal interface for libebl. - Copyright (C) 2000-2011, 2013, 2014, 2016, 2017 Red Hat, Inc. + Copyright (C) 2000-2011, 2013, 2014, 2016, 2017, 2025 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -158,6 +158,22 @@ bool EBLHOOK(set_initial_registers_tid) (pid_t tid, ebl_tid_registers_t *setfunc, void *arg); +/* Set process data from a perf_events sample and call SETFUNC one or more times. + Method should be present only when EBL_PERF_FRAME_REGS_MASK > 0, otherwise the + backend doesn't support unwinding from perf_events data. */ +bool EBLHOOK(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); + +/* Extract the stack address from a perf_events register sample. */ +Dwarf_Word EBLHOOK(sample_base_addr) (const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi); + +/* Extract the instruction pointer from a perf_events register sample. */ +Dwarf_Word EBLHOOK(sample_pc) (const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi); + /* Convert *REGNO as is in DWARF to a lower range suitable for Dwarf_Frame->REGS indexing. */ bool EBLHOOK(dwarf_to_regno) (Ebl *ebl, unsigned *regno); diff --git a/libebl/eblinitreg_sample.c b/libebl/eblinitreg_sample.c new file mode 100644 index 00000000..53244d1e --- /dev/null +++ b/libebl/eblinitreg_sample.c @@ -0,0 +1,72 @@ +/* Populate process Dwfl_Frame from 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/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <libeblP.h> +#include <assert.h> + +Dwarf_Word +ebl_sample_base_addr (Ebl *ebl, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi) +{ + assert (ebl->sample_base_addr != NULL); + return ebl->sample_base_addr (regs, n_regs, regs_mask, abi); +} + +Dwarf_Word +ebl_sample_pc (Ebl *ebl, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi) +{ + assert (ebl->sample_pc != NULL); + return ebl->sample_pc (regs, n_regs, regs_mask, abi); +} + +bool +ebl_set_initial_registers_sample (Ebl *ebl, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi, + ebl_tid_registers_t *setfunc, + void *arg) +{ + /* If set_initial_registers_sample is unsupported then PERF_FRAME_REGS_MASK is zero. */ + assert (ebl->set_initial_registers_sample != NULL); + return ebl->set_initial_registers_sample (regs, n_regs, regs_mask, abi, setfunc, arg); +} + +uint64_t +ebl_perf_frame_regs_mask (Ebl *ebl) +{ + /* ebl is declared NN */ + return ebl->perf_frame_regs_mask; +} diff --git a/libebl/libebl.h b/libebl/libebl.h index 731001d3..a64d70e9 100644 --- a/libebl/libebl.h +++ b/libebl/libebl.h @@ -1,5 +1,5 @@ /* Interface for libebl. - Copyright (C) 2000-2010, 2013, 2014, 2015, 2016, 2017 Red Hat, Inc. + Copyright (C) 2000-2010, 2013, 2014, 2015, 2016, 2017, 2025 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -340,6 +340,35 @@ extern bool ebl_set_initial_registers_tid (Ebl *ebl, extern size_t ebl_frame_nregs (Ebl *ebl) __nonnull_attribute__ (1); +/* Callback to set process data from a linux perf_events sample. + EBL architecture has to have EBL_PERF_FRAME_REGS_MASK > 0, otherwise the + backend doesn't support unwinding from perf_events sample data. */ +extern bool ebl_set_initial_registers_sample (Ebl *ebl, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi, + ebl_tid_registers_t *setfunc, + void *arg) + __nonnull_attribute__ (1, 2, 6); + +/* Extract the stack address from a perf_events register sample. */ +Dwarf_Word ebl_sample_base_addr (Ebl *ebl, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi) + __nonnull_attribute__ (1, 2); + +/* Extract the instruction pointer from a perf_events register sample. */ +Dwarf_Word ebl_sample_pc (Ebl *ebl, + const Dwarf_Word *regs, uint32_t n_regs, + uint64_t regs_mask, uint32_t abi) + __nonnull_attribute__ (1, 2); + + +/* Preferred sample_regs_user mask to request from linux perf_events + to allow unwinding on EBL architecture. Omitting some of these + registers may result in failed or inaccurate unwinding. */ +extern uint64_t ebl_perf_frame_regs_mask (Ebl *ebl) + __nonnull_attribute__ (1); + /* Offset to apply to the value of the return_address_register, as fetched from a Dwarf CFI. This is used by some backends, where the return_address_register actually contains the call address. */ diff --git a/libebl/libeblP.h b/libebl/libeblP.h index c408ed97..be14cc20 100644 --- a/libebl/libeblP.h +++ b/libebl/libeblP.h @@ -1,5 +1,5 @@ /* Internal definitions for interface for libebl. - Copyright (C) 2000-2009, 2013, 2014 Red Hat, Inc. + Copyright (C) 2000-2009, 2013, 2014, 2025 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -60,6 +60,11 @@ struct ebl Ebl architecture can unwind iff FRAME_NREGS > 0. */ size_t frame_nregs; + /* Preferred sample_regs_user mask to request from linux perf_events + to allow unwinding. Ebl architecture supports unwinding from + perf_events sample data iff PERF_FRAME_REGS_MASK > 0. */ + uint64_t perf_frame_regs_mask; + /* Offset to apply to the value of the return_address_register, as fetched from a Dwarf CFI. This is used by some backends, where the return_address_register actually contains the call -- 2.47.0