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

2025-04-04 Thread Mark Wielaard
Hi Samuel,

On Thu, 2025-04-03 at 17:24 +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.
> 
> Signed-off-by: Samuel Zeter 

Thanks, looks good. Committed with a slightly tweaked commit message:

commit ef1ca49f5289ba8836a5f98b6ee8e387b96ebaf3
Author: Samuel Zeter 
Date:   Thu Apr 3 17:24:13 2025 +1000

readelf: Add 'Key to Flags' to eu-readelf --section-headers output

When printing section headers, also include a key to what each flag
is at the end of the section header output.

   * src/readelf.c (print_flag_info): New function.
   (print_shdr): Call print_flag_info.
   * tests/run-copyadd-sections.sh: Fix .extra grep by escaping \.
   * tests/run-large-elf-file.sh: Likewise.
   * tests/test-copymany-subr.sh: Likewise.
   * tests/run-readelf-z.sh: Add Key to Flags to expected output.
   * tests/run-retain.sh: Likewise.
   * tests/run-strip-remove-keep.sh: Likewise.

https://sourceware.org/bugzilla/show_bug.cgi?id=29571

Signed-off-by: Samuel Zeter 

Cheers,

Mark


Re: [PATCH] Avoid double-including config.h

2025-04-04 Thread Mark Wielaard
Hi Dmitry,

On Thu, 2025-04-03 at 19:28 +0300, Dmitry V. Levin wrote:
> I've accidentally noticed that this hunk was not correct back in 2017
> because "#if HAVE_CONFIG_H" is not the same as "#ifdef HAVE_CONFIG_H".
> This was not problematic for elfutils itself because HAVE_CONFIG_H is
> always defined.
> 
> Note, however, that as a side effect of commit 76c84c137a82 ("handle libc
> implementations which do not provide `error.h`") "system.h" now includes
>  unconditionally, essentially reintroducing the original issue
> of including config.h twice for all files that include both 
> and "system.h".

So to be (pedantically) correct should we include the attached?

Thanks,

Mark
From f14c00adb24bf15059821ca972e5d20a58b893fa Mon Sep 17 00:00:00 2001
From: Mark Wielaard 
Date: Fri, 4 Apr 2025 13:50:04 +0200
Subject: [PATCH] lib: Prevent double inclusion of config.h through system.h

Files that include both  and "system.h" might include
config.h twice. Prevent that by adding a guard check in system.h
before including config.h.

   * lib/error.h: Check HAVE_CONFIG_H before including config.h.
   * lib/system.h: Check both HAVE_CONFIG_H and whether
   EU_CONFIG_H is defined before including config.h.

Signed-off-by: Mark Wielaard 
---
 lib/error.c  | 4 +++-
 lib/system.h | 7 ++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/lib/error.c b/lib/error.c
index 5186fc15e9d5..75c9eafb58ba 100644
--- a/lib/error.c
+++ b/lib/error.c
@@ -26,7 +26,9 @@
the GNU Lesser General Public License along with this program.  If
not, see .  */
 
-#include 
+#ifdef HAVE_CONFIG_H
+# include 
+#endif
 
 #if !defined(HAVE_ERROR_H) && defined(HAVE_ERR_H)
 #include 
diff --git a/lib/system.h b/lib/system.h
index 0698e5ffbe2f..c17e2aa0fea1 100644
--- a/lib/system.h
+++ b/lib/system.h
@@ -31,7 +31,12 @@
 #ifndef LIB_SYSTEM_H
 #define LIB_SYSTEM_H	1
 
-#include 
+/* Prevent double inclusion of config.h, config.h includes eu-config.h.  */
+#ifdef HAVE_CONFIG_H
+#ifndef EU_CONFIG_H
+# include 
+#endif
+#endif
 
 #include 
 #include 
-- 
2.48.1



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

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

Mark Wielaard  changed:

   What|Removed |Added

 Status|NEW |RESOLVED
 Resolution|--- |FIXED

--- Comment #8 from Mark Wielaard  ---
commit ef1ca49f5289ba8836a5f98b6ee8e387b96ebaf3
Author: Samuel Zeter 
Date:   Thu Apr 3 17:24:13 2025 +1000

readelf: Add 'Key to Flags' to eu-readelf --section-headers output

When printing section headers, also include a key to what each flag
is at the end of the section header output.

   * src/readelf.c (print_flag_info): New function.
   (print_shdr): Call print_flag_info.
   * tests/run-copyadd-sections.sh: Fix .extra grep by escaping \.
   * tests/run-large-elf-file.sh: Likewise.
   * tests/test-copymany-subr.sh: Likewise.
   * tests/run-readelf-z.sh: Add Key to Flags to expected output.
   * tests/run-retain.sh: Likewise.
   * tests/run-strip-remove-keep.sh: Likewise.

https://sourceware.org/bugzilla/show_bug.cgi?id=29571

Signed-off-by: Samuel Zeter 

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

Re: [PATCH 1/3] Add some supporting framework for C11-style atomics.

2025-04-04 Thread Dmitry V. Levin
Hi Mark,

On Fri, Apr 04, 2025 at 01:38:01PM +0200, Mark Wielaard wrote:
> Hi Dmitry,
> 
> On Thu, 2025-04-03 at 19:09 +0300, Dmitry V. Levin wrote:
> > On Thu, Aug 29, 2019 at 03:16:12PM +0200, Mark Wielaard wrote:
> > > From: Jonathon Anderson 
> > > 
> > > Uses the stdatomic.h provided by FreeBSD when GCC doesn't (ie. GCC < 4.9)
> > > 
> > > Signed-off-by: Jonathon Anderson 
> > > Signed-off-by: Srđan Milaković 
> [...]
> > > diff --git a/configure.ac b/configure.ac
> > > index c443fa3b..b8aba460 100644
> > > --- a/configure.ac
> > > +++ b/configure.ac
> > > @@ -226,6 +226,18 @@ LDFLAGS="$save_LDFLAGS"])
> > >  AS_IF([test "x$ac_cv_tls" != xyes],
> > >AC_MSG_ERROR([__thread support required]))
> > >  
> > > +dnl Before 4.9 gcc doesn't ship stdatomic.h, but the nessesary atomics 
> > > are
> > > +dnl available by (at least) 4.7. So if the system doesn't have a 
> > > stdatomic.h we
> > > +dnl fall back on one copied from FreeBSD that handles the difference.
> > > +AC_CACHE_CHECK([whether gcc provides stdatomic.h], ac_cv_has_stdatomic,
> > > +  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include ]])],
> > > +  ac_cv_has_stdatomic=yes, ac_cv_has_stdatomic=no)])
> > > +AM_CONDITIONAL(HAVE_STDATOMIC_H, test "x$ac_cv_has_stdatomic" = xyes)
> > > +AS_IF([test "x$ac_cv_has_stdatomic" = xyes], 
> > > [AC_DEFINE(HAVE_STDATOMIC_H)])
> > > +
> > > +AH_TEMPLATE([HAVE_STDATOMIC_H], [Define to 1 if `stdatomic.h` is 
> > > provided by the
> > > + system, 0 otherwise.])
> > > +
> > >  dnl This test must come as early as possible after the compiler 
> > > configuration
> > >  dnl tests, because the choice of the file model can (in principle) affect
> > >  dnl whether functions and headers are available, whether they work, etc.
> > 
> > Sorry, do you remember why stdatomic.h availability check had to be so
> > complicated instead of straightforward AC_CHECK_HEADERS([stdatomic.h])?
> 
> I am afraid I don't remember.
> 
> Note that 5 years later we just require C11 + stdatomic:
> https://sourceware.org/cgit/elfutils/commit/?id=d5fe635875628a9de431c883c0e5037dec0f85f3
> The check for stdatomic.h is still the same though. But it doesn't set
> HAVE_STDATOMIC_H anymore and the fallback lib/atomics.h has been
> removed.
> 
> I think that means your issue/worry cannot happen anymore.

Sorry, I must have been looking at some old commit and missed that
lib/atomics.h has been removed.

> But maybe we should just use AC_CHECK_HEADERS([stdatomic.h]) and drop
> the AC_COMPILE_IFELSE trick?

Sure.  And if we want to keep AC_MSG_ERROR, then
  AC_CHECK_HEADERS([stdatomic.h], [], [AC_MSG_ERROR([stdatomic.h required])])
should be fine.


-- 
ldv


Re: [PATCH] Avoid double-including config.h

2025-04-04 Thread Dmitry V. Levin
Hi Mark,

On Fri, Apr 04, 2025 at 01:55:48PM +0200, Mark Wielaard wrote:
> Hi Dmitry,
> 
> On Thu, 2025-04-03 at 19:28 +0300, Dmitry V. Levin wrote:
> > I've accidentally noticed that this hunk was not correct back in 2017
> > because "#if HAVE_CONFIG_H" is not the same as "#ifdef HAVE_CONFIG_H".
> > This was not problematic for elfutils itself because HAVE_CONFIG_H is
> > always defined.
> > 
> > Note, however, that as a side effect of commit 76c84c137a82 ("handle libc
> > implementations which do not provide `error.h`") "system.h" now includes
> >  unconditionally, essentially reintroducing the original issue
> > of including config.h twice for all files that include both 
> > and "system.h".
> 
> So to be (pedantically) correct should we include the attached?

Yes, this should be fine, along with

--- a/lib/crc32.c
+++ b/lib/crc32.c
@@ -28 +28 @@
-#if HAVE_CONFIG_H
+#ifdef HAVE_CONFIG_H


-- 
ldv


Re: ☠ Buildbot (Sourceware): elfutils - failed test (failure) (main)

2025-04-04 Thread Mark Wielaard
Hi,

On Fri, 2025-04-04 at 12:39 +, buil...@sourceware.org wrote:
> A new failure has been detected on builder elfutils-debian-armhf while 
> building elfutils.
> 
> Full details are available at:
> https://builder.sourceware.org/buildbot/#/builders/6/builds/403
> 
> Build state: failed test (failure)
> Revision: ef1ca49f5289ba8836a5f98b6ee8e387b96ebaf3
> Worker: debian-armhf
> Build Reason: (unknown)
> Blamelist: Samuel Zeter 
> [...]
> - 7: make check ( failure )
> Logs:
> - stdio: 
> https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/7/logs/stdio
> - test-suite.log: 
> https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/7/logs/test-suite_log

Not caused by Samuel's patch. This is the backtrace testcase that also
occassionally fails on other arches. But we haven't seen it fail in
armhf for a year, but in the last 9 days it has failed 3 times already
:{
https://builder.sourceware.org/buildbot/#/builders/elfutils-debian-armhf

H,

Mark


☠ Buildbot (Sourceware): elfutils - failed test (failure) (main)

2025-04-04 Thread builder
A new failure has been detected on builder elfutils-debian-armhf while building 
elfutils.

Full details are available at:
https://builder.sourceware.org/buildbot/#/builders/6/builds/403

Build state: failed test (failure)
Revision: ef1ca49f5289ba8836a5f98b6ee8e387b96ebaf3
Worker: debian-armhf
Build Reason: (unknown)
Blamelist: Samuel Zeter 

Steps:

- 0: worker_preparation ( success )

- 1: set package name ( success )

- 2: git checkout ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/2/logs/stdio

- 3: autoreconf ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/3/logs/stdio

- 4: configure ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/4/logs/stdio
- config.log: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/4/logs/config_log

- 5: get version ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/5/logs/stdio
- property changes: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/5/logs/property_changes

- 6: make ( warnings )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/6/logs/stdio
- warnings (3): 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/6/logs/warnings__3_

- 7: make check ( failure )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/7/logs/stdio
- test-suite.log: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/7/logs/test-suite_log

- 8: prep ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/8/logs/stdio

- 9: build bunsen.cpio.gz ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/9/logs/stdio

- 10: fetch bunsen.cpio.gz ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/10/logs/stdio

- 11: unpack bunsen.cpio.gz ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/11/logs/stdio

- 12: pass .bunsen.source.* ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/12/logs/stdio

- 13: upload to bunsen ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/13/logs/stdio

- 14: clean up ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/14/logs/stdio

- 15: make distclean ( success )
Logs:
- stdio: 
https://builder.sourceware.org/buildbot/#/builders/6/builds/403/steps/15/logs/stdio



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

2025-04-04 Thread Serhei Makarov



On Thu, Apr 3, 2025, at 1:19 PM, Serhei Makarov wrote:
> On Sun, Mar 16, 2025, at 7:15 PM, Serhei Makarov wrote:
>> +/* The same callback, except this first attempts to look up a cached
>> +   Elf* and fd from the Dwfl_Module's Dwfl_Process_Tracker (if any).
>> +   If a new Elf* has to be created, this saves it to the cache.  */
>> +extern int dwfl_process_tracker_find_elf (Dwfl_Module *mod, void **userdata,
>> + const char *module_name, Dwarf_Addr base,
>> + char **file_name, Elf **);
> In the reworked patchset I'm trying to expand this api a bit
> to allow other find_elf callbacks to be used with caching.
>
> I'm thinking to introduce a pair of functions for more direct cache access:
>
> bool dwfl_process_tracker_find_cached_elf (tracker, module_name, 
> file_name, elfp, &fd);
> bool dwfl_process_tracker_cache_elf (tracker, module_name, file_name, 
> elf, fd);
> 
> ...
>
> Might come up with a better idea after thinking a bit more today, but 
> this is the best I have so far.
Considering this again; going to implement these functions
(since they make the code significantly cleaner) but
avoid making them public for now. Looking at the code where they
might be useful in Sysprof, I notice that for containerized code
they load "/proc/%pid/root/%modulename", which is not a key
that's useful for caching across different pids. Using just %modulename
(the path) is also dangerous due to it referring to different instances of a 
module
across different containers or the system root. So either the API needs to 
support
some other key for caching, or it can't be applied here. The problem here
is not being able to tell from the /proc/%pid path which %pids
belong to the same container, let alone when the same container
image is multiply instantiated.


Re: [PATCH 1/3] Add some supporting framework for C11-style atomics.

2025-04-04 Thread Mark Wielaard
Hi Dmitry,

On Thu, 2025-04-03 at 19:09 +0300, Dmitry V. Levin wrote:
> On Thu, Aug 29, 2019 at 03:16:12PM +0200, Mark Wielaard wrote:
> > From: Jonathon Anderson 
> > 
> > Uses the stdatomic.h provided by FreeBSD when GCC doesn't (ie. GCC < 4.9)
> > 
> > Signed-off-by: Jonathon Anderson 
> > Signed-off-by: Srđan Milaković 
[...]
> > diff --git a/configure.ac b/configure.ac
> > index c443fa3b..b8aba460 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -226,6 +226,18 @@ LDFLAGS="$save_LDFLAGS"])
> >  AS_IF([test "x$ac_cv_tls" != xyes],
> >AC_MSG_ERROR([__thread support required]))
> >  
> > +dnl Before 4.9 gcc doesn't ship stdatomic.h, but the nessesary atomics are
> > +dnl available by (at least) 4.7. So if the system doesn't have a 
> > stdatomic.h we
> > +dnl fall back on one copied from FreeBSD that handles the difference.
> > +AC_CACHE_CHECK([whether gcc provides stdatomic.h], ac_cv_has_stdatomic,
> > +  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include ]])],
> > +ac_cv_has_stdatomic=yes, ac_cv_has_stdatomic=no)])
> > +AM_CONDITIONAL(HAVE_STDATOMIC_H, test "x$ac_cv_has_stdatomic" = xyes)
> > +AS_IF([test "x$ac_cv_has_stdatomic" = xyes], [AC_DEFINE(HAVE_STDATOMIC_H)])
> > +
> > +AH_TEMPLATE([HAVE_STDATOMIC_H], [Define to 1 if `stdatomic.h` is provided 
> > by the
> > + system, 0 otherwise.])
> > +
> >  dnl This test must come as early as possible after the compiler 
> > configuration
> >  dnl tests, because the choice of the file model can (in principle) affect
> >  dnl whether functions and headers are available, whether they work, etc.
> 
> Sorry, do you remember why stdatomic.h availability check had to be so
> complicated instead of straightforward AC_CHECK_HEADERS([stdatomic.h])?

I am afraid I don't remember.

Note that 5 years later we just require C11 + stdatomic:
https://sourceware.org/cgit/elfutils/commit/?id=d5fe635875628a9de431c883c0e5037dec0f85f3
The check for stdatomic.h is still the same though. But it doesn't set
HAVE_STDATOMIC_H anymore and the fallback lib/atomics.h has been
removed.

I think that means your issue/worry cannot happen anymore.
But maybe we should just use AC_CHECK_HEADERS([stdatomic.h]) and drop
the AC_COMPILE_IFELSE trick?

Cheers,

Mark


[PATCH v2 05/12] libdwfl [5/12]: introduce Dwfl_Process_Tracker

2025-04-04 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 |  7 
 5 files changed, 94 insertions(+), 2 deletions(-)
 create mode 100644 libdwfl/dwfl_process_tracker.c

diff --git a/libdw/libdw.map b/libdw/libdw.map
index d835f442..936ec973 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -389,4 +389,7 @@ ELFUTILS_0.192 {
 ELFUTILS_0.193 {
   global:
 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 967feb42..0db5b74d 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 session using the libr

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

2025-04-04 Thread Serhei Makarov
Changes for v2:

- No longer exposing this in public libdwfl.h api.

* * *

Renaming pid_set_initial_registers to
__libdwfl_set_initial_registers_thread and adding to libdwflP.h.

This callback was private to one file, but now we need to access it
from the perf_events sampling code as well.

* libdwfl/libdwflP.h (__libdwfl_set_initial_registers_thread): New function.
* libdwfl/linux-pid-attach.c (__libdwfl_set_initial_registers_thread):
  Renamed from pid_thread_state_registers_cb.
  (pid_set_initial_registers): Pass the newly renamed callback.
---
 libdwfl/libdwflP.h | 11 ++-
 libdwfl/linux-pid-attach.c | 12 +++-
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 6ec5c966..2b7eb8da 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -1,5 +1,5 @@
 /* Internal definitions for libdwfl.
-   Copyright (C) 2005-2015, 2018, 2024 Red Hat, Inc.
+   Copyright (C) 2005-2015, 2018, 2024-2025 Red Hat, Inc.
This file is part of elfutils.
 
This file is free software; you can redistribute it and/or modify
@@ -591,6 +591,15 @@ extern Dwfl_Module *__libdwfl_report_offline (Dwfl *dwfl, 
const char *name,
 extern void __libdwfl_process_free (Dwfl_Process *process)
   internal_function;
 
+/* 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.  */
+extern bool __libdwfl_set_initial_registers_thread (int firstreg,
+unsigned nregs,
+const Dwarf_Word *regs,
+void *arg);
+
 /* Update STATE->unwound for the unwound frame.
On error STATE->unwound == NULL
or STATE->unwound->pc_state == DWFL_FRAME_STATE_ERROR;
diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c
index 0eec1e88..a6e4e41a 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,11 @@ 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
+/* XXX No internal_function annotation,
+   as this function gets passed as ebl_tid_registers_t *.  */
+__libdwfl_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 +340,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);
+   __libdwfl_set_initial_registers_thread, 
thread);
 }
 
 static void
-- 
2.47.0



[PATCH v2 10/12] eu-stacktrace [10/12]: use dwfl_process_tracker_find_pid

2025-04-04 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 v2 09/12] libdwfl [9/12]: add dwfl_process_tracker_find_pid

2025-04-04 Thread Serhei Makarov
Changes for v2:

- Add locking for dwfltab.

* * *

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.  The newly created Dwfl will be added to the
  dwfltab automatically when its pid is confirmed by a call to
  dwfl_attach_state.
* libdw/libdw.map: Add dwfl_process_tracker_find_pid.
---
 libdw/libdw.map|  1 +
 libdwfl/dwfl_process_tracker.c | 26 ++
 libdwfl/libdwfl.h  | 11 +++
 3 files changed, 38 insertions(+)

diff --git a/libdw/libdw.map b/libdw/libdw.map
index a52d1b87..99fd9105 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -393,4 +393,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 dd93f16e..caf6a8ff 100644
--- a/libdwfl/dwfl_process_tracker.c
+++ b/libdwfl/dwfl_process_tracker.c
@@ -67,6 +67,32 @@ 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;
+
+  rwlock_rdlock (tracker->dwfltab_lock);
+  dwfltracker_dwfl_info *ent = dwfltracker_dwfltab_find(&tracker->dwfltab, 
pid);
+  rwlock_unlock (tracker->dwfltab_lock);
+
+  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 a4408a26..64b3ee82 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 v2 03/12] libebl [3/12]: eu-stacktrace: use new register handling api

2025-04-04 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 v2 06/12] libdwfl [6/12]: Elf* caching via dwfl_process_tracker

2025-04-04 Thread Serhei Makarov
Changes for v2:

- Add locking for elftab. This is needed in addition to the
  intrinsic locking in dynamicsizehash_concurrent to avoid
  having cache_elf expose an incomplete dwfltracker_elf_info*
  entry to other threads while its data is being populated /
  replaced.

- Tidy dwfl_process_tracker_find_elf.c into the main find_elf
  callback and two functions to consider (in future) making into
  a public api for custom cached callbacks.

* * *

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 + associated rwlock.
* 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, implements
  a find_elf callback that wraps dwfl_linux_proc_find_elf
  with additional caching logic.
* 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.
  (noinst_HEADERS): Add dwfl_process_tracker_elftab.h.
* libdw/libdw.map: Add dwfl_process_tracker_find_elf.
---
 libdw/libdw.map |   1 +
 libdwfl/Makefile.am |   7 +-
 libdwfl/dwfl_module_getdwarf.c  |  25 +++-
 libdwfl/dwfl_process_tracker.c  |  25 
 libdwfl/dwfl_process_tracker_elftab.c   |  46 +++
 libdwfl/dwfl_process_tracker_elftab.h   |  40 +++
 libdwfl/dwfl_process_tracker_find_elf.c | 152 
 libdwfl/libdwfl.h   |   7 ++
 libdwfl/libdwflP.h  |  18 ++-
 libdwfl/libdwfl_next_prime.c|   6 +
 10 files changed, 322 insertions(+), 5 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 936ec973..a52d1b87 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -392,4 +392,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..dd99db46 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
@@ -94,7 +95,7 @@ libeu = ../lib/libeu.a
 libdwfl_pic_a_SOURCES =
 am_libdwfl_pic_a_OBJECTS = $(libdwfl_a_SOURCES:.c=.os)
 
-noinst_HEADERS = libdwflP.h
+noinst_HEADERS = libdwflP.h dwfl_process_tracker_elftab.h
 
 EXTRA_libdwfl_a_DEPENDENCIES = libdwfl.manifest
 
diff --git a/libdwfl/dwfl_module_getdwarf.c b/libdwfl/dwfl_module_getdwarf.c
index 6f98c02b..2518c3aa 100644
--- a/libdwfl/dwfl_module_getdwarf.c
+++

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

2025-04-04 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.
  (noinst_HEADERS): Add dwfl_process_tracker_dwfltab.h.
---
 libdwfl/Makefile.am|  3 +-
 libdwfl/dwfl_end.c |  5 +-
 libdwfl/dwfl_frame.c   |  6 +-
 libdwfl/dwfl_process_tracker.c | 79 +-
 libdwfl/dwfl_process_tracker_dwfltab.c | 46 +++
 libdwfl/dwfl_process_tracker_dwfltab.h | 42 ++
 libdwfl/libdwflP.h | 19 +++
 7 files changed, 195 insertions(+), 5 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 dd99db46..11886742 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
 
@@ -95,7 +96,7 @@ libeu = ../lib/libeu.a
 libdwfl_pic_a_SOURCES =
 am_libdwfl_pic_a_OBJECTS = $(libdwfl_a_SOURCES:.c=.os)
 
-noinst_HEADERS = libdwflP.h dwfl_process_tracker_elftab.h
+noinst_HEADERS = libdwflP.h dwfl_process_tracker_elftab.h 
dwfl_process_tracker_dwfltab.h
 
 EXTRA_libdwfl_a_DEPENDENCIES = libdwfl.manifest
 
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 24ee2da7..dd93f16e 100644
--- a/libdwfl/dwfl_process_tracker.c
+++ b/libdwfl/dwfl_process_tracker.c
@@ -45,6 +45,8 @@ Dwfl_Process_Tracker *dwfl_process_tracker_begin (const 
Dwfl_Callbacks *callback
 
   dwfltracker_elftab_init (&tracker->elftab, HTAB_DEFAULT_SIZE);
   rwlock_init (tracker->elftab_lock);
+  dwfltracker_dwfltab_init (&tracker->dwfltab, HTAB_DEFAULT_SIZE);
+  rwlock_init (tracker->dwfltab_lock);
 
   tracker->callbacks = callbacks;
   return tracker;
@@ -58,19 +60,77 @@ Dwfl *dwfl_begin_with_tracker (Dwfl_Process_Tracker 
*tracker)
 
   /* TODO: Could also share dwfl->debuginfod, but

[PATCH v2 04/12] libdwfl [4/12]: add dwfl_perf_sample_preferred_regs_mask

2025-04-04 Thread Serhei Makarov
Changes for v2:

- guard the linux/perf_events.h include properly with an #if defined __linux__

* * *

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.

It feels like an implementation detail, but is unfortunately needed
because the profiler invoking perf_event_open and the elfutils
unwinding code need to agree on the set of registers to be
requested. So the profiler has to ask elfutils for this detail.

* 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   |  5 
 libdwfl/Makefile.am   |  3 +-
 libdwfl/dwfl_perf_frame.c | 63 +++
 libdwfl/libdwfl.h | 12 +++-
 4 files changed, 81 insertions(+), 2 deletions(-)
 create mode 100644 libdwfl/dwfl_perf_frame.c

diff --git a/libdw/libdw.map b/libdw/libdw.map
index bc53385f..d835f442 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_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 ..6eea251d
--- /dev/null
+++ b/libdwfl/dwfl_perf_frame.c
@@ -0,0 +1,63 @@
+/* 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
+
+#if defined(__linux__)
+# include 
+#endif
+
+#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 90523283..967feb42 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
@@ -817,6

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

2025-04-04 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 v2 01/12] libebl [1/12]: api for perf register handling, start with x86_64

2025-04-04 Thread Serhei Makarov
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_PER

[PATCH v2 12/12] eu-stacktrace [12/12]: use dwfl_perf_sample_getframes

2025-04-04 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.
  (parse_opt): Remove show_memory_reads.
  (main): Remove show_memory_reads.
---
 src/stacktrace.c | 233 +++
 1 file changed, 33 insertions(+), 200 deletions(-)

diff --git a/src/stacktrace.c b/src/stacktrace.c
index 89e1866e..d43b70de 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;
@@ -190,9 +191,6 @@ static bool show_summary = true;
 #define ELFUTILS_STACKTRACE_VERBOSE_ENV_VAR "ELFUTILS_STACKTRACE_VERBOSE"
 /* Valid values that turn on diagnostics: 'true', 'verbose', 'debug', '1', 
'2'. */
 
-/* Enables detailed tracing of simulated memory reads: */
-/* #define DEBUG_MEMORY_READ */
-
 /* Enables even more diagnostics on modules: */
 /* #define DEBUG_MODULES */
 
@@ -469,144 +467,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);
-
- 

[PATCH v2 11/12] libdwfl [11/12]: add dwfl_perf_sample_getframes

2025-04-04 Thread Serhei Makarov
Changes for v2:

- use renamed __libdwfl_set_initial_registers_thread

- oops, should use provided sample_arg->perf_regs_mask
  in sample_set_initial registers

* * *

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
  (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 | 192 +-
 libdwfl/libdwfl.h |  15 ++-
 3 files changed, 206 insertions(+), 2 deletions(-)

diff --git a/libdw/libdw.map b/libdw/libdw.map
index 99fd9105..3d535dcd 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -394,4 +394,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 6eea251d..f277d7b4 100644
--- a/libdwfl/dwfl_perf_frame.c
+++ b/libdwfl/dwfl_perf_frame.c
@@ -60,4 +60,194 @@ 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) + a