Commit 74ea0a31 authored by Lucian Grijincu's avatar Lucian Grijincu Committed by Facebook Github Bot

folly: signal-handler: dynamic cache size, based on number of dynamic-loaded ELF files

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
parent 70b158bb
...@@ -16,8 +16,39 @@ ...@@ -16,8 +16,39 @@
#include <folly/experimental/symbolizer/ElfCache.h> #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 { 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) { SignalSafeElfCache::SignalSafeElfCache(size_t capacity) {
map_.reserve(capacity); map_.reserve(capacity);
slots_.reserve(capacity); slots_.reserve(capacity);
......
...@@ -35,6 +35,11 @@ ...@@ -35,6 +35,11 @@
namespace folly { namespace symbolizer { namespace folly { namespace symbolizer {
/**
* Number of ELF files loaded by the dynamic loader.
*/
size_t countLoadedElfFiles();
class ElfCacheBase { class ElfCacheBase {
public: public:
virtual std::shared_ptr<ElfFile> getFile(StringPiece path) = 0; virtual std::shared_ptr<ElfFile> getFile(StringPiece path) = 0;
......
...@@ -18,13 +18,15 @@ ...@@ -18,13 +18,15 @@
#include <folly/experimental/symbolizer/SignalHandler.h> #include <folly/experimental/symbolizer/SignalHandler.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <atomic> #include <atomic>
#include <ctime> #include <ctime>
#include <mutex> #include <mutex>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <vector> #include <vector>
#include <glog/logging.h> #include <glog/logging.h>
...@@ -33,6 +35,7 @@ ...@@ -33,6 +35,7 @@
#include <folly/FileUtil.h> #include <folly/FileUtil.h>
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/ScopeGuard.h> #include <folly/ScopeGuard.h>
#include <folly/experimental/symbolizer/ElfCache.h>
#include <folly/experimental/symbolizer/Symbolizer.h> #include <folly/experimental/symbolizer/Symbolizer.h>
#include <folly/portability/SysSyscall.h> #include <folly/portability/SysSyscall.h>
...@@ -128,7 +131,9 @@ void callPreviousSignalHandler(int signum) { ...@@ -128,7 +131,9 @@ void callPreviousSignalHandler(int signum) {
// in our signal handler at a time. // in our signal handler at a time.
// //
// Leak it so we don't have to worry about destruction order // 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 // Buffered writer (using a fixed-size buffer). We try to write only once
// to prevent interleaving with messages written from other threads. // to prevent interleaving with messages written from other threads.
......
...@@ -29,14 +29,6 @@ namespace folly { namespace symbolizer { ...@@ -29,14 +29,6 @@ namespace folly { namespace symbolizer {
*/ */
void installFatalSignalHandler(); 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 * 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). * be called by LOG(FATAL) and abort() (as those raise SIGABRT internally).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment