[PATCH] translate.cxx: parallelize emit_symbol_data

Aaron Merey amerey@redhat.com
Fri Jul 3 00:04:24 GMT 2026


When stap is built against an elfutils that defines
_ELFUTILS_THREAD_SAFE in <elfutils/version.h>, dump each module's
symbol/unwind data (dump_unwindsyms) on a boost::asio thread pool.
Each worker handles one module.

The dwfl_getmodules scan loops stay on the main thread and act as
a dispatcher.  unwindsym_dump_context is now per-module. Worker output
buffers are flushed in module order, so the emitted C should be
byte-identical to a serial run. Also add a test that checks whether two
-p3 runs produce byte-identical stap_symbols.c.

Signed-off-by: Aaron Merey <amerey@redhat.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---
Benchmarks on a 32-core x86_64 (Fedora, kernel-debuginfo installed,
warm debuginfod cache, median of 5, pass-3 real time) using an
experimental elfutils built from
https://gitlab.com/amerey1/elfutils-ai-lab/-/tree/thread-safety-benchmark

  stap -p3 --ldd -e 'probe process("/usr/bin/ffmpeg").begin
      { print_ubacktrace() }'                 # 318 user DSOs
    0.65s -> 0.15s (4.4x)
  stap -p3 --all-modules -e 'probe timer.profile
      { print_backtrace() }'                  # kernel + 116 modules
    4.17s -> 1.62s (2.6x)
  stap -p3 --all-modules --ldd -d /usr/bin/ffmpeg -e 'probe
      timer.profile { print_backtrace(); print_ubacktrace() }'
    4.83s -> 1.64s (2.9x)                     # 434 modules

 main.cxx                                      |   2 +-
 session.cxx                                   |   2 +
 session.h                                     |  27 +-
 setupdwfl.cxx                                 | 128 +++--
 .../systemtap.base/translate_determinism.exp  |  46 ++
 translate.cxx                                 | 516 ++++++++++++------
 6 files changed, 506 insertions(+), 215 deletions(-)
 create mode 100644 testsuite/systemtap.base/translate_determinism.exp

diff --git a/main.cxx b/main.cxx
index 8586ca05b..c246ba93f 100644
--- a/main.cxx
+++ b/main.cxx
@@ -309,7 +309,7 @@ printscript(systemtap_session& s, ostream& o)
 }
 
 
-int pending_interrupts;
+std::atomic<int> pending_interrupts;
 
 extern "C"
 void handle_interrupt (int)
diff --git a/session.cxx b/session.cxx
index 9bd7e9365..b2e45170c 100644
--- a/session.cxx
+++ b/session.cxx
@@ -2631,6 +2631,8 @@ systemtap_session::print_error_details (std::ostream& message,
 void
 systemtap_session::print_warning (const string& message_str, const token* tok)
 {
+  stap_mutex_guard g (print_warning_mutex);
+
   // Only output in dump mode if -vv is supplied:
   if (suppress_warnings && (!dump_mode || verbose <= 1))
     return; // NB: don't count towards suppressed_warnings count
diff --git a/session.h b/session.h
index 6df2b1ffc..69f821ab4 100644
--- a/session.h
+++ b/session.h
@@ -15,12 +15,14 @@
 #include <locale.h>
 #endif
 
+#include <atomic>
 #include <list>
 #include <string>
 #include <vector>
 #include <iostream>
 #include <sstream>
 #include <map>
+#include <mutex>
 #include <set>
 #include <stdexcept>
 
@@ -28,6 +30,7 @@ extern "C" {
 #include <sys/resource.h>
 #include <signal.h>
 #include <elfutils/libdw.h>
+#include <elfutils/version.h>
 #include <pwd.h>
 }
 
@@ -127,6 +130,27 @@ struct parse_error: public std::runtime_error
     }
 };
 
+/* If _ELFUTILS_THREAD_SAFE is not defined in <elfutils/version.h>
+   then stap_mutex is a no-op.  */
+#ifdef _ELFUTILS_THREAD_SAFE
+using stap_mutex = std::mutex;
+using stap_mutex_guard = std::lock_guard<stap_mutex>;
+#else
+struct stap_mutex
+{
+  void lock () noexcept {}
+  void unlock () noexcept {}
+  bool try_lock () noexcept { return true; }
+};
+
+class stap_mutex_guard
+{
+public:
+  explicit stap_mutex_guard (stap_mutex&) noexcept {}
+  stap_mutex_guard (const stap_mutex_guard&) = delete;
+  stap_mutex_guard& operator= (const stap_mutex_guard&) = delete;
+};
+#endif
 
 struct symresolution_info;
 
@@ -443,6 +467,7 @@ public:
   // NB: It is very important for all of the above (and below) fields
   // to be cleared in the systemtap_session ctor (session.cxx).
 
+  stap_mutex print_warning_mutex;
   std::set<std::string> seen_warnings;
   int suppressed_warnings;
   std::map<std::string, int> seen_errors; // NB: can change to a set if threshold is 1
@@ -530,7 +555,7 @@ struct exit_exception: public std::runtime_error
 
 
 // global counter of SIGINT/SIGTERM's received
-extern int pending_interrupts;
+extern std::atomic<int> pending_interrupts;
 
 // Interrupt exception subclass for catching
 // interrupts (i.e. ctrl-c).
diff --git a/setupdwfl.cxx b/setupdwfl.cxx
index 2da213900..e915e2009 100644
--- a/setupdwfl.cxx
+++ b/setupdwfl.cxx
@@ -658,69 +658,87 @@ internal_find_debuginfo (Dwfl_Module *mod,
   if(!current_session_for_find_debuginfo->download_dbinfo || abrt_path.empty())
     goto call_dwfl_standard_find_debuginfo;
 
-  /* Check that we haven't already run this */
-  if (install_dbinfo_failed < 0)
+  {
+    /* The symbol-dump worker threads (translate.cxx:emit_symbol_data) can
+       reach this path concurrently.  */
+    static stap_mutex download_mutex;
+
+    /* Check that we haven't already run this */
     {
-      if(current_session_for_find_debuginfo->verbose > 1)
-        current_session_for_find_debuginfo->print_warning(_F("We already tried running '%s'", abrt_path.c_str()));
-      goto call_dwfl_standard_find_debuginfo;
+      stap_mutex_guard g (download_mutex);
+      if (install_dbinfo_failed < 0)
+        {
+          if(current_session_for_find_debuginfo->verbose > 1)
+            current_session_for_find_debuginfo->print_warning(_F("We already tried running '%s'", abrt_path.c_str()));
+          goto call_dwfl_standard_find_debuginfo;
+        }
     }
 
-  /* Extract the build ID */
-  const unsigned char *bits;
-  GElf_Addr vaddr;
-  if(current_session_for_find_debuginfo->verbose > 2)
-    clog << _("Extracting build ID.") << endl;
-  bits_length = dwfl_module_build_id(mod, &bits, &vaddr);
+    /* Extract the build ID.  */
+    const unsigned char *bits;
+    GElf_Addr vaddr;
+    if(current_session_for_find_debuginfo->verbose > 2)
+      clog << _("Extracting build ID.") << endl;
+    bits_length = dwfl_module_build_id(mod, &bits, &vaddr);
 
-  /* Convert the binary bits to a hex string */
-  hex = hex_dump(bits, bits_length);
+    /* Convert the binary bits to a hex string */
+    hex = hex_dump(bits, bits_length);
 
-  /* Search for the debuginfo with the build ID */
-  if(current_session_for_find_debuginfo->verbose > 2)
-    clog << _F("Searching for debuginfo with build ID: '%s'.", hex.c_str()) << endl;
-  if (bits_length > 0)
-    {
-      int fd = dwfl_build_id_find_debuginfo(mod,
-             NULL, NULL, 0,
-             NULL, NULL, 0,
-             debuginfo_file_name);
-      if (fd >= 0)
-        return fd;
-    }
+    /* Search for the debuginfo with the build ID */
+    if(current_session_for_find_debuginfo->verbose > 2)
+      clog << _F("Searching for debuginfo with build ID: '%s'.", hex.c_str()) << endl;
+    if (bits_length > 0)
+      {
+        int fd = dwfl_build_id_find_debuginfo(mod,
+               NULL, NULL, 0,
+               NULL, NULL, 0,
+               debuginfo_file_name);
+        if (fd >= 0)
+          return fd;
+      }
 
-  /* The above failed, so call abrt-action-install-debuginfo-to-abrt-cache
-  to download and install the debuginfo */
-  if(current_session_for_find_debuginfo->verbose > 1)
-    clog << _F("Downloading and installing debuginfo with build ID: '%s' using %s.",
-            hex.c_str(), abrt_path.c_str()) << endl;
-
-  struct tms tms_before;
-  times (& tms_before);
-  struct timeval tv_before;
-  struct tms tms_after;
-  unsigned _sc_clk_tck;
-  struct timeval tv_after;
-  gettimeofday (&tv_before, NULL);
-
-  if(execute_abrt_action_install_debuginfo_to_abrt_cache (hex) < 0)
     {
-      install_dbinfo_failed = -1;
-      current_session_for_find_debuginfo->print_warning(_F("%s failed.", abrt_path.c_str()));
-      goto call_dwfl_standard_find_debuginfo;
-    }
+      /* The above failed, so call abrt-action-install-debuginfo-to-abrt-cache
+         to download and install the debuginfo.  Re-check the flag under the
+         lock in case another thread failed while we were looking up the
+         build ID above.  */
+      stap_mutex_guard g (download_mutex);
 
-  _sc_clk_tck = sysconf (_SC_CLK_TCK);
-  times (& tms_after);
-  gettimeofday (&tv_after, NULL);
-  if(current_session_for_find_debuginfo->verbose > 1)
-    clog << _("Download completed in ")
-              << ((tms_after.tms_cutime + tms_after.tms_utime
-              - tms_before.tms_cutime - tms_before.tms_utime) * 1000 / (_sc_clk_tck)) << "usr/"
-              << ((tms_after.tms_cstime + tms_after.tms_stime
-              - tms_before.tms_cstime - tms_before.tms_stime) * 1000 / (_sc_clk_tck)) << "sys/"
-              << ((tv_after.tv_sec - tv_before.tv_sec) * 1000 +
-              ((long)tv_after.tv_usec - (long)tv_before.tv_usec) / 1000) << "real ms"<< endl;
+      if (install_dbinfo_failed < 0)
+        goto call_dwfl_standard_find_debuginfo;
+
+      if(current_session_for_find_debuginfo->verbose > 1)
+        clog << _F("Downloading and installing debuginfo with build ID: '%s' using %s.",
+                hex.c_str(), abrt_path.c_str()) << endl;
+
+      struct tms tms_before;
+      times (& tms_before);
+      struct timeval tv_before;
+      struct tms tms_after;
+      unsigned _sc_clk_tck;
+      struct timeval tv_after;
+      gettimeofday (&tv_before, NULL);
+
+      if(execute_abrt_action_install_debuginfo_to_abrt_cache (hex) < 0)
+        {
+          install_dbinfo_failed = -1;
+          current_session_for_find_debuginfo->print_warning(_F("%s failed.", abrt_path.c_str()));
+          goto call_dwfl_standard_find_debuginfo;
+        }
+
+      _sc_clk_tck = sysconf (_SC_CLK_TCK);
+      times (& tms_after);
+      gettimeofday (&tv_after, NULL);
+      if(current_session_for_find_debuginfo->verbose > 1)
+        clog << _("Download completed in ")
+                  << ((tms_after.tms_cutime + tms_after.tms_utime
+                  - tms_before.tms_cutime - tms_before.tms_utime) * 1000 / (_sc_clk_tck)) << "usr/"
+                  << ((tms_after.tms_cstime + tms_after.tms_stime
+                  - tms_before.tms_cstime - tms_before.tms_stime) * 1000 / (_sc_clk_tck)) << "sys/"
+                  << ((tv_after.tv_sec - tv_before.tv_sec) * 1000 +
+                  ((long)tv_after.tv_usec - (long)tv_before.tv_usec) / 1000) << "real ms"<< endl;
+    }
+  }
 
   call_dwfl_standard_find_debuginfo:
 
diff --git a/testsuite/systemtap.base/translate_determinism.exp b/testsuite/systemtap.base/translate_determinism.exp
new file mode 100644
index 000000000..d8972e91a
--- /dev/null
+++ b/testsuite/systemtap.base/translate_determinism.exp
@@ -0,0 +1,46 @@
+# Pass-3 translation output must be byte-identical from run to run.  In
+# particular stap_symbols.c -- the per-module symbol/unwind data emitted
+# by translate.cxx:emit_symbol_data(), which is dumped by several worker
+# threads when stap is built against a thread-safe elfutils -- must not
+# depend on thread scheduling.
+
+set test "translate_determinism"
+
+set script {probe begin { print_ubacktrace() }}
+set dirs {}
+set failed 0
+
+for {set i 1} {$i <= 2} {incr i} {
+    set errf ""
+    catch {set errf [exec mktemp]}
+    catch {exec stap -p3 -k --ldd -d /bin/sh -e $script > /dev/null 2> $errf}
+    set err ""
+    catch {set err [exec cat $errf]}
+    catch {exec rm -f $errf}
+    if {![regexp {Keeping temporary directory "([^"]*)"} $err -> dir]} {
+        fail "$test (run $i: no kept temporary directory)"
+        set failed 1
+        break
+    }
+    lappend dirs $dir
+}
+
+if {!$failed} {
+    set d1 [lindex $dirs 0]
+    set d2 [lindex $dirs 1]
+    # NB: compare by content, not directory listing: the generated main
+    # source embeds the (differing) pid in its file name.
+    set rc1 [catch {eval exec diff [glob $d1/stap_symbols.c] [glob $d2/stap_symbols.c]} out1]
+    set rc2 [catch {eval exec diff [glob $d1/stap_*_src.c] [glob $d2/stap_*_src.c]} out2]
+    if {$rc1 == 0 && $rc2 == 0} {
+        pass $test
+    } elseif {$rc1 != 0} {
+        fail "$test (stap_symbols.c differs between runs)"
+    } else {
+        fail "$test (translated source differs between runs)"
+    }
+}
+
+foreach d $dirs {
+    if {[string match "/tmp/stap*" $d]} { catch {exec rm -rf $d} }
+}
diff --git a/translate.cxx b/translate.cxx
index a04cf78b0..5cee1d3da 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -33,6 +33,26 @@
 #include <cstring>
 #include <cerrno>
 
+#include <deque>
+#include <exception>
+#include <elfutils/version.h> // for _ELFUTILS_THREAD_SAFE
+
+// Symbol/unwind dumping is parallelized (see emit_symbol_data) only when
+// elfutils is thread-safe; otherwise boost::asio is not needed at all.
+#ifdef _ELFUTILS_THREAD_SAFE
+#include <thread>
+#ifdef HAVE_BOOST_ASIO_THREAD_POOL_HPP
+#include <boost/asio/thread_pool.hpp>
+#else
+#error "elfutils thread-safety support requires boost/asio/thread_pool.hpp"
+#endif
+#ifdef HAVE_BOOST_ASIO_POST_HPP
+#include <boost/asio/post.hpp>
+#else
+#error "elfutils thread-safety support requires boost/asio/post.hpp"
+#endif
+#endif
+
 extern "C" {
 #include <dwarf.h>
 #include <elfutils/libdwfl.h>
@@ -6791,12 +6811,23 @@ c_unparser::visit_hist_op (hist_op*)
 
 typedef map<Dwarf_Addr,const char*> addrmap_t; // NB: plain map, sorted by address
 
+// One context per module (see dump_unwindsyms), so each worker task touches
+// only its own context and no per-field locking is needed.
 struct unwindsym_dump_context
 {
   systemtap_session& session;
-  ostream& output;
   unsigned stp_module_index;
 
+  string modname;          // module name; valid after the owning Dwfl is freed
+  ostringstream output;    // translated output for this module
+  ostringstream log;       // buffered verbose logging, flushed in module order
+
+  // Processing result; DWARF_CB_OK iff this module's data was emitted.
+  // Defaults to DWARF_CB_ABORT so an unfinished module is not emitted.
+  int res;
+  // First exception from processing; rethrown on the main thread after join.
+  std::exception_ptr pending_exception;
+
   int build_id_len;
   unsigned char *build_id_bits;
   GElf_Addr build_id_vaddr;
@@ -6824,7 +6855,31 @@ struct unwindsym_dump_context
   void *debug_line_str;
   size_t debug_line_str_len;
 
-  set<string> undone_unwindsym_modules;
+  unwindsym_dump_context (systemtap_session& s, unsigned modindex)
+    : session (s),
+      stp_module_index (modindex),
+      res (DWARF_CB_ABORT),
+      build_id_len (0),
+      build_id_bits (NULL),
+      build_id_vaddr (0),
+      stp_kretprobe_trampoline_addr (~0UL),
+      stext_offset (0),
+      debug_frame (NULL),
+      debug_len (0),
+      debug_frame_hdr (NULL),
+      debug_frame_hdr_len (0),
+      debug_frame_off (0),
+      eh_frame (NULL),
+      eh_frame_hdr (NULL),
+      eh_len (0),
+      eh_frame_hdr_len (0),
+      eh_addr (0),
+      eh_frame_hdr_addr (0),
+      debug_line (NULL),
+      debug_line_len (0),
+      debug_line_str (NULL),
+      debug_line_str_len (0)
+  {}
 };
 
 static bool need_byte_swap_for_target (const unsigned char e_ident[])
@@ -7093,8 +7148,8 @@ dump_build_id (Dwfl_Module *m,
 
     if (c->session.verbose > 1)
       {
-        clog << _F("Found build-id in %s, length %d, start at %#" PRIx64,
-                   name, build_id_len, build_id_vaddr) << endl;
+        c->log << _F("Found build-id in %s, length %d, start at %#" PRIx64,
+                     name, build_id_len, build_id_vaddr) << endl;
       }
 
     c->build_id_len = build_id_len;
@@ -7478,7 +7533,7 @@ dump_symbol_tables (Dwfl_Module *m,
 			       ki >= 0);
 
 		  if (c->session.verbose > 2)
-		    clog << _F("Found kernel _stext extra offset %#" PRIx64,
+		    c->log << _F("Found kernel _stext extra offset %#" PRIx64,
 			       extra_offset) << endl;
 
 		  if (! c->session.need_symbols
@@ -8001,8 +8056,6 @@ dump_unwindsym_cxt (Dwfl_Module *m,
 
   c->output << "};\n\n";
 
-  c->undone_unwindsym_modules.erase (modname);
-
   // release various malloc'd tables
   // if (eh_frame_hdr) free (eh_frame_hdr); -- nope, this one comes from the elf image in memory
   if (debug_frame_hdr) free (debug_frame_hdr);
@@ -8070,11 +8123,111 @@ static void dump_kallsyms(unwindsym_dump_context *c)
   c->output << ".num_sections = sizeof(_stp_module_" << stpmod_idx << "_sections)/"
             << "sizeof(struct _stp_section),\n";
   c->output << "};\n\n";
+}
+
+// Set by the first module task that throws; the dispatcher then stops taking
+// on new modules.  Declared before the worker pool so it outlives the tasks.
+struct dump_failure_flag
+{
+  stap_mutex mutex;
+  bool failed;
+
+  dump_failure_flag () : failed (false) {}
+};
+
+// Dispatcher state.  dwfl_getmodules() runs the callback only on the main
+// thread, so no locking here; worker tasks never touch this struct.
+struct dump_dispatch
+{
+  systemtap_session& session;
+  deque<unwindsym_dump_context>& ctxs;
+  unsigned& modindex;
+  ostream& kallsyms_out;
+#ifdef _ELFUTILS_THREAD_SAFE
+  boost::asio::thread_pool& pool;
+#endif
+  dump_failure_flag& failure;
+};
+
+// Dump one module into c->output, exactly as the original serial code did.
+// May run on a worker thread, so exceptions are captured for later rethrow.
+static void
+process_module (unwindsym_dump_context *c, dump_failure_flag *f, Dwfl_Module *m,
+                const char *name, Dwarf_Addr base)
+{
+  // We want to extract several bits of information:
+  //
+  // - parts of the program-header that map the file's physical offsets to the text section
+  // - section table: just a list of section (relocation) base addresses
+  // - symbol table of the text-like sections, with all addresses relativized to each base
+  // - the contents of .debug_frame and/or .eh_frame section, for unwinding purposes
+  // Interrupted?  Skip; emit_symbol_data re-checks after the pool is joined.
+  if (pending_interrupts)
+    {
+      c->res = DWARF_CB_ABORT;
+      return;
+    }
+
+  try
+    {
+      int res = dump_build_id (m, c, name, base);
+
+      if (res == DWARF_CB_OK)
+        res = dump_section_list (m, c, name, base);
+
+      // We always need to check the symbols of the kernel if we use it,
+      // for the extra_offset (also used for build_ids) and possibly
+      // stp_kretprobe_trampoline_addr for the dwarf unwinder.
+      if (res == DWARF_CB_OK
+          && (c->session.need_symbols || ! strcmp (name, "kernel")))
+        res = dump_symbol_tables (m, c, name, base);
 
-  c->undone_unwindsym_modules.erase("kernel");
-  c->stp_module_index++;
+      if (res == DWARF_CB_OK && c->session.need_unwind)
+        res = dump_unwind_tables (m, c, name, base);
+
+      if (res == DWARF_CB_OK && c->session.need_lines)
+        // we dont gate on dump_line_tables()'s result because unwindsym stuff
+        // should still get dumped to the output even if gathering debug_line
+        // data fails
+        (void) dump_line_tables (m, c, name, base);
+
+      // And finally dump everything collected into c->output.
+      if (res == DWARF_CB_OK)
+        res = dump_unwindsym_cxt (m, c, name, base);
+
+      c->res = res;
+    }
+  catch (...)
+    {
+      c->pending_exception = std::current_exception ();
+      c->res = DWARF_CB_ABORT;
+
+      // Stop the dispatcher feeding new modules.
+      stap_mutex_guard g (f->mutex);
+      f->failed = true;
+    }
+
+  // Release scratch tables early; only the dump results are read after this.
+  c->addrmap.clear ();
+  c->seclist.clear ();
 }
 
+// Owns the Dwfls opened during the scan so an exception cannot leak them.
+// In the threaded arm they must stay open until the pool has been joined.
+struct dwfl_collection
+{
+  vector<Dwfl *> dwfls;
+
+  void keep (Dwfl *dwfl) { dwfls.push_back (dwfl); }
+  void end_all ()
+  {
+    for (vector<Dwfl *>::iterator it = dwfls.begin (); it != dwfls.end (); ++it)
+      dwfl_end (*it);
+    dwfls.clear ();
+  }
+  ~dwfl_collection () { end_all (); }
+};
+
 static int
 dump_unwindsyms (Dwfl_Module *m,
                  void **userdata __attribute__ ((unused)),
@@ -8085,83 +8238,65 @@ dump_unwindsyms (Dwfl_Module *m,
   if (pending_interrupts)
     return DWARF_CB_ABORT;
 
-  unwindsym_dump_context *c = (unwindsym_dump_context*) arg;
-  assert (c);
+  dump_dispatch *d = (dump_dispatch *) arg;
+  assert (d);
+
+  // After a failure, stop taking on new modules and drain the iteration;
+  // the error is rethrown after the scan.
+  {
+    stap_mutex_guard g (d->failure.mutex);
+    if (d->failure.failed)
+      return DWARF_CB_ABORT;
+  }
 
   // skip modules/files we're not actually interested in
   string modname = name;
-  if (c->session.unwindsym_modules.find(modname)
-      == c->session.unwindsym_modules.end())
+  if (d->session.unwindsym_modules.find (modname)
+      == d->session.unwindsym_modules.end ())
     return DWARF_CB_OK;
 
-  if (c->session.verbose > 1)
-    clog << "dump_unwindsyms " << name
-         << " index=" << c->stp_module_index
-         << " base=0x" << hex << base << dec << endl;
-
-  // We want to extract several bits of information:
-  //
-  // - parts of the program-header that map the file's physical offsets to the text section
-  // - section table: just a list of section (relocation) base addresses
-  // - symbol table of the text-like sections, with all addresses relativized to each base
-  // - the contents of .debug_frame and/or .eh_frame section, for unwinding purposes
+  // Allocated on the main thread; the deque keeps the pointers captured by
+  // worker tasks stable as more contexts are appended.
+  d->ctxs.emplace_back (d->session, d->modindex++);
+  unwindsym_dump_context *c = &d->ctxs.back ();
+  c->modname = modname;
+
+  if (d->session.verbose > 1)
+    c->log << "dump_unwindsyms " << name
+           << " index=" << c->stp_module_index
+           << " base=0x" << hex << base << dec << endl;
+
+  // The task references Dwfl-owned storage (m, name), so dwfl_end() must be
+  // deferred until the pool has been joined.
+  dump_failure_flag *f = &d->failure;
+#ifdef _ELFUTILS_THREAD_SAFE
+  boost::asio::post (d->pool, [c, f, m, name, base] {
+    process_module (c, f, m, name, base);
+  });
+#else
+  // Stream the header first, as the pre-threading code did.
+  clog << c->log.str ();
+  c->log.str (string ());
+
+  process_module (c, f, m, name, base);
+
+  // Serial: stream this module's log and output right away (as the
+  // pre-threading code did), keeping peak memory at one module's worth.
+  clog << c->log.str ();
+  d->kallsyms_out << c->output.str ();
+  c->log.str (string ());
+  c->output.str (string ());
+#endif
 
-  int res = DWARF_CB_OK;
-
-  c->build_id_len = 0;
-  c->build_id_vaddr = 0;
-  c->build_id_bits = NULL;
-  res = dump_build_id (m, c, name, base);
-
-  c->seclist.clear();
-  if (res == DWARF_CB_OK)
-    res = dump_section_list(m, c, name, base);
-
-  // We always need to check the symbols of the kernel if we use it,
-  // for the extra_offset (also used for build_ids) and possibly
-  // stp_kretprobe_trampoline_addr for the dwarf unwinder.
-  c->addrmap.clear();
-  if (res == DWARF_CB_OK
-      && (c->session.need_symbols || ! strcmp(name, "kernel")))
-    res = dump_symbol_tables (m, c, name, base);
-
-  c->debug_frame = NULL;
-  c->debug_len = 0;
-  c->debug_frame_hdr = NULL;
-  c->debug_frame_hdr_len = 0;
-  c->debug_frame_off = 0;
-  c->eh_frame = NULL;
-  c->eh_frame_hdr = NULL;
-  c->eh_len = 0;
-  c->eh_frame_hdr_len = 0;
-  c->eh_addr = 0;
-  c->eh_frame_hdr_addr = 0;
-  if (res == DWARF_CB_OK && c->session.need_unwind)
-    res = dump_unwind_tables (m, c, name, base);
-
-  c->debug_line = NULL;
-  c->debug_line_len = 0;
-  c->debug_line_str = NULL;
-  c->debug_line_str_len = 0;
-  if (res == DWARF_CB_OK && c->session.need_lines)
-    // we dont set res = dump_line_tables() because unwindsym stuff should still
-    // get dumped to the output even if gathering debug_line data fails
-    (void) dump_line_tables (m, c, name, base);
-
-  /* And finally dump everything collected in the output. */
-  if (res == DWARF_CB_OK)
-    res = dump_unwindsym_cxt (m, c, name, base);
-
-  if (res == DWARF_CB_OK)
-    c->stp_module_index++;
-
-  return res;
+  return DWARF_CB_OK;
 }
 
 
 // Emit symbol table & unwind data, plus any calls needed to register
 // them with the runtime.
-void emit_symbol_data_done (unwindsym_dump_context*, systemtap_session&);
+void emit_symbol_data_done (const vector<unsigned>& module_indices, ostream&,
+			    unsigned long, const set<string>&,
+			    systemtap_session&);
 
 
 void
@@ -8272,12 +8407,17 @@ prepare_symbol_data (systemtap_session& s)
   // step 0.5: add vdso(s) when vma tracker was requested
   if (vma_tracker_enabled (s))
     add_unwindsym_vdso (s);
-  // NB: do this before the ctx.unwindsym_modules copy is taken
+  // NB: do this before emit_symbol_data takes its "undone" copy
 }
 
 void
 emit_symbol_data (systemtap_session& s)
 {
+  unsigned modindex = 0;
+  unsigned long trampoline_addr = ~0UL;
+  deque<unwindsym_dump_context> ctxs;
+  dwfl_collection dwfls;
+  dump_failure_flag failure;
   ofstream kallsyms_out (s.symbols_source.c_str ());
 
   if (s.runtime_usermode_p ())
@@ -8293,39 +8433,35 @@ emit_symbol_data (systemtap_session& s)
         "#include \"stap_common.h\"\n";
     }
 
-  vector<pair<string,unsigned> > seclist;
-  map<unsigned, addrmap_t> addrmap;
-  unwindsym_dump_context ctx = { s, kallsyms_out,
-				 0, /* module index */
-				 0, NULL, 0, /* build_id len, bits, vaddr */
-				 ~0UL, /* stp_kretprobe_trampoline_addr */
-				 0, /* stext_offset */
-				 seclist, addrmap,
-				 NULL, /* debug_frame */
-				 0, /* debug_len */
-				 NULL, /* debug_frame_hdr */
-				 0, /* debug_frame_hdr_len */
-				 0, /* debug_frame_off */
-				 NULL, /* eh_frame */
-				 NULL, /* eh_frame_hdr */
-				 0, /* eh_len */
-				 0, /* eh_frame_hdr_len */
-				 0, /* eh_addr */
-				 0, /* eh_frame_hdr_addr */
-				 NULL, /* debug_line */
-				 0, /* debug_line_len */
-				 NULL, /* debug_line_str */
-				 0, /* debug_line_str_len */
-				 s.unwindsym_modules };
-
   // Micro optimization, mainly to speed up tiny regression tests
   // using just begin probe.
   if (s.unwindsym_modules.size () == 0)
     {
-      emit_symbol_data_done(&ctx, s);
+      vector<unsigned> module_indices;
+      set<string> undone;
+      emit_symbol_data_done (module_indices, kallsyms_out, trampoline_addr,
+			     undone, s);
       return;
     }
 
+#ifdef _ELFUTILS_THREAD_SAFE
+  // One task per module; cap the pool so trivial sessions don't spawn a
+  // thread per core.  Constructed after ctxs, dwfls and failure so unwinding
+  // joins the tasks before destroying what they reference.
+  unsigned nthreads = thread::hardware_concurrency ();
+  if (nthreads == 0 || nthreads > s.unwindsym_modules.size ())
+    nthreads = s.unwindsym_modules.size ();  // >= 1 here (size 0 returned above)
+  boost::asio::thread_pool worker_pool (nthreads);
+#endif
+
+  dump_dispatch dispatch = {
+    s, ctxs, modindex, kallsyms_out,
+#ifdef _ELFUTILS_THREAD_SAFE
+    worker_pool,
+#endif
+    failure
+  };
+
   // ---- step 1: process any kernel modules listed
   set<string> offline_search_modules;
   unsigned count;
@@ -8340,6 +8476,8 @@ emit_symbol_data (systemtap_session& s)
         offline_search_modules.insert (foo);
     }
   Dwfl *dwfl = setup_dwfl_kernel (offline_search_modules, &count, s);
+  dwfls.keep (dwfl);
+
   /* NB: It's not an error to find a few fewer modules than requested.
      There might be third-party modules loaded (e.g. uprobes). */
   /* DWFL_ASSERT("all kernel modules found",
@@ -8349,12 +8487,15 @@ emit_symbol_data (systemtap_session& s)
   do
     {
       assert_no_interrupts();
-      if (ctx.undone_unwindsym_modules.empty()) break;
-      off = dwfl_getmodules (dwfl, &dump_unwindsyms, (void *) &ctx, off);
+      off = dwfl_getmodules (dwfl, &dump_unwindsyms, &dispatch, off);
     }
   while (off > 0);
   DWFL_ASSERT("dwfl_getmodules", off == 0);
-  dwfl_end(dwfl);
+
+#ifndef _ELFUTILS_THREAD_SAFE
+  // Serial arm: this Dwfl's modules are done; release it now (as before).
+  dwfls.end_all ();
+#endif
 
   // ---- step 2: process any user modules (files) listed
   for (std::set<std::string>::iterator it = s.unwindsym_modules.begin();
@@ -8364,64 +8505,124 @@ emit_symbol_data (systemtap_session& s)
       string modname = *it;
       assert (modname.length() != 0);
       if (! is_user_module (modname)) continue;
-      Dwfl *dwfl = setup_dwfl_user (modname);
+
+      {
+        // A module has failed; skip opening further Dwfls.
+        stap_mutex_guard g (failure.mutex);
+        if (failure.failed)
+          break;
+      }
+
+      dwfl = setup_dwfl_user (modname);
+
       if (dwfl != NULL) // tolerate missing data; will warn below
         {
-          ptrdiff_t off = 0;
+          dwfls.keep (dwfl);
+          off = 0;
           do
             {
               assert_no_interrupts();
-              if (ctx.undone_unwindsym_modules.empty()) break;
-              off = dwfl_getmodules (dwfl, &dump_unwindsyms, (void *) &ctx, off);
+              off = dwfl_getmodules (dwfl, &dump_unwindsyms, &dispatch, off);
             }
           while (off > 0);
           DWFL_ASSERT("dwfl_getmodules", off == 0);
+
+#ifndef _ELFUTILS_THREAD_SAFE
+          dwfls.end_all ();  // serial arm: this Dwfl's modules are done
+#endif
+        }
+    }
+
+#ifdef _ELFUTILS_THREAD_SAFE
+  // Wait for all tasks before reading results or ending the Dwfls.
+  worker_pool.join ();
+
+  // Re-check now so a ^C during the join aborts before anything is flushed.
+  assert_no_interrupts ();
+#endif
+
+  dwfls.end_all ();
+
+  // Flush buffered verbose logs in module order (the serial arm already
+  // streamed them during the scan).
+  for (deque<unwindsym_dump_context>::iterator it = ctxs.begin ();
+       it != ctxs.end (); ++it)
+    clog << it->log.str ();
+
+  // Rethrow the first worker error, now on the main thread.
+  for (deque<unwindsym_dump_context>::iterator it = ctxs.begin ();
+       it != ctxs.end (); ++it)
+    if (it->pending_exception)
+      std::rethrow_exception (it->pending_exception);
+
+  // Flush module output in module order; collect the emitted indices.
+  vector<unsigned> module_indices;
+  set<string> undone (s.unwindsym_modules);
+  for (deque<unwindsym_dump_context>::iterator it = ctxs.begin ();
+       it != ctxs.end (); ++it)
+    {
+      kallsyms_out << it->output.str ();
+
+      if (it->res == DWARF_CB_OK)
+        {
+          module_indices.push_back (it->stp_module_index);
+          undone.erase (it->modname);
+          if (it->modname == "kernel")
+            trampoline_addr = it->stp_kretprobe_trampoline_addr;
         }
-      dwfl_end(dwfl);
     }
 
-  // Use /proc/kallsyms if debuginfo not found.
-  if (ctx.undone_unwindsym_modules.find("kernel") != ctx.undone_unwindsym_modules.end())
-    dump_kallsyms(&ctx);
+  // Use /proc/kallsyms if the kernel's debuginfo was not found (PR17921).
+  if (undone.find ("kernel") != undone.end ())
+    {
+      unwindsym_dump_context kc (s, modindex++);
+      dump_kallsyms (&kc);
+      kallsyms_out << kc.output.str ();
+      module_indices.push_back (kc.stp_module_index);
+      undone.erase ("kernel");
+    }
 
-  emit_symbol_data_done (&ctx, s);
+  emit_symbol_data_done (module_indices, kallsyms_out, trampoline_addr,
+			 undone, s);
 }
 
 void
-self_unwind_declarations(unwindsym_dump_context *ctx)
-{
-  ctx->output << "static uint8_t _stp_module_self_eh_frame [] = {0,};\n";
-  ctx->output << "struct _stp_symbol _stp_module_self_symbols_0[] = {{0},};\n";
-  ctx->output << "struct _stp_symbol _stp_module_self_symbols_1[] = {{0},};\n";
-  ctx->output << "struct _stp_section _stp_module_self_sections[] = {\n";
-  ctx->output << "{.name = \".symtab\", .symbols = _stp_module_self_symbols_0, .num_symbols = 0},\n";
-  ctx->output << "{.name = \".text\", .symbols = _stp_module_self_symbols_1, .num_symbols = 0},\n";
-  ctx->output << "};\n";
-  ctx->output << "struct _stp_module _stp_module_self = {\n";
-  ctx->output << ".name = \"stap_self_tmp_value\",\n";
-  ctx->output << ".path = \"stap_self_tmp_value\",\n";
-  ctx->output << ".num_sections = 2,\n";
-  ctx->output << ".sections = _stp_module_self_sections,\n";
-  ctx->output << ".eh_frame = _stp_module_self_eh_frame,\n";
-  ctx->output << ".eh_frame_len = 0,\n";
-  ctx->output << ".unwind_hdr_addr = 0x0,\n";
-  ctx->output << ".unwind_hdr = NULL,\n";
-  ctx->output << ".unwind_hdr_len = 0,\n";
-  ctx->output << ".debug_frame = NULL,\n";
-  ctx->output << ".debug_frame_len = 0,\n";
-  ctx->output << ".debug_line = NULL,\n";
-  ctx->output << ".debug_line_len = 0,\n";
-  ctx->output << ".debug_line_str = NULL,\n";
-  ctx->output << ".debug_line_str_len = 0,\n";
-  ctx->output << "};\n";
+self_unwind_declarations(ostream& output)
+{
+  output << "static uint8_t _stp_module_self_eh_frame [] = {0,};\n";
+  output << "struct _stp_symbol _stp_module_self_symbols_0[] = {{0},};\n";
+  output << "struct _stp_symbol _stp_module_self_symbols_1[] = {{0},};\n";
+  output << "struct _stp_section _stp_module_self_sections[] = {\n";
+  output << "{.name = \".symtab\", .symbols = _stp_module_self_symbols_0, .num_symbols = 0},\n";
+  output << "{.name = \".text\", .symbols = _stp_module_self_symbols_1, .num_symbols = 0},\n";
+  output << "};\n";
+  output << "struct _stp_module _stp_module_self = {\n";
+  output << ".name = \"stap_self_tmp_value\",\n";
+  output << ".path = \"stap_self_tmp_value\",\n";
+  output << ".num_sections = 2,\n";
+  output << ".sections = _stp_module_self_sections,\n";
+  output << ".eh_frame = _stp_module_self_eh_frame,\n";
+  output << ".eh_frame_len = 0,\n";
+  output << ".unwind_hdr_addr = 0x0,\n";
+  output << ".unwind_hdr = NULL,\n";
+  output << ".unwind_hdr_len = 0,\n";
+  output << ".debug_frame = NULL,\n";
+  output << ".debug_frame_len = 0,\n";
+  output << ".debug_line = NULL,\n";
+  output << ".debug_line_len = 0,\n";
+  output << ".debug_line_str = NULL,\n";
+  output << ".debug_line_str_len = 0,\n";
+  output << "};\n";
 }
 
 void
-emit_symbol_data_done (unwindsym_dump_context *ctx, systemtap_session& s)
+emit_symbol_data_done (const vector<unsigned>& module_indices,
+		       ostream& output, unsigned long trampoline_addr,
+		       const set<string>& undone, systemtap_session& s)
 {
   // Add a .eh_frame terminator dummy object file, much like
   // libgcc/crtstuff.c's EH_FRAME_SECTION_NAME closer.  We need this in
-  // order for runtime/sym.c 
+  // order for runtime/sym.c
   translator_output *T_800 = s.op_create_auxiliary(true);
   T_800->newline() << "__extension__ unsigned int T_800 []"; // assumed 32-bits wide
   T_800->newline(1) << "__attribute__((used, section(\".eh_frame\"), aligned(4)))";
@@ -8430,30 +8631,29 @@ emit_symbol_data_done (unwindsym_dump_context *ctx, systemtap_session& s)
   T_800->assert_0_indent (); // flush to disk
 
   // Print out a definition of the runtime's _stp_modules[] globals.
-  ctx->output << "\n";
-  self_unwind_declarations(ctx);
-   ctx->output << "struct _stp_module *_stp_modules [] = {\n";
-  for (unsigned i=0; i<ctx->stp_module_index; i++)
+  output << "\n";
+  self_unwind_declarations(output);
+  output << "struct _stp_module *_stp_modules [] = {\n";
+  for (vector<unsigned>::const_iterator it = module_indices.begin ();
+       it != module_indices.end (); ++it)
     {
-      ctx->output << "& _stp_module_" << i << ",\n";
+      output << "& _stp_module_" << *it << ",\n";
     }
-  ctx->output << "& _stp_module_self,\n";
-  ctx->output << "};\n";
-  ctx->output << "const unsigned _stp_num_modules = ARRAY_SIZE(_stp_modules);\n";
+  output << "& _stp_module_self,\n";
+  output << "};\n";
+  output << "const unsigned _stp_num_modules = ARRAY_SIZE(_stp_modules);\n";
 
-  ctx->output << "unsigned long _stp_kretprobe_trampoline = ";
+  output << "unsigned long _stp_kretprobe_trampoline = ";
   // Special case for -1, which is invalid in hex if host width > target width.
-  if (ctx->stp_kretprobe_trampoline_addr == (unsigned long) -1)
-    ctx->output << "-1;\n";
+  if (trampoline_addr == (unsigned long) -1)
+    output << "-1;\n";
   else
-    ctx->output << "0x" << hex << ctx->stp_kretprobe_trampoline_addr << dec
-		<< ";\n";
+    output << "0x" << hex << trampoline_addr << dec << ";\n";
 
   // Some nonexistent modules may have been identified with "-d".  Note them.
   if (! s.suppress_warnings)
-    for (set<string>::iterator it = ctx->undone_unwindsym_modules.begin();
-	 it != ctx->undone_unwindsym_modules.end();
-	 it ++)
+    for (set<string>::const_iterator it = undone.begin ();
+	 it != undone.end (); ++it)
       s.print_warning (_("missing unwind/symbol data for module '")
 		       + (*it) + "'");
 }
-- 
2.54.0



More information about the Systemtap mailing list