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

2025-03-16 Thread Serhei Makarov
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.
---
 backends/Makefile.am |   4 +-
 backends/libebl_PERF_FLAGS.h |  55 
 backends/x86_64_init.c   |   5 +-
 backends/x86_64_initreg_sample.c | 107 +++
 libebl/Makefile.am   |   4 +-
 libebl/ebl-hooks.h   |  10 ++-
 libebl/eblinitreg_sample.c   |  54 
 libebl/libebl.h  |  18 +-
 libebl/libeblP.h |   7 +-
 9 files changed, 256 insertions(+), 8 deletions(-)
 create mode 100644 backends/libebl_PERF_FLAGS.h
 create mode 100644 backends/x86_64_initreg_sample.c
 create mode 100644 libebl/eblinitreg_sample.c

diff --git a/backends/Makefile.am b/backends/Makefile.am
index 540d0c6c..311afcbf 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.
 ##
@@ -47,7 +47,7 @@ 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
diff --git a/backends/libebl_PERF_FLAGS.h b/backends/libebl_PERF_FLAGS.h
new file mode 100644
index ..eedd8b7a
--- /dev/null
+++ b/backends/libebl_PERF_FLAGS.h
@@ -0,0 +1,55 @@
+/* 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 .  */
+
+#ifndef _LIBEBL_PERF_FLAGS_H
+#define _LIBEBL_PERF_FLAGS_H 1
+
+#if defined(__linux__)
+# include 
+#endif
+
+#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

[PATCH 02/13] libdwfl [2/13]: expose setfunc callback for libdwfl+libebl clients

2025-03-16 Thread Serhei Makarov
Renaming to dwfl_set_initial_registers_thread.

This callback was private to one file, but now that other
tools (eu-stacktrace, sysprof via the dwfl_perf_sample_getframes) are
invoking the ebl set_initial_registers_sample api, we need to expose
it.  Otherwise, clients would need to reimplement this code
identically, including the undocumented/unexplained use of -2 for
aarch64 insn_mask.

TODO(REVIEW): Should this be in libdwflP.h since libebl is private?
On the other hand, dwfl_thread_state_register_pc et al. are also
public in spite of being used primarily by the previously-internal
code in dwfl_set_initial_registers_thread.

* libdw/libdw.map: Add dwfl_set_initial_registers_thread.
* libdwfl/libdwfl.h (dwfl_set_initial_registers_thread): New function.
* libdwfl/linux-pid-attach.c (dwfl_set_initial_registers_thread):
  Renamed from pid_thread_state_registers_cb.
  (pid_set_initial_registers): Pass the newly renamed callback.
---
 libdw/libdw.map|  5 +
 libdwfl/libdwfl.h  | 10 +-
 libdwfl/linux-pid-attach.c | 10 +-
 3 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/libdw/libdw.map b/libdw/libdw.map
index bc53385f..afbc467f 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -385,3 +385,8 @@ ELFUTILS_0.192 {
 dwfl_frame_unwound_source;
 dwfl_unwound_source_str;
 } ELFUTILS_0.191;
+
+ELFUTILS_0.193 {
+  global:
+dwfl_set_initial_registers_thread;
+} ELFUTILS_0.192;
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 90523283..b3b32d51 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -1,5 +1,5 @@
 /* Interfaces for libdwfl.
-   Copyright (C) 2005-2010, 2013, 2024 Red Hat, Inc.
+   Copyright (C) 2005-2010, 2013, 2024-2025 Red Hat, Inc.
This file is part of elfutils.
 
This file is free software; you can redistribute it and/or modify
@@ -783,6 +783,14 @@ bool dwfl_thread_state_registers (Dwfl_Thread *thread, int 
firstreg,
 void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc)
   __nonnull_attribute__ (1);
 
+/* Basic implementation of Dwfl_Thread_Callbacks.set_initial_registers.
+   ARG must be a Dwfl_Thread *.  Calls dwfl_thread_state_register_pc
+   if firstreg is -1 (indicating arch PC), dwfl_thread_state_registers
+   otherwise.  */
+bool dwfl_set_initial_registers_thread (int firstreg, unsigned nregs,
+const Dwarf_Word *regs, void *arg)
+  __nonnull_attribute__ (3, 4);
+
 /* Iterate through the threads for a process.  Returns zero if all threads have
been processed by the callback, returns -1 on error, or the value of the
callback when not DWARF_CB_OK.  -1 returned on error will set dwfl_errno ().
diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c
index 0eec1e88..2d35a8c6 100644
--- a/libdwfl/linux-pid-attach.c
+++ b/libdwfl/linux-pid-attach.c
@@ -1,5 +1,5 @@
 /* Get Dwarf Frame state for target live PID process.
-   Copyright (C) 2013, 2014, 2015, 2018 Red Hat, Inc.
+   Copyright (C) 2013, 2014, 2015, 2018, 2025 Red Hat, Inc.
This file is part of elfutils.
 
This file is free software; you can redistribute it and/or modify
@@ -304,9 +304,9 @@ pid_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t 
tid,
 
 /* Implement the ebl_set_initial_registers_tid setfunc callback.  */
 
-static bool
-pid_thread_state_registers_cb (int firstreg, unsigned nregs,
-  const Dwarf_Word *regs, void *arg)
+bool
+dwfl_set_initial_registers_thread (int firstreg, unsigned nregs,
+  const Dwarf_Word *regs, void *arg)
 {
   Dwfl_Thread *thread = (Dwfl_Thread *) arg;
   if (firstreg == -1)
@@ -338,7 +338,7 @@ pid_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg)
   Dwfl_Process *process = thread->process;
   Ebl *ebl = process->ebl;
   return ebl_set_initial_registers_tid (ebl, tid,
-   pid_thread_state_registers_cb, thread);
+   dwfl_set_initial_registers_thread, 
thread);
 }
 
 static void
-- 
2.47.0



[PATCH 07/13] eu-stacktrace [7/13]: use Dwfl_Process_Tracker for Elf * caching

2025-03-16 Thread Serhei Makarov
* src/stacktrace.c (tracker): New global variable.
  (sample_callbacks): Use dwfl_process_tracker_find_elf for caching.
  (sysprof_init_dwfl): Use dwfl_begin_with_tracker.
  (main): Initialize and clean up tracker.
---
 src/stacktrace.c | 11 ---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/stacktrace.c b/src/stacktrace.c
index 7b498e40..a6866bd5 100644
--- a/src/stacktrace.c
+++ b/src/stacktrace.c
@@ -611,6 +611,8 @@ static const Dwfl_Thread_Callbacks sample_thread_callbacks =
  * Dwfl and statistics table for multiple processes *
  /
 
+Dwfl_Process_Tracker *tracker = NULL;
+
 /* This echoes lib/dynamicsizehash.* with some necessary modifications. */
 typedef struct
 {
@@ -843,7 +845,7 @@ static char *debuginfo_path = NULL;
 
 static const Dwfl_Callbacks sample_callbacks =
   {
-.find_elf = dwfl_linux_proc_find_elf,
+.find_elf = dwfl_process_tracker_find_elf,
 .find_debuginfo = dwfl_standard_find_debuginfo,
 .debuginfo_path = &debuginfo_path,
   };
@@ -869,7 +871,7 @@ nop_find_debuginfo (Dwfl_Module *mod 
__attribute__((unused)),
 
 static const Dwfl_Callbacks sample_callbacks =
 {
-  .find_elf = dwfl_linux_proc_find_elf,
+  .find_elf = dwfl_process_tracker_find_elf,
   .find_debuginfo = nop_find_debuginfo, /* work with CFI only */
 };
 
@@ -992,7 +994,7 @@ sysprof_init_dwfl (struct sysprof_unwind_info *sui,
   cached = true;
   goto reuse;
 }
-  dwfl = dwfl_begin (&sample_callbacks);
+  dwfl = dwfl_begin_with_tracker (tracker);
 
   int err = dwfl_linux_proc_report (dwfl, pid);
   if (err < 0)
@@ -1486,6 +1488,7 @@ 
https://sourceware.org/cgit/elfutils/tree/README.eu-stacktrace?h=users/serhei/eu
   (void)maxframes;
 #else
   fprintf(stderr, "\n=== starting eu-stacktrace ===\n");
+  tracker = dwfl_process_tracker_begin (&sample_callbacks);
 
   /* TODO: For now, code the processing loop for sysprof only; generalize 
later. */
   assert (input_format == FORMAT_SYSPROF);
@@ -1569,5 +1572,7 @@ 
https://sourceware.org/cgit/elfutils/tree/README.eu-stacktrace?h=users/serhei/eu
   if (output_fd != -1)
 close (output_fd);
 
+  dwfl_process_tracker_end (tracker);
+
   return EXIT_OK;
 }
-- 
2.47.0



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

2025-03-16 Thread Serhei Makarov
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.
---
 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 
+
+#include "libdwflP.h"
+
+Ebl *default_ebl = NULL;
+GElf_Half default_ebl_machine = EM_NONE;
+
+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)
   __nonnull_attribute__ (1, 3);
 
+/* XXX dwfl_perf_sample_getframes to be added in subsequent patch */
+
+/* Returns the linux perf_events register mask describing a set of
+   registers sufficient for unwinding on MACHINE, or 0 if libdwfl does
+   not handle perf_events samples for MACHINE.  Does not take a Dwfl*
+   or Elf* since this is meant to allow a profiling tool to configure
+   perf_events to produce meaningful data for a libdwfl session to be
+   opened later.  */
+uint64_t dwfl_perf_sample_preferred_regs_mask (GElf_Half machine);
+
 /* Return *PC (program counter) for thread-specific

[PATCH 08/13] libdwfl [8/13]: Dwfl* caching via dwfl_process_tracker

2025-03-16 Thread Serhei Makarov
The Dwfl_Process_Tracker also includes a dynamicsizehash cache which
maps process ids to Dwfl * (or rather, dwfltracker_dwfl_info *
allowing the table entry to be replaced).  Dwfls created from
the tracker are automatically added to it, and removed on dwfl_end().

* libdwfl/libdwflP.h (dwfltracker_dwfl_info): New typedef, provides
  indirection to allow a dwfltab entry to be invalidated.
  (struct Dwfl_Process_Tracker): add dwfltab.
  (__libdwfl_add_dwfl_to_tracker): New function.
  (__libdwfl_remove_dwfl_from_tracker): New function.
* libdwfl/dwfl_process_tracker_dwfltab.c: New file, instantiates
  lib/dynamicsizehash_concurrent.c to store dwfltracker_dwfl_info
  structs.
* libdwfl/dwfl_process_tracker_dwfltab.h: New file, ditto.
* libdwfl/dwfl_process_tracker.c (dwfl_process_tracker_begin): Init dwfltab.
  (__libdwfl_add_dwfl_to_tracker): New function; add dwfl to dwfltab.
  (__libdwfl_remove_dwfl_from_tracker): New function; invalidate dwfl
  entry, since dynamicsizehash doesn't support outright deletion.
  (dwfl_process_tracker_end): Clean up dwfltab. Lock and iterate the
  table to free tracker->dwfltab.table items.
* libdwfl/dwfl_frame.c (dwfl_attach_state): Call __libdwfl_add_dwfl_to_tracker.
* libdwfl/dwfl_end.c (dwfl_end): Call __libdwfl_remove_dwfl_from_tracker.
* libdwfl/Makefile.am (libdwfl_a_SOURCES): Add dwfl_process_tracker_dwfltab.c.
---
 libdwfl/Makefile.am|  1 +
 libdwfl/dwfl_end.c |  5 +-
 libdwfl/dwfl_frame.c   |  6 ++-
 libdwfl/dwfl_process_tracker.c | 67 +-
 libdwfl/dwfl_process_tracker_dwfltab.c | 46 ++
 libdwfl/dwfl_process_tracker_dwfltab.h | 42 
 libdwfl/libdwflP.h | 18 +++
 7 files changed, 181 insertions(+), 4 deletions(-)
 create mode 100644 libdwfl/dwfl_process_tracker_dwfltab.c
 create mode 100644 libdwfl/dwfl_process_tracker_dwfltab.h

diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index f05f12af..5c7bc1d8 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -73,6 +73,7 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c 
dwfl_version.c \
linux-pid-attach.c linux-core-attach.c dwfl_frame_regs.c \
dwfl_process_tracker.c dwfl_process_tracker_find_elf.c \
dwfl_process_tracker_elftab.c libdwfl_next_prime.c \
+   dwfl_process_tracker_dwfltab.c \
dwfl_perf_frame.c \
gzip.c debuginfod-client.c
 
diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c
index 7b5ac8a1..a9e7983c 100644
--- a/libdwfl/dwfl_end.c
+++ b/libdwfl/dwfl_end.c
@@ -1,5 +1,5 @@
 /* Finish a session using libdwfl.
-   Copyright (C) 2005, 2008, 2012-2013, 2015 Red Hat, Inc.
+   Copyright (C) 2005, 2008, 2012-2013, 2015, 2025 Red Hat, Inc.
This file is part of elfutils.
 
This file is free software; you can redistribute it and/or modify
@@ -42,6 +42,9 @@ dwfl_end (Dwfl *dwfl)
   __libdwfl_debuginfod_end (dwfl->debuginfod);
 #endif
 
+  if (dwfl->tracker != NULL)
+__libdwfl_remove_dwfl_from_tracker (dwfl);
+
   if (dwfl->process)
 __libdwfl_process_free (dwfl->process);
 
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 2e6c6de8..0ce9b8a8 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -1,5 +1,5 @@
 /* Get Dwarf Frame state for target PID or core file.
-   Copyright (C) 2013, 2014, 2024 Red Hat, Inc.
+   Copyright (C) 2013, 2014, 2024-2025 Red Hat, Inc.
This file is part of elfutils.
 
This file is free software; you can redistribute it and/or modify
@@ -206,6 +206,10 @@ dwfl_attach_state (Dwfl *dwfl, Elf *elf, pid_t pid,
   process->pid = pid;
   process->callbacks = thread_callbacks;
   process->callbacks_arg = arg;
+
+  if (dwfl->tracker != NULL)
+  __libdwfl_add_dwfl_to_tracker (dwfl);
+
   return true;
 }
 INTDEF(dwfl_attach_state)
diff --git a/libdwfl/dwfl_process_tracker.c b/libdwfl/dwfl_process_tracker.c
index 3a414fc0..f1d1feb5 100644
--- a/libdwfl/dwfl_process_tracker.c
+++ b/libdwfl/dwfl_process_tracker.c
@@ -44,6 +44,7 @@ Dwfl_Process_Tracker *dwfl_process_tracker_begin (const 
Dwfl_Callbacks *callback
 }
 
   dwfltracker_elftab_init (&tracker->elftab, HTAB_DEFAULT_SIZE);
+  dwfltracker_dwfltab_init (&tracker->dwfltab, HTAB_DEFAULT_SIZE);
 
   tracker->callbacks = callbacks;
   return tracker;
@@ -57,18 +58,66 @@ Dwfl *dwfl_begin_with_tracker (Dwfl_Process_Tracker 
*tracker)
 
   /* TODO: Could also share dwfl->debuginfod, but thread-safely? */
   dwfl->tracker = tracker;
+
+  /* XXX: dwfl added to dwfltab when dwfl->process set in dwfl_attach_state. */
+  /* XXX: dwfl removed from dwfltab in dwfl_end() */
+
   return dwfl;
 }
 
+void __libdwfl_add_dwfl_to_tracker (Dwfl *dwfl) {
+  Dwfl_Process_Tracker *tracker = dwfl->tracker;
+  assert (tracker != NULL);
+
+  /* First try to find an existing entry to replace: */
+  dwfltracker_dwfl_info *ent = NULL;
+  unsigned long

[PATCH 09/13] libdwfl [9/13]: add dwfl_process_tracker_find_pid

2025-03-16 Thread Serhei Makarov
New function that retrieves the Dwfl for a particular PID, or,
if the Dwfl is absent, creates it via a provided callback
and adds it to the table later, when the PID is confirmed
via dwfl_attach_state.

* libdwfl/libdwfl.h (dwfl_process_tracker_find_pid): New function.
* libdwfl/dwfl_process_tracker.h (dwfl_process_tracker_find_pid):
  New function; find a Dwfl in the dwfltab or create one using the
  provided callback.
* libdw/libdw.map: Add dwfl_process_tracker_find_pid.
---
 libdw/libdw.map|  1 +
 libdwfl/dwfl_process_tracker.c | 22 ++
 libdwfl/libdwfl.h  | 11 +++
 3 files changed, 34 insertions(+)

diff --git a/libdw/libdw.map b/libdw/libdw.map
index 1a2a879f..502be882 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -394,4 +394,5 @@ ELFUTILS_0.193 {
 dwfl_begin_with_tracker;
 dwfl_process_tracker_end;
 dwfl_process_tracker_find_elf;
+dwfl_process_tracker_find_pid;
 } ELFUTILS_0.192;
diff --git a/libdwfl/dwfl_process_tracker.c b/libdwfl/dwfl_process_tracker.c
index f1d1feb5..ba99f1f2 100644
--- a/libdwfl/dwfl_process_tracker.c
+++ b/libdwfl/dwfl_process_tracker.c
@@ -65,6 +65,28 @@ Dwfl *dwfl_begin_with_tracker (Dwfl_Process_Tracker *tracker)
   return dwfl;
 }
 
+Dwfl *dwfl_process_tracker_find_pid (Dwfl_Process_Tracker *tracker,
+pid_t pid,
+Dwfl *(*callback) (Dwfl_Process_Tracker *,
+   pid_t, void *),
+void *arg)
+{
+  Dwfl *dwfl = NULL;
+  dwfltracker_dwfl_info *ent = dwfltracker_dwfltab_find(&tracker->dwfltab, 
pid);
+  if (ent != NULL && !ent->invalid)
+dwfl = ent->dwfl;
+  if (dwfl == NULL && callback != NULL)
+dwfl = callback(tracker, pid, arg);
+  if (dwfl != NULL)
+{
+  assert (dwfl->tracker == tracker);
+  /* XXX: dwfl added to dwfltab when dwfl->process set in 
dwfl_attach_state.
+ Prior to that, the pid is not confirmed. */
+}
+
+  return dwfl;
+}
+
 void __libdwfl_add_dwfl_to_tracker (Dwfl *dwfl) {
   Dwfl_Process_Tracker *tracker = dwfl->tracker;
   assert (tracker != NULL);
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 3613be58..c6d0f02e 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -133,6 +133,17 @@ extern Dwfl_Process_Tracker *dwfl_process_tracker_begin 
(const Dwfl_Callbacks *c
 extern Dwfl *dwfl_begin_with_tracker (Dwfl_Process_Tracker *tracker)
   __nonnull_attribute__ (1);
 
+/* Find the Dwfl corresponding to PID.  If CALLBACK is non-NULL
+   and the Dwfl has not been created, invoke CALLBACK to create
+   the Dwfl and then store it in the tracker.  */
+extern Dwfl *dwfl_process_tracker_find_pid (Dwfl_Process_Tracker *tracker,
+pid_t pid,
+Dwfl *(*callback) 
(Dwfl_Process_Tracker *tracker,
+   pid_t pid,
+   void *arg),
+void *arg)
+  __nonnull_attribute__ (1);
+
 /* End a multi-process session.  */
 extern void dwfl_process_tracker_end (Dwfl_Process_Tracker *tracker);
 
-- 
2.47.0



[PATCH 13/13] eu-stacktrace [13/13]: use dwfl_perf_sample_getframes

2025-03-16 Thread Serhei Makarov
Remove the code from src/stacktrace.c that is now covered by
libdwfl/dwfl_perf_frame.c and dwfl_perf_sample_getframes.

* src/stacktrace.c (show_memory_reads): Remove this verbose option as
  the relevant code is inside libdwfl now.
  (struct __sample_arg): Remove, handled by libdwfl/dwfl_perf_frame.c.
  (sample_next_thread): Ditto.
  (sample_getthread): Ditto.
  (copy_word_64): Ditto.
  (copy_word_32): Ditto.
  (elf_memory_read): Ditto.
  (sample_memory_read): Ditto.
  (sample_set_initial_registers): Ditto.
  (sample_detach): Ditto.
  (sample_thread_callbacks): Ditto.
  (sysprof_find_dwfl): Now also return the Elf* so that it can be
  passed to dwfl_perf_sample_getframes. Don't create sample_arg.  Do
  record sp in sui->last_sp. Don't dwfl_attach_state,
  dwfl_perf_sample_getframes handles that now.
  (sysprof_unwind_cb): Adapt to sysprof_find_dwfl changes,
  now invoke dwfl_perf_sample_getframes instead of
  dwfl_getthread_frames.
---
 src/stacktrace.c | 219 +++
 1 file changed, 29 insertions(+), 190 deletions(-)

diff --git a/src/stacktrace.c b/src/stacktrace.c
index 89e1866e..87dbe4c8 100644
--- a/src/stacktrace.c
+++ b/src/stacktrace.c
@@ -96,8 +96,7 @@
 #include ELFUTILS_HEADER(ebl)
 /* #include ELFUTILS_HEADER(dwfl) */
 #include "../libdwfl/libdwflP.h"
-/* XXX: Private header needed for find_procfile, sysprof_find_dwfl,
-   sample_set_initial_registers. */
+/* XXX: Private header needed for find_procfile. */
 
 /*
  * Includes: sysprof data structures *
@@ -176,11 +175,13 @@ static int processing_mode = MODE_NAIVE;
 static int input_format;
 static int output_format = FORMAT_SYSPROF;
 
+/* XXX Used to decide regs_mask for dwfl_perf_sample_getframes. */
+Ebl *default_ebl = NULL;
+
 /* non-printable argp options.  */
 #define OPT_DEBUG  0x100
 
 /* Diagnostic options. */
-static bool show_memory_reads = false;
 static bool show_frames = false;
 static bool show_samples = false;
 static bool show_failures = false;
@@ -469,144 +470,6 @@ sysprof_reader_getframes (SysprofReader *reader,
 
 #endif /* HAVE_SYSPROF_HEADERS */
 
-/***
- * Memory read interface for stack samples *
- ***/
-
-/* TODO: elfutils (internal) libraries use libNN_set_errno and DWFL_E_WHATEVER;
-   this code fails silently in sample_getthread. */
-
-struct __sample_arg
-{
-  int tid;
-  Dwarf_Addr base_addr;
-  uint64_t size;
-  uint8_t *data;
-  uint64_t n_regs;
-  uint64_t abi; /* PERF_SAMPLE_REGS_ABI_{32,64} */
-  Dwarf_Addr pc;
-  Dwarf_Addr sp;
-  Dwarf_Addr *regs;
-};
-
-/* The next few functions imitate the corefile interface for a single
-   stack sample, with very restricted access to registers and memory. */
-
-/* Just yield the single thread id matching the sample. */
-static pid_t
-sample_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg,
-   void **thread_argp)
-{
-  struct __sample_arg *sample_arg = (struct __sample_arg *)dwfl_arg;
-  if (*thread_argp == NULL)
-{
-  *thread_argp = (void *)0xea7b3375;
-  return sample_arg->tid;
-}
-  else
-return 0;
-}
-
-/* Just check that the thread id matches the sample. */
-static bool
-sample_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t tid,
- void *dwfl_arg, void **thread_argp)
-{
-  struct __sample_arg *sample_arg = (struct __sample_arg *)dwfl_arg;
-  *thread_argp = (void *)sample_arg;
-  if (sample_arg->tid != tid)
-{
-  return false;
-}
-  return true;
-}
-
-#define copy_word_64(result, d) \
-  if uintptr_t) (d)) & (sizeof (uint64_t) - 1)) == 0) \
-*(result) = *(uint64_t *)(d); \
-  else \
-memcpy ((result), (d), sizeof (uint64_t));
-
-#define copy_word_32(result, d) \
-  if uintptr_t) (d)) & (sizeof (uint32_t) - 1)) == 0) \
-*(result) = *(uint32_t *)(d); \
-  else \
-memcpy ((result), (d), sizeof (uint32_t));
-
-#define copy_word(result, d, abi) \
-  if ((abi) == PERF_SAMPLE_REGS_ABI_64)\
-{ copy_word_64((result), (d)); } \
-  else if ((abi) == PERF_SAMPLE_REGS_ABI_32) \
-{ copy_word_32((result), (d)); } \
-  else \
-*(result) = 0;
-
-static bool
-elf_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg)
-{
-  struct __sample_arg *sample_arg = (struct __sample_arg *)arg;
-  Dwfl_Module *mod = dwfl_addrmodule(dwfl, addr);
-  Dwarf_Addr bias;
-  Elf_Scn *section = dwfl_module_address_section(mod, &addr, &bias);
-
-  if (!section)
-return false;
-
-  Elf_Data *data = elf_getdata(section, NULL);
-  if (data && data->d_buf && data->d_size > addr) {
-uint8_t *d = ((uint8_t *)data->d_buf) + addr;
-copy_word(result, d, sample_arg->abi);
-return true;
-  }
-  return false;
-}
-
-static bool
-sample_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg)
-{
-  struct __sample_arg *sample_arg = (struct __sample_arg *)arg;
-  if (show_memory_reads)
-

[PATCH 12/13] libdwfl [12/13]: add dwfl_perf_sample_getframes

2025-03-16 Thread Serhei Makarov
This is a new interface for unwinding that doesn't require the Dwfl to
be attached to a live process (via ptrace) or via corefile. Instead,
data from a perf_events stack sample is provided along with an Elf
struct used to identify the architecture. Based on code from
eu-stacktrace.

* libdwfl/libdwfl.h (dwfl_perf_sample_getframes): New function.
* libdwfl/dwfl_perf_frame.c: New file.
  (struct __libdwfl_perf_sample_info): New struct, based on
  src/stacktrace.c struct sample_arg.
  (sample_next_thread): New function, based on src/stacktrace.c.
  (sample_getthread): Ditto.
  (copy_word_64): New macro, based on src/stacktrace.c.
  (copy_word_32): Ditto.
  (copy_word): Ditto.
  (elf_memory_read): New function, based on src/stacktrace.c.
  (sample_memory_read): Ditto.
  (sample_set_initial_registers): Ditto.
  (sample_detach): Ditto.
  (sample_thread_callbacks): New struct, set of callbacks based on
  src/stacktrace.c sample_thread_callbacks.
  (dwfl_perf_sample_getframes): New function, based on parts of
  src/stacktrace.c sysprof_find_dwfl. If the Dwfl is not attached,
  attaches it with sample_thread_callbacks and
  __libdwfl_perf_sample_info. Populates the __libdwfl_perf_sample_info
  with data from the stack sample and calls dwfl_getthread_frames to
  unwind it using the sample_thread_callbacks.
* libdw/libdw.map (ELFUTILS_0.193): Add dwfl_perf_sample_getframes.
* libdwfl/Makefile.am (libdwfl_a_SOURCES): Add dwfl_perf_frame.c.
---
 libdw/libdw.map   |   1 +
 libdwfl/dwfl_perf_frame.c | 193 +-
 libdwfl/libdwfl.h |  15 ++-
 3 files changed, 207 insertions(+), 2 deletions(-)

diff --git a/libdw/libdw.map b/libdw/libdw.map
index 502be882..d92e5365 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -395,4 +395,5 @@ ELFUTILS_0.193 {
 dwfl_process_tracker_end;
 dwfl_process_tracker_find_elf;
 dwfl_process_tracker_find_pid;
+dwfl_perf_sample_getframes;
 } ELFUTILS_0.192;
diff --git a/libdwfl/dwfl_perf_frame.c b/libdwfl/dwfl_perf_frame.c
index 87db97bc..b962ace3 100644
--- a/libdwfl/dwfl_perf_frame.c
+++ b/libdwfl/dwfl_perf_frame.c
@@ -58,4 +58,195 @@ uint64_t dwfl_perf_sample_preferred_regs_mask (GElf_Half 
machine)
   return 0;
 }
 
-/* XXX dwfl_perf_sample_getframes to be added in subsequent patch */
+struct __libdwfl_perf_sample_info {
+  pid_t pid;
+  pid_t tid;
+  Dwarf_Addr base_addr;
+  uint8_t *stack;
+  size_t stack_size;
+  const Dwarf_Word *regs;
+  uint n_regs;
+  uint64_t perf_regs_mask;
+  uint abi;
+  Dwarf_Addr pc;
+};
+
+/* The next few functions imitate the corefile interface for a single
+   stack sample, with very restricted access to registers and memory. */
+
+/* Just yield the single thread id matching the sample. */
+static pid_t
+sample_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg,
+   void **thread_argp)
+{
+  struct __libdwfl_perf_sample_info *sample_arg =
+(struct __libdwfl_perf_sample_info *)dwfl_arg;
+  if (*thread_argp == NULL)
+{
+  *thread_argp = (void *)0xea7b3375;
+  return sample_arg->tid;
+}
+  else
+return 0;
+}
+
+/* Just check that the thread id matches the sample. */
+static bool
+sample_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t tid,
+ void *dwfl_arg, void **thread_argp)
+{
+  struct __libdwfl_perf_sample_info *sample_arg =
+(struct __libdwfl_perf_sample_info *)dwfl_arg;
+  *thread_argp = (void *)sample_arg;
+  if (sample_arg->tid != tid)
+{
+  __libdwfl_seterrno(DWFL_E_INVALID_ARGUMENT);
+  return false;
+}
+  return true;
+}
+
+#define copy_word_64(result, d) \
+  if uintptr_t) (d)) & (sizeof (uint64_t) - 1)) == 0) \
+*(result) = *(uint64_t *)(d); \
+  else \
+memcpy ((result), (d), sizeof (uint64_t));
+
+#define copy_word_32(result, d) \
+  if uintptr_t) (d)) & (sizeof (uint32_t) - 1)) == 0) \
+*(result) = *(uint32_t *)(d); \
+  else \
+memcpy ((result), (d), sizeof (uint32_t));
+
+#define copy_word(result, d, abi) \
+  if ((abi) == PERF_SAMPLE_REGS_ABI_64)\
+{ copy_word_64((result), (d)); } \
+  else if ((abi) == PERF_SAMPLE_REGS_ABI_32) \
+{ copy_word_32((result), (d)); } \
+  else \
+*(result) = 0;
+
+static bool
+elf_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg)
+{
+  struct __libdwfl_perf_sample_info *sample_arg =
+(struct __libdwfl_perf_sample_info *)arg;
+  Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (dwfl, addr);
+  Dwarf_Addr bias;
+  Elf_Scn *section = INTUSE(dwfl_module_address_section) (mod, &addr, &bias);
+
+  if (!section)
+{
+  __libdwfl_seterrno(DWFL_E_ADDR_OUTOFRANGE);
+  return false;
+}
+
+  Elf_Data *data = elf_getdata(section, NULL);
+  if (data && data->d_buf && data->d_size > addr) {
+uint8_t *d = ((uint8_t *)data->d_buf) + addr;
+copy_word(result, d, sample_arg->abi);
+return true;
+  }
+  __libdwfl_seterrno(DWFL_E_ADDR_OUTOFRANGE);
+  return false;
+}
+
+static bool
+sa

[PATCH 11/13] libebl [11/13]: provide access to register file pc, sp

2025-03-16 Thread Serhei Makarov
These additional libebl hooks are needed to extract the location of
the stack frame in-memory from the perf_events register sample,
when dwfl_perf_sample_getframes is implemented in the next patch.

Implementing x86_64 for now, just as with the earlier ebl patches.

* libebl/libebl.h (ebl_sample_base_addr): New function.
  (ebl_sample_pc): New function.
* libebl/eblinitreg_sample.c (ebl_sample_base_addr): New function.
  (ebl_sample_pc): New function.
* libebl/ebl-hooks.h (sample_base_addr): New EBLHOOK.
  (sample_pc): New EBLHOOK.
* backends/x86_64_init.c (x86_64_init): Add sample_base_addr,
  sample_pc hooks.
* backends/x86_64_initreg_sample.c (find_reg): New function to index
  into a perf register array whose contents are described by
  regs_mask. The target index is the index of the register in
  linux arch/x86/include/uapi/asm/perf_regs.h,
  enum perf_event_x86_regs. Note that on other arches
  the 'abi' argument may be required, but on x86 this is
  an unused arg.
  (x86_64_sample_base_addr): Extract stack pointer (reg7).
  (x86_64_sample_pc): Extract program counter (reg8).
---
 backends/x86_64_init.c   |  2 ++
 backends/x86_64_initreg_sample.c | 50 
 libebl/ebl-hooks.h   |  8 +
 libebl/eblinitreg_sample.c   | 18 
 libebl/libebl.h  | 13 +
 5 files changed, 91 insertions(+)

diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c
index 2e6d1df1..6a1cbc4b 100644
--- a/backends/x86_64_init.c
+++ b/backends/x86_64_init.c
@@ -64,6 +64,8 @@ x86_64_init (Elf *elf __attribute__ ((unused)),
   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
index 182f8197..e49b4faf 100644
--- a/backends/x86_64_initreg_sample.c
+++ b/backends/x86_64_initreg_sample.c
@@ -40,6 +40,56 @@
 #include "libebl_CPU.h"
 #include "libebl_PERF_FLAGS.h"
 
+Dwarf_Word
+find_reg (const Dwarf_Word *regs, uint32_t n_regs,
+  uint64_t regs_mask,
+ /* XXX hypothetically needed if the register position varies
+for 32-bit perf_events samples; not needed on x86 */
+ uint32_t abi __attribute__((unused)),
+ 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;
+}
+
+/* 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, uint32_t abi)
+{
+#if !defined(__x86_64__) || !defined(__linux__)
+return 0;
+#else /* __x86_64__ */
+return find_reg(regs, n_regs, regs_mask, abi,
+   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)
+{
+#if !defined(__x86_64__) || !defined(__linux__)
+return 0;
+#else /* __x86_64__ */
+return find_reg(regs, n_regs, regs_mask, abi,
+   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,
diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h
index f0475cdf..05474fbc 100644
--- a/libebl/ebl-hooks.h
+++ b/libebl/ebl-hooks.h
@@ -166,6 +166,14 @@ bool EBLHOOK(set_initial_registers_sample) (const 
Dwarf_Word *regs, uint32_t n_r
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
index 2a387b7a..53244d1e 100644
--- a/libebl/eblinitreg_sample.c
+++ b/libebl/eblinitreg_sample.c
@@ -34,

[PATCH 10/13] eu-stacktrace [10/13]: use dwfl_process_tracker_find_pid

2025-03-16 Thread Serhei Makarov
Initial minimal change to ensure dwfl_process_tracker_find_pid is
tested. For now, we keep the additional dwfltab code in stacktrace.c,
since it's used to track statistics.

In future follow-ups, it will be good to switch to storing
eu-stacktrace statistics in dwfl->process->callbacks_arg. This
requires some additional design to keep the statistics from being lost
when a pid is reused and the corresponding processtracker table entry
is replaced.

* src/stacktrace.c (sysprof_init_dwfl): New function.
  (sysprof_find_dwfl): Rename the existing sysprof_init_dwfl.
  Also use dwfl_process_tracker_find_pid with callback.
  (sysprof_unwind_cb): Rename the existing sysprof_init_dwfl.
---
 src/stacktrace.c | 63 +++-
 1 file changed, 36 insertions(+), 27 deletions(-)

diff --git a/src/stacktrace.c b/src/stacktrace.c
index a6866bd5..89e1866e 100644
--- a/src/stacktrace.c
+++ b/src/stacktrace.c
@@ -96,7 +96,7 @@
 #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_find_dwfl,
sample_set_initial_registers. */
 
 /*
@@ -967,7 +967,34 @@ find_procfile (Dwfl *dwfl, pid_t *pid, Elf **elf, int 
*elf_fd)
 }
 
 Dwfl *
-sysprof_init_dwfl (struct sysprof_unwind_info *sui,
+sysprof_init_dwfl (Dwfl_Process_Tracker *cb_tracker,
+   pid_t pid,
+   void *arg __attribute__ ((unused)))
+{
+  Dwfl *dwfl = dwfl_begin_with_tracker (cb_tracker);
+
+  int err = dwfl_linux_proc_report (dwfl, pid);
+  if (err < 0)
+{
+  if (show_failures)
+   fprintf(stderr, "dwfl_linux_proc_report pid %lld: %s",
+   (long long) pid, dwfl_errmsg (-1));
+  return NULL;
+}
+  err = dwfl_report_end (dwfl, NULL, NULL);
+  if (err != 0)
+{
+  if (show_failures)
+   fprintf(stderr, "dwfl_report_end pid %lld: %s",
+   (long long) pid, dwfl_errmsg (-1));
+  return NULL;
+}
+
+  return dwfl;
+}
+
+Dwfl *
+sysprof_find_dwfl (struct sysprof_unwind_info *sui,
   SysprofCaptureStackUser *ev,
   SysprofCaptureUserRegs *regs)
 {
@@ -980,42 +1007,24 @@ sysprof_init_dwfl (struct sysprof_unwind_info *sui,
   if (regs->n_regs < EXPECTED_REGS) /* XXX expecting everything except FLAGS */
 {
   if (show_failures)
-   fprintf(stderr, N_("sysprof_init_dwfl: n_regs=%d, expected %d\n"),
+   fprintf(stderr, N_("sysprof_find_dwfl: n_regs=%d, expected %d\n"),
regs->n_regs, EXPECTED_REGS);
   return NULL;
 }
 
-  Dwfl *dwfl = pid_find_dwfl(pid);
+  Dwfl *dwfl = dwfl_process_tracker_find_pid (tracker, pid, sysprof_init_dwfl, 
NULL);
   struct __sample_arg *sample_arg;
   bool cached = false;
-  if (dwfl != NULL)
+  if (dwfl != NULL && dwfl->process != NULL)
 {
   sample_arg = dwfl->process->callbacks_arg; /* XXX requires libdwflP.h */
   cached = true;
   goto reuse;
 }
-  dwfl = dwfl_begin_with_tracker (tracker);
-
-  int err = dwfl_linux_proc_report (dwfl, pid);
-  if (err < 0)
-{
-  if (show_failures)
-   fprintf(stderr, "dwfl_linux_proc_report pid %lld: %s",
-   (long long) pid, dwfl_errmsg (-1));
-  return NULL;
-}
-  err = dwfl_report_end (dwfl, NULL, NULL);
-  if (err != 0)
-{
-  if (show_failures)
-   fprintf(stderr, "dwfl_report_end pid %lld: %s",
-   (long long) pid, dwfl_errmsg (-1));
-  return NULL;
-}
 
   Elf *elf = NULL;
   int elf_fd = -1;
-  err = find_procfile (dwfl, &pid, &elf, &elf_fd);
+  int err = find_procfile (dwfl, &pid, &elf, &elf_fd);
   if (err < 0)
 {
   if (show_failures)
@@ -1052,7 +1061,7 @@ sysprof_init_dwfl (struct sysprof_unwind_info *sui,
 
   if (show_frames) {
 bool is_abi32 = (sample_arg->abi == PERF_SAMPLE_REGS_ABI_32);
-fprintf(stderr, "sysprof_init_dwfl pid %lld%s: size=%ld%s pc=%lx 
sp=%lx+(%lx)\n",
+fprintf(stderr, "sysprof_find_dwfl pid %lld%s: size=%ld%s pc=%lx 
sp=%lx+(%lx)\n",
(long long) pid, cached ? " (cached)" : "",
sample_arg->size, is_abi32 ? " (32-bit)" : "",
sample_arg->pc, sample_arg->base_addr,
@@ -1213,7 +1222,7 @@ sysprof_unwind_cb (SysprofCaptureFrame *frame, void *arg)
   SysprofCaptureUserRegs *regs = (SysprofCaptureUserRegs *)tail_ptr;
   if (show_frames)
 fprintf(stderr, "\n"); /* extra newline for padding */
-  Dwfl *dwfl = sysprof_init_dwfl (sui, ev, regs);
+  Dwfl *dwfl = sysprof_find_dwfl (sui, ev, regs);
   if (dwfl == NULL)
 {
   if (show_summary)
@@ -1223,7 +1232,7 @@ sysprof_unwind_cb (SysprofCaptureFrame *frame, void *arg)
  dwfl_ent->lost_samples++;
}
   if (show_failures)
-   fprintf(stderr, "sysprof_init_dwfl pid %lld (%s) (failed)\n",
+   fprintf(stderr, "sysprof_find_dwfl pid %lld (%s) (failed)\n",

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

2025-03-16 Thread Serhei Makarov
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.
---
 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;
-  dwfl_thread_state_registers (thread, i, 1, &sample_arg->regs[j]);
-}
-  return true;
+  Dwfl_Process *process = thread->process;
+  Ebl *ebl = process->ebl;
+  /* XXX Sysprof provides exactly the required registers for unwinding: */
+  uint64_t regs_mask = ebl_perf_frame_regs_mask (ebl);
+  return ebl_set_initial_registers_sample
+(ebl, sample_arg->regs, sample_arg->n_regs, regs_mas

[PATCH 05/13] libdwfl [5/13]: introduce Dwfl_Process_Tracker

2025-03-16 Thread Serhei Makarov
New data structure to coordinate caching Elf data among multiple Dwfl
structs attached to different processes. Meant to reduce the overhead
for profilers that use elfutils for unwinding.

The caching is well-justified, as the prior approach (e.g. in
eu-stacktrace, sysprof-live-unwinder) of creating a separate Dwfl per
process was wildly redundant, opening ~hundreds of file descriptors
just for each common library such as libc.so and leaking the data.

Initial patch just introduces the struct, to be filled in by the rest
of the patch series.

* libdwfl/libdwfl.h (Dwfl_Process_Tracker): New struct.
  (dwfl_process_tracker_begin): New function.
  (dwfl_begin_with_tracker): New function.
  (dwfl_process_tracker_end): New function.
* libdw/libdw.map: Add new functions.
* libdwfl/libdwflP.h (struct Dwfl_Process_Tracker): New struct.
  (struct Dwfl): Add 'tracker' field.
* libdwfl/Makefile.am (libdwfl_a_SOURCES): Add dwfl_process_tracker.c.
* libdwfl/dwfl_process_tracker.c: New file.
  (dwfl_process_tracker_begin): Initialize the tracker.
  (dwfl_begin_with_tracker): Initialize Dwfl * with specified tracker.
  (dwfl_process_tracker_end): Deallocate the tracker.
---
 libdw/libdw.map|  3 ++
 libdwfl/Makefile.am|  3 +-
 libdwfl/dwfl_process_tracker.c | 66 ++
 libdwfl/libdwfl.h  | 17 -
 libdwfl/libdwflP.h |  9 -
 5 files changed, 95 insertions(+), 3 deletions(-)
 create mode 100644 libdwfl/dwfl_process_tracker.c

diff --git a/libdw/libdw.map b/libdw/libdw.map
index 3218fd9c..ad3ec590 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -390,4 +390,7 @@ ELFUTILS_0.193 {
   global:
 dwfl_set_initial_registers_thread;
 dwfl_perf_sample_preferred_regs_mask;
+dwfl_process_tracker_begin;
+dwfl_begin_with_tracker;
+dwfl_process_tracker_end;
 } ELFUTILS_0.192;
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index 37c57bee..b41122e3 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -71,7 +71,8 @@ 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 \
+   dwfl_process_tracker.c \
+dwfl_perf_frame.c \
gzip.c debuginfod-client.c
 
 if BZLIB
diff --git a/libdwfl/dwfl_process_tracker.c b/libdwfl/dwfl_process_tracker.c
new file mode 100644
index ..c42d8ad2
--- /dev/null
+++ b/libdwfl/dwfl_process_tracker.c
@@ -0,0 +1,66 @@
+/* Track multiple Dwfl structs for multiple processes.
+   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 "libdwflP.h"
+
+Dwfl_Process_Tracker *dwfl_process_tracker_begin (const Dwfl_Callbacks 
*callbacks)
+{
+  Dwfl_Process_Tracker *tracker = calloc (1, sizeof *tracker);
+  if (tracker == NULL)
+{
+  __libdwfl_seterrno (DWFL_E_NOMEM);
+  return tracker;
+}
+
+  tracker->callbacks = callbacks;
+  return tracker;
+}
+
+Dwfl *dwfl_begin_with_tracker (Dwfl_Process_Tracker *tracker)
+{
+  Dwfl *dwfl = dwfl_begin (tracker->callbacks);
+  if (dwfl == NULL)
+return dwfl;
+
+  /* TODO: Could also share dwfl->debuginfod, but thread-safely? */
+  dwfl->tracker = tracker;
+  return dwfl;
+}
+
+void dwfl_process_tracker_end (Dwfl_Process_Tracker *tracker)
+{
+  if (tracker == NULL)
+return;
+
+  /* TODO: Call dwfl_end for each Dwfl connected to this tracker. */
+  free (tracker);
+}
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 29505863..86005515 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -32,9 +32,12 @@
 #include "libdw.h"
 #include 
 
-/* Handle for a session using the library.  */
+/* Handle for a session using the library to attach to a single target 
process.  */
 typedef struct Dwfl Dwfl;
 
+/* Handle for a

[PATCH 2/9 v4] libdw: Add locking to dwarf_getsrcfiles, dwarf_getsrclines, dwarf_macro_getsrcfiles

2025-03-16 Thread Aaron Merey
* libdw/dwarf_begin_elf.c (dwarf_begin_elf): Init macro_lock.
* libdw/dwarf_end.c (cu_free): Free src_lock.
(dwarf_end): Free macro_lock.
* libdw/dwarf_getsrcfiles.c (dwarf_getsrcfiles): Use src_lock.
* libdw/dwarf_getsrclines.c (dwarf_getsrclines): Ditto.
* libdw/dwarf_macro_getsrclines.c (dwarf_macro_getsrclines): Use
macro_lock.
* libdw/libdwP.h (struct Dwarf): Define macro_lock.
(struct Dwarf_CU): Define src_lock.
* libdw/libdw_findcu.c (__libdw_intern_next_unit): Init src_lock.

Signed-off-by: Aaron Merey 
---
v3: 
https://patchwork.sourceware.org/project/elfutils/patch/20250220043644.2058519-3-ame...@redhat.com/

v4: Instead of using dwarf_lock, define a new lock for dwarf_getsrclines and
dwarf_getsrcfiles as well as dwarf_macro_getsrcfiles.

 libdw/dwarf_begin_elf.c |  1 +
 libdw/dwarf_end.c   |  2 ++
 libdw/dwarf_getsrcfiles.c   | 11 +++
 libdw/dwarf_getsrclines.c   | 25 +++--
 libdw/dwarf_macro_getsrcfiles.c | 10 +-
 libdw/libdwP.h  | 10 --
 libdw/libdw_findcu.c|  1 +
 7 files changed, 47 insertions(+), 13 deletions(-)

diff --git a/libdw/dwarf_begin_elf.c b/libdw/dwarf_begin_elf.c
index 8a3a939b..58a309e9 100644
--- a/libdw/dwarf_begin_elf.c
+++ b/libdw/dwarf_begin_elf.c
@@ -580,6 +580,7 @@ dwarf_begin_elf (Elf *elf, Dwarf_Cmd cmd, Elf_Scn *scngrp)
   return NULL;
 }
   mutex_init (result->dwarf_lock);
+  mutex_init (result->macro_lock);
   eu_search_tree_init (&result->cu_tree);
   eu_search_tree_init (&result->tu_tree);
   eu_search_tree_init (&result->split_tree);
diff --git a/libdw/dwarf_end.c b/libdw/dwarf_end.c
index 92389c07..c12815e1 100644
--- a/libdw/dwarf_end.c
+++ b/libdw/dwarf_end.c
@@ -70,6 +70,7 @@ cu_free (void *arg)
   Dwarf_Abbrev_Hash_free (&p->abbrev_hash);
   rwlock_fini (p->abbrev_lock);
   rwlock_fini (p->split_lock);
+  mutex_fini (p->src_lock);
 
   /* Free split dwarf one way (from skeleton to split).  */
   if (p->unit_type == DW_UT_skeleton
@@ -130,6 +131,7 @@ dwarf_end (Dwarf *dwarf)
 free (dwarf->mem_tails);
   pthread_rwlock_destroy (&dwarf->mem_rwl);
   mutex_fini (dwarf->dwarf_lock);
+  mutex_fini (dwarf->macro_lock);
 
   /* Free the pubnames helper structure.  */
   free (dwarf->pubnames_sets);
diff --git a/libdw/dwarf_getsrcfiles.c b/libdw/dwarf_getsrcfiles.c
index 24e4b7d2..9becd1d5 100644
--- a/libdw/dwarf_getsrcfiles.c
+++ b/libdw/dwarf_getsrcfiles.c
@@ -47,9 +47,10 @@ dwarf_getsrcfiles (Dwarf_Die *cudie, Dwarf_Files **files, 
size_t *nfiles)
 }
 
   int res = -1;
+  struct Dwarf_CU *const cu = cudie->cu;
+  mutex_lock (cudie->cu->src_lock);
 
   /* Get the information if it is not already known.  */
-  struct Dwarf_CU *const cu = cudie->cu;
   if (cu->files == NULL)
 {
   /* For split units there might be a simple file table (without lines).
@@ -96,7 +97,10 @@ dwarf_getsrcfiles (Dwarf_Die *cudie, Dwarf_Files **files, 
size_t *nfiles)
  Dwarf_Off debug_line_offset;
  if (__libdw_formptr (stmt_list, IDX_debug_line, DWARF_E_NO_DEBUG_LINE,
   NULL, &debug_line_offset) == NULL)
-   return -1;
+   {
+ mutex_unlock (cudie->cu->src_lock);
+ return -1;
+   }
 
  res = __libdw_getsrcfiles (cu->dbg, debug_line_offset,
 __libdw_getcompdir (cudie),
@@ -115,8 +119,7 @@ dwarf_getsrcfiles (Dwarf_Die *cudie, Dwarf_Files **files, 
size_t *nfiles)
*nfiles = cu->files->nfiles;
 }
 
-  // XXX Eventually: unlocking here.
-
+  mutex_unlock (cudie->cu->src_lock);
   return res;
 }
 INTDEF (dwarf_getsrcfiles)
diff --git a/libdw/dwarf_getsrclines.c b/libdw/dwarf_getsrclines.c
index da78db67..be10cdee 100644
--- a/libdw/dwarf_getsrclines.c
+++ b/libdw/dwarf_getsrclines.c
@@ -1442,8 +1442,10 @@ dwarf_getsrclines (Dwarf_Die *cudie, Dwarf_Lines 
**lines, size_t *nlines)
   return -1;
 }
 
-  /* Get the information if it is not already known.  */
   struct Dwarf_CU *const cu = cudie->cu;
+  mutex_lock (cu->src_lock);
+
+  /* Get the information if it is not already known.  */
   if (cu->lines == NULL)
 {
   /* For split units always pick the lines from the skeleton.  */
@@ -1464,10 +1466,13 @@ dwarf_getsrclines (Dwarf_Die *cudie, Dwarf_Lines 
**lines, size_t *nlines)
  *lines = cu->lines;
  *nlines = cu->lines->nlines;
}
+
+ mutex_unlock (cu->src_lock);
  return res;
}
 
  __libdw_seterrno (DWARF_E_NO_DEBUG_LINE);
+ mutex_unlock (cu->src_lock);
  return -1;
}
 
@@ -1485,21 +1490,29 @@ dwarf_getsrclines (Dwarf_Die *cudie, Dwarf_Lines 
**lines, size_t *nlines)
   Dwarf_Off debug_line_offset;
   if (__libdw_formptr (stmt_list, IDX_debug_line, DWARF_E_NO_DEBUG_L

[PATCH 06/13] libdwfl [6/13]: Elf* caching via dwfl_process_tracker

2025-03-16 Thread Serhei Makarov
The Dwfl_Process_Tracker includes a dynamicsizehash cache which maps
file paths to Elf * (or rather, dwfl_tracker_elf_info * storing fd and
Elf *).  We provide a dwfl_process_tracker_find_elf callback which
checks the cache for an already-loaded Elf * and, if missing,
populates the cache with the fd returned by dwfl_linux_proc_find_elf.

Later, open_elf updates the cache with the Elf * for that fd.  The
commented asserts still catch some cases where a redundant Elf * is
being created without checking the cache.

Since the Elf * outlasts the Dwfl that created it, we use the
(convenient, already-existing) reference count field in Elf * to
retain the data in the table.  Then dwfl_end calling elf_end will
decrement the refcount cleanly, and dwfl_process_tracker_end will
issue another elf_end call.

* libdwfl/libdwfl.h (dwfl_process_tracker_find_elf): New function,
  serves as a cached version of the dwfl_linux_proc_find_elf callback.
* libdwfl/libdwflP.h (dwfltracker_elf_info): New struct typedef.
  (struct Dwfl_Process_Tracker): Add dynamicsizehash table of
  dwfltracker_elf_info structs.
* libdwfl/dwfl_process_tracker_elftab.c: New file, instantiates
  lib/dynamicsizehash_concurrent.c to store dwfltracker_elf_info
  structs.
* libdwfl/dwfl_process_tracker_elftab.h: New file, ditto.
* libdwfl/libdwfl_next_prime.c: New file.
* libdwfl/dwfl_process_tracker.c (dwfl_process_tracker_begin): Init elftab.
  (dwfl_process_tracker_end): Clean up elftab. Lock and iterate the
  hash to free tracker->elftab.table items.
* libdwfl/dwfl_process_tracker_find_elf.c: New file.
* libdwfl/dwfl_module_getdwarf.c (open_elf): Cache file->elf in
  Dwfl_Process_Tracker. Must be done here as dwfl_linux_proc_find_elf
  opens an fd but does not yet create the Elf *.  Also, increment
  Elf * refcount so the table retains the Elf * after caller's
  dwfl_end cleanup.
* libdwfl/Makefile.am (libdwfl_a_SOURCES): Add
  dwfl_process_tracker_find_elf.c, dwfl_process_tracker_elftab.c,
  libdwfl_next_prime.c.
* libdw/libdw.map: Add dwfl_process_tracker_find_elf.
---
 libdw/libdw.map |   1 +
 libdwfl/Makefile.am |   5 +-
 libdwfl/dwfl_module_getdwarf.c  |  23 -
 libdwfl/dwfl_process_tracker.c  |  23 +
 libdwfl/dwfl_process_tracker_elftab.c   |  46 ++
 libdwfl/dwfl_process_tracker_elftab.h   |  40 +
 libdwfl/dwfl_process_tracker_find_elf.c | 111 
 libdwfl/libdwfl.h   |   7 ++
 libdwfl/libdwflP.h  |  17 +++-
 libdwfl/libdwfl_next_prime.c|   6 ++
 10 files changed, 275 insertions(+), 4 deletions(-)
 create mode 100644 libdwfl/dwfl_process_tracker_elftab.c
 create mode 100644 libdwfl/dwfl_process_tracker_elftab.h
 create mode 100644 libdwfl/dwfl_process_tracker_find_elf.c
 create mode 100644 libdwfl/libdwfl_next_prime.c

diff --git a/libdw/libdw.map b/libdw/libdw.map
index ad3ec590..1a2a879f 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -393,4 +393,5 @@ ELFUTILS_0.193 {
 dwfl_process_tracker_begin;
 dwfl_begin_with_tracker;
 dwfl_process_tracker_end;
+dwfl_process_tracker_find_elf;
 } ELFUTILS_0.192;
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index b41122e3..f05f12af 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -71,8 +71,9 @@ 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_process_tracker.c \
-dwfl_perf_frame.c \
+   dwfl_process_tracker.c dwfl_process_tracker_find_elf.c \
+   dwfl_process_tracker_elftab.c libdwfl_next_prime.c \
+   dwfl_perf_frame.c \
gzip.c debuginfod-client.c
 
 if BZLIB
diff --git a/libdwfl/dwfl_module_getdwarf.c b/libdwfl/dwfl_module_getdwarf.c
index 6f98c02b..ec8f76be 100644
--- a/libdwfl/dwfl_module_getdwarf.c
+++ b/libdwfl/dwfl_module_getdwarf.c
@@ -1,5 +1,5 @@
 /* Find debugging and symbol information for a module in libdwfl.
-   Copyright (C) 2005-2012, 2014, 2015 Red Hat, Inc.
+   Copyright (C) 2005-2012, 2014, 2015, 2025 Red Hat, Inc.
This file is part of elfutils.
 
This file is free software; you can redistribute it and/or modify
@@ -79,6 +79,27 @@ open_elf (Dwfl_Module *mod, struct dwfl_file *file)
   if (error != DWFL_E_NOERROR)
 return error;
 
+  /* Cache file->elf in Dwfl_Process_Tracker if available: */
+  if (mod->dwfl->tracker != NULL && file->name != NULL)
+{
+  dwfltracker_elf_info *ent = dwfltracker_elftab_find 
(&mod->dwfl->tracker->elftab, elf_hash(file->name));
+  if (ent != NULL)
+   {
+ /* TODO(REVIEW): The following assertions are still
+triggered on certain code paths that acquire fds or
+create El