Re: [PATCH 01/13] libebl [1/13]: api for perf register handling, start with x86_64

2025-03-19 Thread Mark Wielaard
Hi Serhei,

On Sun, 2025-03-16 at 19:12 -0400, Serhei Makarov wrote:
> 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 implementation taken from eu-stacktrace. The
> code has been generalized to accept other perf register masks besides
> the 'preferred' one.
> 
> * backends/Makefile.am (x86_64_SRCS): Add x86_64_initreg_sample.c.
> * backends/libebl_PERF_FLAGS.h: New file.
> * backends/x86_64_initreg_sample.c: New file, implements a generalized
>   version of the eu-stacktrace register shuffling for x86_64.
> * backends/x86_64_init.c (x86_64_init): Add initialization for
>   set_initial_registers_sample, perf_frame_regs_mask.
> * libebl/Makefile.am (libebl_a_SOURCES): Add eblinitreg_sample.c.
> * libebl/ebl-hooks.h (set_initial_registers_sample): New hook.
> * libebl/eblinitreg_sample.c: New file, implements ebl interface to
>   set_initial_registers_sample backend hook.
> * libebl/libebl.h (ebl_set_initial_registers_sample): New function.
>   (ebl_perf_frame_regs_mask): New function.
> * libebl/libeblP.h (struct ebl): Add perf_frame_regs_mask field
>   giving the preferred register mask.

Just some random observations, I don't believe I really understand
where this is going yet. Will have to read the rest of the patchset
first.
> +
> +#if defined(_ASM_X86_PERF_REGS_H)
> +/* XXX See the code in x86_64_initreg.c for the list of required regs
> +   and linux arch/.../include/asm/ptrace.h for matching pt_regs struct.  */
> +#define REG(R) (1ULL << PERF_REG_X86_ ## R)
> +/* XXX FLAGS and segment regs are excluded from the following masks: */
> +#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))
> +/* XXX 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 is for a different arch, we can't unwind x86_64 
> frames.  */
> +#define PERF_FRAME_REGISTERS_X86_64 0
> +#endif
> +
> +#endif   /* libebl_PERF_FLAGS.h */

Why the XXX?
The PERF_FRAME_REGISTERS_I386 naming is a little confusing imho.
These don't seem to be the i386 registers, but the 64bit x86_64 ones.

> diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c
> index be965fa6..2e6d1df1 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 , 2015.
> This file is part of elfutils.
> Written by Ulrich Drepper , 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,8 @@ 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);
> +  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 ..182f8197
> --- /dev/null
> +++ b/backends/x86_64_initreg_sample.c
> @@ -0,0 +1,107 @@
> +/* 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.

Re: [PATCH 03/13] libebl [3/13]: eu-stacktrace: use new register handling api

2025-03-19 Thread Mark Wielaard
Hi Serhei,

On Sun, Mar 16, 2025 at 07:13:41PM -0400, Serhei Makarov wrote:
> Change the sample_set_initial_registers callback in eu-stacktrace to
> use the proper libebl ebl_set_initial_registers_sample function.
> 
> * src/Makefile.am (stacktrace_LDADD): Add libebl.
> * src/stacktrace.c (sample_registers_cb): New function,
>   though identical to pid_thread_state_registers_cb.
>   (sample_set_initial_registers): Invoke
>   ebl_set_initial_registers_sample instead of containing
>   platform-specific code directly.

Yes, this seems the logical consequence of patch 01/13 (it could even
be folded into it.

Cheers,

Mark

> ---
>  src/Makefile.am  |  4 ++--
>  src/stacktrace.c | 48 +---
>  2 files changed, 15 insertions(+), 37 deletions(-)
> 
> diff --git a/src/Makefile.am b/src/Makefile.am
> index ed245fc1..6d713e88 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -1,6 +1,6 @@
>  ## Process this file with automake to create Makefile.in
>  ##
> -## Copyright (C) 1996-2014, 2016, 2024 Red Hat, Inc.
> +## Copyright (C) 1996-2014, 2016, 2024-2025 Red Hat, Inc.
>  ## This file is part of elfutils.
>  ##
>  ## This file is free software; you can redistribute it and/or modify
> @@ -105,7 +105,7 @@ ar_LDADD = libar.a $(libelf) $(libeu) $(argp_LDADD) 
> $(obstack_LIBS)
>  unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD)
>  stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) 
> $(demanglelib)
>  if ENABLE_STACKTRACE
> -stacktrace_LDADD = $(libelf) $(libdw) $(libeu) $(argp_LDADD)
> +stacktrace_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD)
>  endif
>  elfcompress_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD)
>  elfclassify_LDADD = $(libelf) $(libdw) $(libeu) $(argp_LDADD)
> diff --git a/src/stacktrace.c b/src/stacktrace.c
> index d8699ce5..7b498e40 100644
> --- a/src/stacktrace.c
> +++ b/src/stacktrace.c
> @@ -1,5 +1,5 @@
>  /* Process a stream of stack samples into stack traces.
> -   Copyright (C) 2023-2024 Red Hat, Inc.
> +   Copyright (C) 2023-2025 Red Hat, Inc.
> This file is part of elfutils.
>  
> This file is free software; you can redistribute it and/or modify
> @@ -93,9 +93,11 @@
>   * Includes: libdwfl data structures *
>   */
>  
> +#include ELFUTILS_HEADER(ebl)
>  /* #include ELFUTILS_HEADER(dwfl) */
>  #include "../libdwfl/libdwflP.h"
> -/* XXX: Private header needed for find_procfile, sysprof_init_dwfl */
> +/* XXX: Private header needed for find_procfile, sysprof_init_dwfl,
> +   sample_set_initial_registers. */
>  
>  /*
>   * Includes: sysprof data structures *
> @@ -574,42 +576,18 @@ sample_memory_read (Dwfl *dwfl, Dwarf_Addr addr, 
> Dwarf_Word *result, void *arg)
>return true;
>  }
>  
> -/* TODO: Need to generalize this code beyond x86 architectures. */
>  static bool
> -sample_set_initial_registers (Dwfl_Thread *thread, void *thread_arg)
> +sample_set_initial_registers (Dwfl_Thread *thread, void *arg)
>  {
> -  /* 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().)
> - - sysprof libsysprof/perf-event-stream-private.h records all registers
> -   except segment and flags.
> -   - TODO: Should include the perf regs mask in sysprof data and
> - translate registers in fully-general fashion, removing this 
> assumption.
> - - dwarf register order seen in elfutils 
> backends/{x86_64,i386}_initreg.c;
> -   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. */
> -  struct __sample_arg *sample_arg = (struct __sample_arg *)thread_arg;
> -  bool is_abi32 = (sample_arg->abi == PERF_SAMPLE_REGS_ABI_32);
> -  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*/, 9, 10, 11, 
> 12, 13, 14, 15, 16, 8/*ip*/};
> -  const int *reg_xlat = is_abi32 ? regs_i386 : regs_x86_64;
> -  int n_regs = is_abi32 ? 9 : 17;
> +  struct __sample_arg *sample_arg = (struct __sample_arg *)arg;
>dwfl_thread_state_register_pc (thread, sample_arg->pc);
> -  if (sample_arg->n_regs < (uint64_t)n_regs && show_failures)
> -fprintf(stderr, N_("sample_set_initial_regs: n_regs=%ld, expected %d\n"),
> - sample_arg->n_regs, n_regs);
> -  for (int i = 0; i < n_regs; i++)
> -{
> -  int j = reg_xlat[i];
> -  if (j < 0) continue;
> -  if (sample_arg->n_regs <= (uint64_t)j) continue;
> -   

Re: [PATCH 04/13] libdwfl [4/13]: add dwfl_perf_sample_preferred_regs_mask

2025-03-19 Thread Mark Wielaard
Hi Serhei,

On Sun, Mar 16, 2025 at 07:14:11PM -0400, Serhei Makarov wrote:
> Since libebl is a private interface, subsequent patches in the series
> introduce another api wrapping the libebl perf register handling.  In
> this patch, add an interface to access the preferred set of registers
> that libdwfl would like to see to allow proper unwinding of stack
> sample data.
> 
> * libdwfl/libdwfl.h (dwfl_perf_sample_preferred_regs_mask):
>   New function.
> * libdwfl/dwfl_perf_frame.c: New file.
>   (dwfl_perf_sample_preferred_regs_mask): New function.
> * libdw/libdw.map: Add dwfl_perf_sample_preferred_regs_mask.
> * libdwfl/Makefile.am: Add dwfl_perf_frame.c.

I probably need to read the rest of the patch series first. On its own
I am not a fan, it feels very much an implementation detail that
should be abstracted away.

> ---
>  libdw/libdw.map   |  1 +
>  libdwfl/Makefile.am   |  3 +-
>  libdwfl/dwfl_perf_frame.c | 61 +++
>  libdwfl/libdwfl.h | 10 +++
>  4 files changed, 74 insertions(+), 1 deletion(-)
>  create mode 100644 libdwfl/dwfl_perf_frame.c
> 
> diff --git a/libdw/libdw.map b/libdw/libdw.map
> index afbc467f..3218fd9c 100644
> --- a/libdw/libdw.map
> +++ b/libdw/libdw.map
> @@ -389,4 +389,5 @@ ELFUTILS_0.192 {
>  ELFUTILS_0.193 {
>global:
>  dwfl_set_initial_registers_thread;
> +dwfl_perf_sample_preferred_regs_mask;
>  } ELFUTILS_0.192;
> diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
> index b30b86f0..37c57bee 100644
> --- a/libdwfl/Makefile.am
> +++ b/libdwfl/Makefile.am
> @@ -2,7 +2,7 @@
>  ##
>  ## Process this file with automake to create Makefile.in
>  ##
> -## Copyright (C) 2005-2010, 2013 Red Hat, Inc.
> +## Copyright (C) 2005-2010, 2013, 2025 Red Hat, Inc.
>  ## This file is part of elfutils.
>  ##
>  ## This file is free software; you can redistribute it and/or modify
> @@ -71,6 +71,7 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c 
> dwfl_version.c \
>   link_map.c core-file.c open.c image-header.c \
>   dwfl_frame.c frame_unwind.c dwfl_frame_pc.c \
>   linux-pid-attach.c linux-core-attach.c dwfl_frame_regs.c \
> + dwfl_perf_frame.c \
>   gzip.c debuginfod-client.c
>  
>  if BZLIB
> diff --git a/libdwfl/dwfl_perf_frame.c b/libdwfl/dwfl_perf_frame.c
> new file mode 100644
> index ..87db97bc
> --- /dev/null
> +++ b/libdwfl/dwfl_perf_frame.c
> @@ -0,0 +1,61 @@
> +/* Get Dwarf Frame state for perf stack sample data.
> +   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 .  */
> +
> +#ifdef HAVE_CONFIG_H
> +# include 
> +#endif
> +
> +#include 

This should probably be guarded by #ifdef __linux__ or some configure
check? Or... where is this used?

> +#include "libdwflP.h"
> +
> +Ebl *default_ebl = NULL;
> +GElf_Half default_ebl_machine = EM_NONE;

Make these static.

> +uint64_t dwfl_perf_sample_preferred_regs_mask (GElf_Half machine)
> +{
> +  /* XXX The most likely case is that this will only be called once,
> + for the current architecture.  So we keep one Ebl* around for
> + answering this query and replace it in the unlikely case of
> + getting called with different architectures.  */
> +  if (default_ebl != NULL && default_ebl_machine != machine)
> +{
> +  ebl_closebackend(default_ebl);
> +  default_ebl = NULL;
> +}
> +  if (default_ebl == NULL)
> +{
> +  default_ebl = ebl_openbackend_machine(machine);
> +  default_ebl_machine = machine;
> +}
> +  if (default_ebl != NULL)
> +return ebl_perf_frame_regs_mask (default_ebl);
> +  return 0;
> +}
> +
> +/* XXX dwfl_perf_sample_getframes to be added in subsequent patch */
> diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
> index b3b32d51..29505863 100644
> --- a/libdwfl/libdwfl.h
> +++ b/libdwfl/libdwfl.h
> @@ -825,6 +825,16 @@ int dwfl_getthread_frames (Dwfl *dwfl, pid_t tid,
>  void *arg)
>__

[Bug general/29571] Add 'Key to Flags' to eu-readelf output

2025-03-19 Thread mark at klomp dot org
https://sourceware.org/bugzilla/show_bug.cgi?id=29571

--- Comment #7 from Mark Wielaard  ---
(In reply to Sam Zeter from comment #6)
> I also noticed in libelf/elf.h that we define these flags but do not print
> them in readelf:
> 
> #define SHF_MASKOS 0x0ff0 /* OS-specific.  */
> #define SHF_MASKPROC   0xf000 /* Processor-specific */
> 
> is this intentional?

Yes, those aren't flag values themselves. They as masks, you would use them if
you are only interested in the OS or Processor specific flag values.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Re: [PATCH] Add 'Key to Flags' to eu-readelf output [bz 29571]

2025-03-19 Thread Mark Wielaard
Hi Samuel,

On Mon, 2025-03-17 at 17:39 +1000, Samuel Zeter wrote:
> When printing section headers, also include a key to what each flag
> is at the end of the section header output.

Did you run make check after your patch? It seems various tests fail
because they aren't expecting the new flag info. I am getting:

FAIL: run-large-elf-file.sh
FAIL: run-strip-remove-keep.sh
FAIL: run-readelf-z.sh
FAIL: run-retain.sh
FAIL: run-backtrace-native-core-biarch.sh
FAIL: run-copyadd-sections.sh
FAIL: run-copymany-be32.sh
FAIL: run-copymany-le32.sh
FAIL: run-copymany-be64.sh
FAIL: run-copymany-le64.sh

# TOTAL: 286
# PASS:  275
# SKIP:  1
# XFAIL: 0
# FAIL:  10
# XPASS: 0
# ERROR: 0

> Signed-off-by: Samuel Zeter 
> ---
>  src/readelf.c | 11 +++
>  1 file changed, 11 insertions(+)
> 
> diff --git a/src/readelf.c b/src/readelf.c
> index 12d85472..f9c1c742 100644
> --- a/src/readelf.c
> +++ b/src/readelf.c
> @@ -341,6 +341,7 @@ static void print_strings (Ebl *ebl);
>  static void dump_archive_index (Elf *, const char *);
>  static void print_dwarf_addr (Dwfl_Module *dwflmod, int address_size,
> Dwarf_Addr address, Dwarf_Addr raw);
> +static void print_flag_info(void);
>  
>  enum dyn_idx
>  {
> @@ -1406,9 +1407,19 @@ There are %zd section headers, starting at offset %#" 
> PRIx64 ":\n\
>   }
>  }
>  
> +  print_flag_info();
>fputc ('\n', stdout);
>  }
>  
> +/* Print flag information.  */
> +static void
> +print_flag_info (void)
> +{
> + puts ("Key to Flags:");
> + puts ("  W (write), A (alloc), X (execute), M (merge), S (strings), I 
> (info),");
> + puts ("  L (link order), N (extra OS processing required), G (group), T 
> (TLS),");
> + puts ("  C (compressed), O (ordered), R (GNU retain), E (exclude)");
> +}
>  
>  /* Print the program header.  */
>  static void

The patch itself does look correct. These are the flags that print_shdr
prints if the corresponding shdr->sh_flags are set.

Cheers,

Mark


Re: Question about elf symbol's file offset

2025-03-19 Thread Mark Wielaard
Hi Hengqi,

On Tue, 2025-03-11 at 13:27 +0800, Hengqi Chen wrote:
> I want to ask you a question regarding elf internals.
> How to calculate a symbol's file offset (which is kernel uprobe expects)
> in an elf (executable or shared object)?

Could you point me to a description of what uprobe expects?

> Some real world use case use either section header like libbpf:
>   
> https://github.com/libbpf/libbpf/blob/374036c9f1cdfe2a8df98d9d6a53c34fd02de14b/src/elf.c#L259-L270
> Or use program header like BCC:
>   
> https://github.com/iovisor/bcc/blob/82f9d1cb633aa3b4ebcbbc5d8b809f48d3dfa222/src/cc/bcc_syms.cc#L767-L775
> 
> Which is correct ? Is there a unified way to get the file offset of a symbol ?

I am not sure I understand enough of what uprobe expects to know right
now. In general it depends on the ELF file type, for ET_REL files the
st_value is relative to to associated section (unless SHN_ABS),
otherwise the associated section load address doesn't really matter
except for where the program header says it is loaded, which might be
absolute (for ET_EXEC) or dynamic (for ET_DYN). It might also depend on
whether the dynamic loader has relocated the symbol and/or section
addresses (so whether you are reading the values from memory or on
disk).

Cheers,

Mark


[PATCH] libdwfl: resolve all paths relative to sysroot

2025-03-19 Thread Michal Sekletar
Whenever possible, resolve all symlinks as if the sysroot path were a
chroot environment. This prevents potential interactions with files from
the host filesystem.

Signed-off-by: Michal Sekletar 
---
 configure.ac | 17 +
 libdwfl/dwfl_segment_report_module.c | 21 +
 libdwfl/link_map.c   | 27 ++-
 3 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 3298f7fc..22f7258a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -283,6 +283,23 @@ case "$CFLAGS" in
 ;;
 esac
 
+AC_MSG_CHECKING([for openat2 with RESOLVE_IN_ROOT support])
+AC_LINK_IFELSE(
+  [AC_LANG_PROGRAM([[
+#include 
+#include 
+#include 
+#include 
+#include 
+  ]], [[
+struct open_how how = { .flags = O_RDONLY|O_DIRECTORY, .resolve = 
RESOLVE_IN_ROOT };
+return syscall (SYS_openat2, AT_FDCWD, ".", &how, sizeof(how)) >= 0 ? 
EXIT_SUCCESS : EXIT_FAILURE;
+  ]])],
+  [AC_DEFINE([HAVE_OPENAT2_RESOLVE_IN_ROOT], [1], [Define if openat2 is 
available])
+   AC_MSG_RESULT(yes)],
+  [AC_MSG_RESULT(no)]
+)
+
 dnl enable debugging of branch prediction.
 AC_ARG_ENABLE([debugpred],
 AS_HELP_STRING([--enable-debugpred],[build binaries with support to debug 
branch prediction]),
diff --git a/libdwfl/dwfl_segment_report_module.c 
b/libdwfl/dwfl_segment_report_module.c
index 32f44af8..53b938b9 100644
--- a/libdwfl/dwfl_segment_report_module.c
+++ b/libdwfl/dwfl_segment_report_module.c
@@ -37,6 +37,12 @@
 #include 
 #include 
 
+#if HAVE_OPENAT2_RESOLVE_IN_ROOT
+#include 
+#include 
+#include 
+#endif
+
 #include 
 
 
@@ -784,6 +790,19 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const 
char *name,
 sysroot if it is set.  */
   if (dwfl->sysroot && !executable)
 {
+#if HAVE_OPENAT2_RESOLVE_IN_ROOT
+ int sysrootfd;
+ struct open_how how = {
+   .flags = O_RDONLY,
+   .resolve = RESOLVE_IN_ROOT,
+ };
+
+ sysrootfd = open (dwfl->sysroot, O_DIRECTORY|O_PATH);
+ if (sysrootfd < 0)
+   return -1;
+ fd = syscall (SYS_openat2, sysrootfd, name, &how, sizeof(how));
+ close (sysrootfd);
+#else
  int r;
  char *n;
 
@@ -793,6 +812,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char 
*name,
  fd = open (n, O_RDONLY);
  free (n);
}
+
+#endif
 }
   else
  fd = open (name, O_RDONLY);
diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c
index 8ab14862..761e9b03 100644
--- a/libdwfl/link_map.c
+++ b/libdwfl/link_map.c
@@ -34,6 +34,12 @@
 
 #include 
 
+#if HAVE_OPENAT2_RESOLVE_IN_ROOT
+#include 
+#include 
+#include 
+#endif
+
 /* This element is always provided and always has a constant value.
This makes it an easy thing to scan for to discern the format.  */
 #define PROBE_TYPE AT_PHENT
@@ -418,7 +424,25 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t 
elfdata,
  /* This code is mostly inlined dwfl_report_elf.  */
  char *sysroot_name = NULL;
  const char *sysroot = dwfl->sysroot;
+ int fd;
 
+#if HAVE_OPENAT2_RESOLVE_IN_ROOT
+ if (sysroot)
+   {
+ int sysrootfd;
+
+ struct open_how how = {
+   .flags = O_RDONLY,
+   .resolve = RESOLVE_IN_ROOT,
+ };
+
+ sysrootfd = open (sysroot, O_DIRECTORY|O_PATH);
+ if (sysrootfd < 0)
+   return -1;
+ fd = syscall (SYS_openat2, sysrootfd, name, &how, sizeof(how));
+ close (sysrootfd);
+   }
+#else
  /* Don't use the sysroot if the path is already inside it.  */
  bool name_in_sysroot = sysroot && startswith (name, sysroot);
 
@@ -430,7 +454,8 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata,
  name = sysroot_name;
}
 
- int fd = open (name, O_RDONLY);
+ fd = open (name, O_RDONLY);
+#endif
  if (fd >= 0)
{
  Elf *elf;
-- 
2.39.5 (Apple Git-154)