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
#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);
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;
#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>
#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>
// 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.
*/
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).