[PATCH v2] translate.cxx: parallelize emit_symbol_data

Aaron Merey amerey@redhat.com
Mon Jul 13 13:39:38 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.  If _ELFUTILS_THREAD_SAFE is not
defined, the thread pool is used with only 1 worker.

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.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
---

v2: Thread pool always used with size min(#cpus, #modules) or
1 if _ELFUTILS_THREAD_SAFE is not defined.

helgrind reports no races in systemtap code in either serial or
multithreaded configurations. The only reports involved elfutils-side
debuginfod-client, libcurl and openssl which I'm investigating.

No meaningful performance differences were measured between v1
and v2 in serial and multithreaded configurations.

For review convenience, here's the git diff -w output for this commit

  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..3d36e2d40 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)
   {
  +  std::lock_guard<std::mutex> 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..acd048eb4 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>
   
  @@ -127,7 +129,6 @@ struct parse_error: public std::runtime_error
       }
   };
   
  -
   struct symresolution_info;
   
   struct systemtap_session
  @@ -443,6 +444,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).
   
  +  std::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 +532,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..049b35c1f 100644
  --- a/setupdwfl.cxx
  +++ b/setupdwfl.cxx
  @@ -17,6 +17,7 @@
   #include <algorithm>
   #include <iostream>
   #include <fstream>
  +#include <mutex>
   #include <sstream>
   #include <set>
   #include <string>
  @@ -658,15 +659,23 @@ internal_find_debuginfo (Dwfl_Module *mod,
     if(!current_session_for_find_debuginfo->download_dbinfo || abrt_path.empty())
       goto call_dwfl_standard_find_debuginfo;
   
  +  {
  +    /* The symbol-dump worker threads (translate.cxx:emit_symbol_data) can
  +       reach this path concurrently.  */
  +    static std::mutex download_mutex;
  +
       /* Check that we haven't already run this */
  +    {
  +      std::lock_guard<std::mutex> 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 */
  +    /* Extract the build ID.  */
       const unsigned char *bits;
       GElf_Addr vaddr;
       if(current_session_for_find_debuginfo->verbose > 2)
  @@ -689,8 +698,16 @@ internal_find_debuginfo (Dwfl_Module *mod,
             return fd;
         }
   
  +    {
         /* The above failed, so call abrt-action-install-debuginfo-to-abrt-cache
  -  to download and install the debuginfo */
  +         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.  */
  +      std::lock_guard<std::mutex> g (download_mutex);
  +
  +      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;
  @@ -721,6 +738,8 @@ internal_find_debuginfo (Dwfl_Module *mod,
                     - 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..0820c6122 100644
  --- a/translate.cxx
  +++ b/translate.cxx
  @@ -33,6 +33,23 @@
   #include <cstring>
   #include <cerrno>
   
  +#include <atomic>
  +#include <deque>
  +#include <exception>
  +#include <thread>
  +#include <elfutils/version.h> // for _ELFUTILS_THREAD_SAFE
  +
  +#ifdef HAVE_BOOST_ASIO_THREAD_POOL_HPP
  +#include <boost/asio/thread_pool.hpp>
  +#else
  +#error "boost/asio/thread_pool.hpp is required"
  +#endif
  +#ifdef HAVE_BOOST_ASIO_POST_HPP
  +#include <boost/asio/post.hpp>
  +#else
  +#error "boost/asio/post.hpp is required"
  +#endif
  +
   extern "C" {
   #include <dwarf.h>
   #include <elfutils/libdwfl.h>
  @@ -6791,12 +6808,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 +6852,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,7 +7145,7 @@ dump_build_id (Dwfl_Module *m,
   
       if (c->session.verbose > 1)
         {
  -        clog << _F("Found build-id in %s, length %d, start at %#" PRIx64,
  +        c->log << _F("Found build-id in %s, length %d, start at %#" PRIx64,
                        name, build_id_len, build_id_vaddr) << endl;
         }
   
  @@ -7478,7 +7530,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 +8053,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,98 +8120,147 @@ 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";
  -
  -  c->undone_unwindsym_modules.erase("kernel");
  -  c->stp_module_index++;
   }
   
  -static int
  -dump_unwindsyms (Dwfl_Module *m,
  -                 void **userdata __attribute__ ((unused)),
  -                 const char *name,
  -                 Dwarf_Addr base,
  -                 void *arg)
  +// One pending module dump, collected while the main thread scans the Dwfls.
  +struct module_task
   {
  -  if (pending_interrupts)
  -    return DWARF_CB_ABORT;
  -
  -  unwindsym_dump_context *c = (unwindsym_dump_context*) arg;
  -  assert (c);
  -
  -  // skip modules/files we're not actually interested in
  -  string modname = name;
  -  if (c->session.unwindsym_modules.find(modname)
  -      == c->session.unwindsym_modules.end())
  -    return DWARF_CB_OK;
  +  unwindsym_dump_context *c;
  +  Dwfl_Module *m;
  +  const char *name;   // Dwfl-owned; valid until dwfl_end
  +  Dwarf_Addr base;
  +};
   
  -  if (c->session.verbose > 1)
  -    clog << "dump_unwindsyms " << name
  -         << " index=" << c->stp_module_index
  -         << " base=0x" << hex << base << dec << endl;
  +// Dispatcher state.  dwfl_getmodules() runs the callback only on the main
  +// thread, so no locking here.
  +struct dump_dispatch
  +{
  +  systemtap_session& session;
  +  deque<unwindsym_dump_context>& ctxs;
  +  unsigned& modindex;
  +  vector<module_task>& tasks;
  +};
   
  +// 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, std::atomic<bool> *failed,
  +                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, or another module failed?  Skip; emit_symbol_data
  +  // re-checks interrupts and rethrows errors after the pool is joined.
  +  if (pending_interrupts || *failed)
  +    {
  +      c->res = DWARF_CB_ABORT;
  +      return;
  +    }
   
  -  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);
  +  try
  +    {
  +      int 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
  +        // 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 in the output. */
  +      // And finally dump everything collected into c->output.
         if (res == DWARF_CB_OK)
           res = dump_unwindsym_cxt (m, c, name, base);
   
  -  if (res == DWARF_CB_OK)
  -    c->stp_module_index++;
  +      c->res = res;
  +    }
  +  catch (...)
  +    {
  +      c->pending_exception = std::current_exception ();
  +      c->res = DWARF_CB_ABORT;
  +      *failed = true;
  +    }
   
  -  return res;
  +  // 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.
  +// They must stay open until the worker 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)),
  +                 const char *name,
  +                 Dwarf_Addr base,
  +                 void *arg)
  +{
  +  if (pending_interrupts)
  +    return DWARF_CB_ABORT;
  +
  +  dump_dispatch *d = (dump_dispatch *) arg;
  +  assert (d);
  +
  +  // skip modules/files we're not actually interested in
  +  string modname = name;
  +  if (d->session.unwindsym_modules.find (modname)
  +      == d->session.unwindsym_modules.end ())
  +    return DWARF_CB_OK;
  +
  +  // 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.
  +  d->tasks.push_back (module_task { c, m, name, base });
  +
  +  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 +8371,18 @@ 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;
  +  vector<module_task> tasks;
  +  std::atomic<bool> dump_failed (false);
     ofstream kallsyms_out (s.symbols_source.c_str ());
   
     if (s.runtime_usermode_p ())
  @@ -8293,39 +8398,19 @@ 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;
       }
   
  +  dump_dispatch dispatch = { s, ctxs, modindex, tasks };
  +
     // ---- step 1: process any kernel modules listed
     set<string> offline_search_modules;
     unsigned count;
  @@ -8340,6 +8425,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 +8436,10 @@ 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);
   
     // ---- step 2: process any user modules (files) listed
     for (std::set<std::string>::iterator it = s.unwindsym_modules.begin();
  @@ -8364,60 +8449,132 @@ 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);
  +
  +      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);
           }
  -      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);
  +  // The scans above are done, so the main thread makes no elfutils calls
  +  // while the pool runs.  With a thread-safe elfutils use one worker per
  +  // cpu (capped at one per module); otherwise a single worker, so at most
  +  // one thread is inside elfutils at a time.
  +#ifdef _ELFUTILS_THREAD_SAFE
  +  unsigned nthreads = thread::hardware_concurrency ();
  +  if (nthreads == 0 || nthreads > tasks.size ())
  +    nthreads = tasks.size ();
  +#else
  +  unsigned nthreads = 1;
  +#endif
  +
  +  if (! tasks.empty ())
  +    {
  +      boost::asio::thread_pool worker_pool (nthreads);
  +      for (vector<module_task>::iterator it = tasks.begin ();
  +           it != tasks.end (); ++it)
  +        {
  +          module_task t = *it;
  +          std::atomic<bool> *failed = &dump_failed;
  +          boost::asio::post (worker_pool, [t, failed] {
  +            process_module (t.c, failed, t.m, t.name, t.base);
  +          });
  +        }
   
  -  emit_symbol_data_done (&ctx, s);
  +      // 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 ();
  +
  +  dwfls.end_all ();
  +
  +  // Flush buffered verbose logs in module order.
  +  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;
  +        }
  +    }
  +
  +  // 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 (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
  @@ -8430,30 +8587,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) + "'");
   }

 main.cxx                                      |   2 +-
 session.cxx                                   |   2 +
 session.h                                     |   6 +-
 setupdwfl.cxx                                 | 129 +++--
 .../systemtap.base/translate_determinism.exp  |  46 ++
 translate.cxx                                 | 470 ++++++++++++------
 6 files changed, 440 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..3d36e2d40 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)
 {
+  std::lock_guard<std::mutex> 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..acd048eb4 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>
 
@@ -127,7 +129,6 @@ struct parse_error: public std::runtime_error
     }
 };
 
-
 struct symresolution_info;
 
 struct systemtap_session
@@ -443,6 +444,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).
 
+  std::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 +532,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..049b35c1f 100644
--- a/setupdwfl.cxx
+++ b/setupdwfl.cxx
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <iostream>
 #include <fstream>
+#include <mutex>
 #include <sstream>
 #include <set>
 #include <string>
@@ -658,69 +659,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 std::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;
+      std::lock_guard<std::mutex> 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.  */
+      std::lock_guard<std::mutex> 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..0820c6122 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -33,6 +33,23 @@
 #include <cstring>
 #include <cerrno>
 
+#include <atomic>
+#include <deque>
+#include <exception>
+#include <thread>
+#include <elfutils/version.h> // for _ELFUTILS_THREAD_SAFE
+
+#ifdef HAVE_BOOST_ASIO_THREAD_POOL_HPP
+#include <boost/asio/thread_pool.hpp>
+#else
+#error "boost/asio/thread_pool.hpp is required"
+#endif
+#ifdef HAVE_BOOST_ASIO_POST_HPP
+#include <boost/asio/post.hpp>
+#else
+#error "boost/asio/post.hpp is required"
+#endif
+
 extern "C" {
 #include <dwarf.h>
 #include <elfutils/libdwfl.h>
@@ -6791,12 +6808,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 +6852,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 +7145,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 +7530,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 +8053,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 +8120,104 @@ 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";
+}
+
+// One pending module dump, collected while the main thread scans the Dwfls.
+struct module_task
+{
+  unwindsym_dump_context *c;
+  Dwfl_Module *m;
+  const char *name;   // Dwfl-owned; valid until dwfl_end
+  Dwarf_Addr base;
+};
+
+// Dispatcher state.  dwfl_getmodules() runs the callback only on the main
+// thread, so no locking here.
+struct dump_dispatch
+{
+  systemtap_session& session;
+  deque<unwindsym_dump_context>& ctxs;
+  unsigned& modindex;
+  vector<module_task>& tasks;
+};
+
+// 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, std::atomic<bool> *failed,
+                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, or another module failed?  Skip; emit_symbol_data
+  // re-checks interrupts and rethrows errors after the pool is joined.
+  if (pending_interrupts || *failed)
+    {
+      c->res = DWARF_CB_ABORT;
+      return;
+    }
+
+  try
+    {
+      int res = dump_build_id (m, c, name, base);
 
-  c->undone_unwindsym_modules.erase("kernel");
-  c->stp_module_index++;
+      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);
+
+      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;
+      *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.
+// They must stay open until the worker 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 +8228,39 @@ 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);
 
   // 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;
+  // 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;
 
-  // 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
+  if (d->session.verbose > 1)
+    c->log << "dump_unwindsyms " << name
+           << " index=" << c->stp_module_index
+           << " base=0x" << hex << base << dec << endl;
 
-  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;
+  // The task references Dwfl-owned storage (m, name), so dwfl_end() must be
+  // deferred until the pool has been joined.
+  d->tasks.push_back (module_task { c, m, name, base });
+
+  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 +8371,18 @@ 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;
+  vector<module_task> tasks;
+  std::atomic<bool> dump_failed (false);
   ofstream kallsyms_out (s.symbols_source.c_str ());
 
   if (s.runtime_usermode_p ())
@@ -8293,39 +8398,19 @@ 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;
     }
 
+  dump_dispatch dispatch = { s, ctxs, modindex, tasks };
+
   // ---- step 1: process any kernel modules listed
   set<string> offline_search_modules;
   unsigned count;
@@ -8340,6 +8425,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 +8436,10 @@ 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);
 
   // ---- step 2: process any user modules (files) listed
   for (std::set<std::string>::iterator it = s.unwindsym_modules.begin();
@@ -8364,64 +8449,136 @@ 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);
+
+      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);
         }
-      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);
+  // The scans above are done, so the main thread makes no elfutils calls
+  // while the pool runs.  With a thread-safe elfutils use one worker per
+  // cpu (capped at one per module); otherwise a single worker, so at most
+  // one thread is inside elfutils at a time.
+#ifdef _ELFUTILS_THREAD_SAFE
+  unsigned nthreads = thread::hardware_concurrency ();
+  if (nthreads == 0 || nthreads > tasks.size ())
+    nthreads = tasks.size ();
+#else
+  unsigned nthreads = 1;
+#endif
+
+  if (! tasks.empty ())
+    {
+      boost::asio::thread_pool worker_pool (nthreads);
+      for (vector<module_task>::iterator it = tasks.begin ();
+           it != tasks.end (); ++it)
+        {
+          module_task t = *it;
+          std::atomic<bool> *failed = &dump_failed;
+          boost::asio::post (worker_pool, [t, failed] {
+            process_module (t.c, failed, t.m, t.name, t.base);
+          });
+        }
+
+      // 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 ();
+
+  dwfls.end_all ();
+
+  // Flush buffered verbose logs in module order.
+  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 ();
 
-  emit_symbol_data_done (&ctx, s);
+      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;
+        }
+    }
+
+  // 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 (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 +8587,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