On Thu, Apr 24, 2025 at 5:49 PM Serhei Makarov <ser...@serhei.io> wrote:
>
> 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)))

Some extra whitespace here.

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

Missing space between function name and args here and elsewhere.

> +#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? */

Let's leave this as-is for now and after the release test if caching
is worth it.

> +  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

Besides a couple code formatting issues mentioned above, these arch
and profiler-specific implementations LGTM.

I have not tested this patch series in depth and it does not add any
tests to the testsuite. However you mentioned on IRC you have tested
this by hand. This is ok since eu-stacktrace and the new API added in
this series are marked as experimental, so we don't necessarily
require automated tests yet. Of course we will have to add to the
testsuite before removing the "experimental" labels.

Aaron

Reply via email to