From: Lucian Grijincu Date: Wed, 12 Oct 2016 00:03:55 +0000 (-0700) Subject: folly: signal-handler: dynamic cache size, based on number of dynamic-loaded ELF... X-Git-Tag: v2016.10.17.00~15 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=74ea0a31f65bcb4a6debc997a57f9cc1b3882d6b 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 --- diff --git a/folly/experimental/symbolizer/ElfCache.cpp b/folly/experimental/symbolizer/ElfCache.cpp index 472c0b93..4b7388f0 100644 --- a/folly/experimental/symbolizer/ElfCache.cpp +++ b/folly/experimental/symbolizer/ElfCache.cpp @@ -16,8 +16,39 @@ #include +#include + +/* + * 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); diff --git a/folly/experimental/symbolizer/ElfCache.h b/folly/experimental/symbolizer/ElfCache.h index 8b8be334..378baa20 100644 --- a/folly/experimental/symbolizer/ElfCache.h +++ b/folly/experimental/symbolizer/ElfCache.h @@ -35,6 +35,11 @@ namespace folly { namespace symbolizer { +/** + * Number of ELF files loaded by the dynamic loader. + */ +size_t countLoadedElfFiles(); + class ElfCacheBase { public: virtual std::shared_ptr getFile(StringPiece path) = 0; diff --git a/folly/experimental/symbolizer/SignalHandler.cpp b/folly/experimental/symbolizer/SignalHandler.cpp index 5319b11a..b5dd4c14 100644 --- a/folly/experimental/symbolizer/SignalHandler.cpp +++ b/folly/experimental/symbolizer/SignalHandler.cpp @@ -18,13 +18,15 @@ #include +#include +#include #include +#include + +#include #include #include #include -#include -#include -#include #include #include @@ -33,6 +35,7 @@ #include #include #include +#include #include #include @@ -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. diff --git a/folly/experimental/symbolizer/SignalHandler.h b/folly/experimental/symbolizer/SignalHandler.h index 7bb69643..726c8a81 100644 --- a/folly/experimental/symbolizer/SignalHandler.h +++ b/folly/experimental/symbolizer/SignalHandler.h @@ -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).