folly: signal-handler: dynamic cache size, based on number of dynamic-loaded ELF...
authorLucian Grijincu <lucian@fb.com>
Wed, 12 Oct 2016 00:03:55 +0000 (17:03 -0700)
committerFacebook Github Bot <facebook-github-bot-bot@fb.com>
Wed, 12 Oct 2016 00:08:59 +0000 (17:08 -0700)
Summary:
I added kFatalSignalHandlerCacheSize in {D3984649}, and chose a big
number to satisfy most use-cases. But the size increase is non-trivial.

Memory usage:

- before: 5.1 MB in folly::symbolizer::SignalSafeElfCache::SignalSafeElfCache
- after: 80.0 MB in folly::symbolizer::SignalSafeElfCache::SignalSafeElfCache

Switch to a dynamic approach and everyone pays for what they use.

https://github.com/bminor/glibc/blob/ee19f1de0d0da24114be554fdf94243c0ec6b86c/elf/rtld-debugger-interface.txt#L10-L20
```
The r_debug structure contains (amongst others) the following fields:

  struct link_map *r_map:
    A linked list of loaded objects.

  enum { RT_CONSISTENT, RT_ADD, RT_DELETE } r_state:
    The current state of the r_map list.  RT_CONSISTENT means that r_map
    is not currently being modified and may safely be inspected.  RT_ADD
    means that an object is being added to r_map, and that the list is
    not guaranteed to be consistent.  Likewise RT_DELETE means that an
    object is being removed from the list.
```

https://github.com/bminor/glibc/blob/ee19f1de0d0da24114be554fdf94243c0ec6b86c/elf/rtld.c#L303-L307
```
  /* Call the OS-dependent function to set up life so we can do things like
     file access.  It will call `dl_main' (below) to do all the real work
     of the dynamic linker, and then unwind our frame and run the user
     entry point on the same stack we entered on.  */
  start_addr = _dl_sysdep_start (arg, &dl_main);
```

dl_main: https://github.com/bminor/glibc/blob/ee19f1de0d0da24114be554fdf94243c0ec6b86c/elf/rtld.c#L1192-L1199
```
  /* Initialize the data structures for the search paths for shared
     objects.  */
  _dl_init_paths (library_path);

  /* Initialize _r_debug.  */
  struct r_debug *r = _dl_debug_initialize (GL(dl_rtld_map).l_addr,
    LM_ID_BASE);
  r->r_state = RT_CONSISTENT;
...

  /* We start adding objects.  */
  r->r_state = RT_ADD;
  _dl_debug_state ();
  LIBC_PROBE (init_start, 2, LM_ID_BASE, r);

...

  /* Notify the debugger all new objects are now ready to go.  We must re-get
     the address since by now the variable might be in another object.  */
  r = _dl_debug_initialize (0, LM_ID_BASE);
  r->r_state = RT_CONSISTENT;
  _dl_debug_state ();
  LIBC_PROBE (init_complete, 2, LM_ID_BASE, r);

```

Reviewed By: bixue2010

Differential Revision: D3996974

fbshipit-source-id: e24d72e3cc0339e4cf1acdd2f4c9a7ebfcfdf739

folly/experimental/symbolizer/ElfCache.cpp
folly/experimental/symbolizer/ElfCache.h
folly/experimental/symbolizer/SignalHandler.cpp
folly/experimental/symbolizer/SignalHandler.h

index 472c0b9..4b7388f 100644 (file)
 
 #include <folly/experimental/symbolizer/ElfCache.h>
 
+#include <link.h>
+
+/*
+ * This is declared in `link.h' on Linux platforms, but apparently not on the
+ * Mac version of the file.  It's harmless to declare again, in any case.
+ *
+ * Note that declaring it with `extern "C"` results in linkage conflicts.
+ */
+extern struct r_debug _r_debug;
+
 namespace folly { namespace symbolizer {
 
+size_t countLoadedElfFiles() {
+  // _r_debug synchronization is... lacking to say the least. It's
+  // meant as an aid for debuggers and synchrnization is done by
+  // calling dl_debug_state() which debuggers are supposed to
+  // intercept by setting a breakpoint on.
+
+  // Can't really do that here, so we apply the hope-and-pray strategy.
+  if (_r_debug.r_version != 1 || _r_debug.r_state != r_debug::RT_CONSISTENT) {
+    // computo ergo sum
+    return 1;
+  }
+
+  //     r_map       -> head of a linked list of 'link_map_t' entries,
+  //                    one per ELF 'binary' in the process address space.
+  size_t count = 0;
+  for (auto lmap = _r_debug.r_map; lmap != nullptr; lmap = lmap->l_next) {
+    ++count;
+  }
+  return count;
+}
+
 SignalSafeElfCache::SignalSafeElfCache(size_t capacity) {
   map_.reserve(capacity);
   slots_.reserve(capacity);
index 8b8be33..378baa2 100644 (file)
 
 namespace folly { namespace symbolizer {
 
+/**
+ * Number of ELF files loaded by the dynamic loader.
+ */
+size_t countLoadedElfFiles();
+
 class ElfCacheBase {
  public:
   virtual std::shared_ptr<ElfFile> getFile(StringPiece path) = 0;
index 5319b11..b5dd4c1 100644 (file)
 
 #include <folly/experimental/symbolizer/SignalHandler.h>
 
+#include <pthread.h>
+#include <signal.h>
 #include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
 #include <atomic>
 #include <ctime>
 #include <mutex>
-#include <pthread.h>
-#include <signal.h>
-#include <unistd.h>
 #include <vector>
 
 #include <glog/logging.h>
@@ -33,6 +35,7 @@
 #include <folly/FileUtil.h>
 #include <folly/Portability.h>
 #include <folly/ScopeGuard.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
 #include <folly/experimental/symbolizer/Symbolizer.h>
 #include <folly/portability/SysSyscall.h>
 
@@ -128,7 +131,9 @@ void callPreviousSignalHandler(int signum) {
 // in our signal handler at a time.
 //
 // Leak it so we don't have to worry about destruction order
-auto gSignalSafeElfCache = new SignalSafeElfCache(kFatalSignalHandlerCacheSize);
+constexpr size_t kMinSignalSafeElfCacheSize = 500;
+auto gSignalSafeElfCache = new SignalSafeElfCache(
+    std::max(countLoadedElfFiles(), kMinSignalSafeElfCacheSize));
 
 // Buffered writer (using a fixed-size buffer). We try to write only once
 // to prevent interleaving with messages written from other threads.
index 7bb6964..726c8a8 100644 (file)
@@ -29,14 +29,6 @@ namespace folly { namespace symbolizer {
  */
 void installFatalSignalHandler();
 
-/**
- * NOTE: The signal handler cache has a fixed size. ELF files for the
- * binary and DSOs are added to the cache but never removed.
- *
- * Addresses from ELF files not in the cache will (silently) NOT be symbolized.
- */
-constexpr size_t kFatalSignalHandlerCacheSize = 10000;
-
 /**
  * Add a callback to be run when receiving a fatal signal. They will also
  * be called by LOG(FATAL) and abort() (as those raise SIGABRT internally).