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
+++ 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,29 @@ 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)
+    {
+      rwlock_wrlock (&mod->dwfl->tracker->elftab_lock);
+      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 Elf structs without checking the caching mechanism
+            first.  This is not a serious problem, and can be fixed
+            gradually. */
+
+         /* assert(ent->elf == NULL || ent->elf == file->elf); */ /* Guard 
against redundant/leaked Elf *. */
+         /* assert(ent->fd == file->fd); */ /* Guard against redundant open. */
+
+         ent->elf = file->elf;
+         /* XXX Dwfl_Process_Tracker also holds the Elf * jointly with the 
caller: */
+         ent->elf->ref_count++;
+       }
+      rwlock_unlock (&mod->dwfl->tracker->elftab_lock);
+    }
+
   GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (file->elf, &ehdr_mem);
   if (ehdr == NULL)
     {
diff --git a/libdwfl/dwfl_process_tracker.c b/libdwfl/dwfl_process_tracker.c
index c42d8ad2..24ee2da7 100644
--- a/libdwfl/dwfl_process_tracker.c
+++ b/libdwfl/dwfl_process_tracker.c
@@ -32,6 +32,8 @@
 
 #include "libdwflP.h"
 
+#define HTAB_DEFAULT_SIZE 1021
+
 Dwfl_Process_Tracker *dwfl_process_tracker_begin (const Dwfl_Callbacks 
*callbacks)
 {
   Dwfl_Process_Tracker *tracker = calloc (1, sizeof *tracker);
@@ -41,6 +43,9 @@ Dwfl_Process_Tracker *dwfl_process_tracker_begin (const 
Dwfl_Callbacks *callback
       return tracker;
     }
 
+  dwfltracker_elftab_init (&tracker->elftab, HTAB_DEFAULT_SIZE);
+  rwlock_init (tracker->elftab_lock);
+
   tracker->callbacks = callbacks;
   return tracker;
 }
@@ -61,6 +66,26 @@ void dwfl_process_tracker_end (Dwfl_Process_Tracker *tracker)
   if (tracker == NULL)
     return;
 
+  /* HACK to allow iteration of dynamicsizehash_concurrent.  */
+  /* XXX Based on lib/dynamicsizehash_concurrent.c free().  */
+  rwlock_fini (tracker->elftab_lock);
+  pthread_rwlock_destroy(&tracker->elftab.resize_rwl);
+  for (size_t idx = 1; idx <= tracker->elftab.size; idx++)
+    {
+      dwfltracker_elftab_ent *ent = &tracker->elftab.table[idx];
+      if (ent->hashval == 0)
+       continue;
+      dwfltracker_elf_info *t = (dwfltracker_elf_info *) atomic_load_explicit 
(&ent->val_ptr,
+                                                                              
memory_order_relaxed);
+      free(t->module_name);
+      if (t->fd >= 0)
+       close(t->fd);
+      if (t->elf != NULL)
+       elf_end(t->elf);
+      free(t); /* TODO: Check necessity. */
+    }
+  free (tracker->elftab.table);
+
   /* TODO: Call dwfl_end for each Dwfl connected to this tracker. */
   free (tracker);
 }
diff --git a/libdwfl/dwfl_process_tracker_elftab.c 
b/libdwfl/dwfl_process_tracker_elftab.c
new file mode 100644
index 00000000..af1f3ce3
--- /dev/null
+++ b/libdwfl/dwfl_process_tracker_elftab.c
@@ -0,0 +1,46 @@
+/* Dwfl_Process_Tracker Elf table implementation.
+   Copyright (C) 2025 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <string.h>
+
+#include <libdwflP.h>
+
+/* Definitions for the Elf table. */
+#define TYPE dwfltracker_elf_info *
+#define NAME dwfltracker_elftab
+#define ITERATE 1
+/* TODO(REVIEW): Omit reverse? */
+#define REVERSE 1
+#define COMPARE(a, b) \
+  strcmp ((a)->module_name, (b)->module_name)
+
+#include "../lib/dynamicsizehash_concurrent.c"
diff --git a/libdwfl/dwfl_process_tracker_elftab.h 
b/libdwfl/dwfl_process_tracker_elftab.h
new file mode 100644
index 00000000..d78a7394
--- /dev/null
+++ b/libdwfl/dwfl_process_tracker_elftab.h
@@ -0,0 +1,40 @@
+/* Dwfl_Process_Tracker Elf table.
+   Copyright (C) 2025 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef DWFL_PROCESS_TRACKER_ELFTAB_H
+#define DWFL_PROCESS_TRACKER_ELFTAB_H 1
+
+/* Definitions for the Elf table.  */
+#define TYPE dwfltracker_elf_info *
+#define NAME dwfltracker_elftab
+#define ITERATE 1
+#define COMPARE(a, b) \
+  strcmp ((a)->module_name, (b)->module_name)
+#include <dynamicsizehash_concurrent.h>
+
+#endif
diff --git a/libdwfl/dwfl_process_tracker_find_elf.c 
b/libdwfl/dwfl_process_tracker_find_elf.c
new file mode 100644
index 00000000..72621bb1
--- /dev/null
+++ b/libdwfl/dwfl_process_tracker_find_elf.c
@@ -0,0 +1,152 @@
+/* Find Elf file from dwfl_linux_proc_report, cached via Dwfl_Process_Tracker.
+   Copyright (C) 2025, Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/stat.h>
+#include "../libelf/libelfP.h"
+/* XXX: Private header needed for Elf * ref_count field. */
+/* TODO: Consider dup_elf() rather than direct ref_count access. */
+
+#include "libdwflP.h"
+
+/* TODO: Consider making this a public api, 
dwfl_process_tracker_find_cached_elf. */
+bool
+find_cached_elf (Dwfl_Process_Tracker *tracker,
+                const char *module_name,
+                char **file_name, Elf **elfp, int *fdp)
+{
+  dwfltracker_elf_info *ent = NULL;
+  int rc;
+  struct stat sb;
+
+  unsigned long int hval = elf_hash(module_name);
+
+  rwlock_rdlock(tracker->elftab_lock);
+  ent = dwfltracker_elftab_find(&tracker->elftab, hval);
+  rwlock_unlock(tracker->elftab_lock);
+
+  if (ent == NULL)
+    return false;
+
+  /* Verify that ent->fd has not been updated: */
+  rc = fstat(ent->fd, &sb);
+  if (rc < 0 || ent->dev != sb.st_dev || ent->ino != sb.st_ino
+      || ent->last_mtime != sb.st_mtime)
+    return false;
+
+  if (ent->elf != NULL)
+    ent->elf->ref_count++;
+  *elfp = ent->elf;
+  *file_name = strdup(ent->module_name);
+  *fdp = ent->fd;
+  return true;
+}
+
+/* TODO: Consider making this a public api, dwfl_process_tracker_cache_elf. */
+bool
+cache_elf (Dwfl_Process_Tracker *tracker,
+          const char *module_name,
+          char *file_name __attribute__((unused)),
+          Elf *elf, int fd)
+{
+  dwfltracker_elf_info *ent = NULL;
+  int rc;
+  struct stat sb;
+
+  unsigned long int hval = elf_hash(module_name);
+  rc = fstat(fd, &sb);
+
+  rwlock_wrlock(tracker->elftab_lock);
+  ent = dwfltracker_elftab_find(&tracker->elftab, hval);
+  if (ent == NULL)
+    {
+      ent = calloc (1, sizeof (dwfltracker_elf_info));
+      ent->module_name = strdup(module_name);
+
+      if (dwfltracker_elftab_insert(&tracker->elftab, hval, ent) != 0)
+       {
+         free(ent->module_name);
+         free(ent);
+         rwlock_unlock(tracker->elftab_lock);
+         assert(false); /* Should not occur due to the wrlock on elftab. */
+       }
+    }
+  else
+    {
+      /* Safe to replace the existing elf, keep module_name. */
+      if (ent->elf != NULL)
+       elf_end(ent->elf);
+      return true;
+    }
+  if (elf != NULL)
+    elf->ref_count++;
+  ent->elf = elf;
+  ent->fd = fd;
+  if (rc == 0) /* TODO(REVIEW): Report rc != 0 via errno? */
+    {
+      ent->dev = sb.st_dev;
+      ent->ino = sb.st_ino;
+      ent->last_mtime = sb.st_mtime;
+    }
+  rwlock_unlock(tracker->elftab_lock);
+  return true;
+}
+
+int
+dwfl_process_tracker_find_elf (Dwfl_Module *mod,
+                              void **userdata __attribute__ ((unused)),
+                              const char *module_name, Dwarf_Addr base,
+                              char **file_name, Elf **elfp)
+{
+  /* TODO(REVIEW): Assuming this isn't called with elfp already set. */
+  assert (*elfp == NULL);
+
+  Dwfl_Process_Tracker *tracker = mod->dwfl->tracker;
+  int fd;
+
+  if (tracker != NULL
+      && find_cached_elf (tracker, module_name,
+                         file_name, elfp, &fd))
+    {
+      return fd;
+    }
+
+  fd = INTUSE(dwfl_linux_proc_find_elf) (mod, userdata, module_name,
+                                        base, file_name, elfp);
+
+  if (tracker != NULL && fd >= 0 && *file_name != NULL)
+    {
+      cache_elf (tracker, module_name,
+                *file_name, *elfp, fd);
+    }
+  return fd;
+}
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 0db5b74d..a4408a26 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -409,6 +409,13 @@ extern int dwfl_linux_proc_find_elf (Dwfl_Module *mod, 
void **userdata,
                                     const char *module_name, Dwarf_Addr base,
                                     char **file_name, Elf **);
 
+/* 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 **);
+
 /* Standard argument parsing for using a standard callback set.  */
 struct argp;
 extern const struct argp *dwfl_standard_argp (void) __const_attribute__;
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 885acae0..ae849233 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -101,12 +101,28 @@ typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error;
 extern int __libdwfl_canon_error (Dwfl_Error) internal_function;
 extern void __libdwfl_seterrno (Dwfl_Error) internal_function;
 
+/* Hash table for Elf *. */
+typedef struct
+{
+  char *module_name; /* dwfltracker_elftab_ent is used iff non-NULL.  */
+  int fd;
+  Elf *elf;
+  dev_t dev;
+  ino_t ino;
+  time_t last_mtime;
+} dwfltracker_elf_info;
+#include "dwfl_process_tracker_elftab.h"
+
 struct Dwfl_Process_Tracker
 {
   const Dwfl_Callbacks *callbacks;
-  /* ... */
+
+  /* Table of cached Elf * including fd, path, fstat info.  */
+  dwfltracker_elftab elftab;
+  rwlock_define(, elftab_lock);
 };
 
+
 /* Resources we might keep for the user about the core file that the
    Dwfl might have been created from.  Can currently only be set
    through std-argp.  */
diff --git a/libdwfl/libdwfl_next_prime.c b/libdwfl/libdwfl_next_prime.c
new file mode 100644
index 00000000..f99d4c6c
--- /dev/null
+++ b/libdwfl/libdwfl_next_prime.c
@@ -0,0 +1,6 @@
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#define next_prime attribute_hidden __libdwfl_next_prime
+#include "../lib/next_prime.c"
-- 
2.47.0

Reply via email to