diff --git a/README.md b/README.md index 5f245e6..8b616ff 100644 --- a/README.md +++ b/README.md @@ -224,10 +224,27 @@ The library makes an attempt to fail silently and continue during trace generati `cpptrace::absorb_trace_exceptions` can be used to configure whether these exceptions are absorbed silently internally or wether they're rethrown to the caller. +`cpptrace::experimental::set_cache_mode` can be used to control time-memory tradeoffs within the library. By default +speed is prioritized. If using this function, set the cache mode at the very start of your program before any traces are +performed. + ```cpp namespace cpptrace { std::string demangle(const std::string& name); void absorb_trace_exceptions(bool absorb); + + enum class cache_mode { + // Only minimal lookup tables + prioritize_memory, + // Build lookup tables but don't keep them around between trace calls + hybrid, + // Build lookup tables as needed + prioritize_speed + }; + + namespace experimental { + void set_cache_mode(cache_mode mode); + } } ``` diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 9957d4a..5016e76 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -130,8 +130,22 @@ namespace cpptrace { CPPTRACE_API std::string demangle(const std::string& name); CPPTRACE_API void absorb_trace_exceptions(bool absorb); + enum class cache_mode { + // Only minimal lookup tables + prioritize_memory, + // Build lookup tables but don't keep them around between trace calls + hybrid, + // Build lookup tables as needed + prioritize_speed + }; + + namespace experimental { + CPPTRACE_API void set_cache_mode(cache_mode mode); + } + namespace detail { CPPTRACE_API bool should_absorb_trace_exceptions(); + CPPTRACE_API enum cache_mode get_cache_mode(); } class exception : public std::exception { diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 80ca69b..635da81 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -26,10 +26,6 @@ #define CYAN ESC "36m" namespace cpptrace { - namespace detail { - std::atomic_bool absorb_trace_exceptions(true); - } - CPPTRACE_FORCE_NO_INLINE CPPTRACE_API raw_trace raw_trace::current(std::uint_least32_t skip) { return generate_raw_trace(skip + 1); @@ -316,13 +312,28 @@ namespace cpptrace { return detail::demangle(name); } + namespace detail { + std::atomic_bool absorb_trace_exceptions(true); + std::atomic cache_mode(cache_mode::prioritize_speed); + } + CPPTRACE_API void absorb_trace_exceptions(bool absorb) { detail::absorb_trace_exceptions = absorb; } + namespace experimental { + CPPTRACE_API void set_cache_mode(cache_mode mode) { + detail::cache_mode = mode; + } + } + namespace detail { CPPTRACE_API bool should_absorb_trace_exceptions() { - return detail::absorb_trace_exceptions; + return absorb_trace_exceptions; + } + + CPPTRACE_API enum cache_mode get_cache_mode() { + return cache_mode; } } } diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index e089518..7e16f32 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -398,7 +398,13 @@ namespace dbghelp { // TODO: When does this need to be called? Can it be moved to the symbolizer? SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS); HANDLE proc = GetCurrentProcess(); - get_syminit_manager().init(proc); + if(get_cache_mode() == cache_mode::prioritize_speed) { + get_syminit_manager().init(proc); + } else { + if(!SymInitialize(proc, NULL, TRUE)) { + throw std::logic_error("Cpptrace SymInitialize failed"); + } + } for(const auto frame : frames) { try { trace.push_back(resolve_frame(proc, frame)); @@ -409,6 +415,11 @@ namespace dbghelp { trace.push_back(null_frame); } } + if(get_cache_mode() != cache_mode::prioritize_speed) { + if(!SymCleanup(proc)) { + throw std::logic_error("Cpptrace SymCleanup failed"); + } + } return trace; } } diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index ce5d0ea..b53142c 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -575,7 +575,7 @@ namespace libdwarf { // Check for .debug_aranges for fast lookup wrap(dwarf_get_aranges, dbg, &aranges, &arange_count); } - if(ok && !aranges) { + if(ok && !aranges && get_cache_mode() != cache_mode::prioritize_memory) { walk_compilation_units([this] (const die_object& cu_die) { Dwarf_Half offset_size = 0; Dwarf_Half dwversion = 0; @@ -839,39 +839,44 @@ namespace libdwarf { Dwarf_Half dwversion, stacktrace_frame& frame ) { - auto off = cu_die.get_global_offset(); - auto it = subprograms_cache.find(off); - if(it == subprograms_cache.end()) { - std::vector vec; - preprocess_subprograms(cu_die, dwversion, vec); - std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) { - return a.low < b.low; - }); - subprograms_cache.emplace(off, std::move(vec)); - it = subprograms_cache.find(off); - } - auto& vec = it->second; - auto vec_it = std::lower_bound( - vec.begin(), - vec.end(), - pc, - [] (const subprogram_entry& entry, Dwarf_Addr pc) { - return entry.low < pc; - } - ); - // vec_it is first >= pc - // we want first <= pc - if(vec_it != vec.begin()) { - vec_it--; - } - // If the vector has been empty this can happen - if(vec_it != vec.end()) { - //vec_it->die.print(); - if(vec_it->die.pc_in_die(dwversion, pc)) { - retrieve_symbol_for_subprogram(vec_it->die, dwversion, frame); - } + if(get_cache_mode() == cache_mode::prioritize_memory) { + retrieve_symbol_walk(cu_die, pc, dwversion, frame); } else { - ASSERT(vec.size() == 0, "Vec should be empty?"); + auto off = cu_die.get_global_offset(); + auto it = subprograms_cache.find(off); + if(it == subprograms_cache.end()) { + // TODO: Refactor. Do the sort in the preprocess function and return the vec directly. + std::vector vec; + preprocess_subprograms(cu_die, dwversion, vec); + std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) { + return a.low < b.low; + }); + subprograms_cache.emplace(off, std::move(vec)); + it = subprograms_cache.find(off); + } + auto& vec = it->second; + auto vec_it = std::lower_bound( + vec.begin(), + vec.end(), + pc, + [] (const subprogram_entry& entry, Dwarf_Addr pc) { + return entry.low < pc; + } + ); + // vec_it is first >= pc + // we want first <= pc + if(vec_it != vec.begin()) { + vec_it--; + } + // If the vector has been empty this can happen + if(vec_it != vec.end()) { + //vec_it->die.print(); + if(vec_it->die.pc_in_die(dwversion, pc)) { + retrieve_symbol_for_subprogram(vec_it->die, dwversion, frame); + } + } else { + ASSERT(vec.size() == 0, "Vec should be empty?"); + } } } @@ -998,7 +1003,7 @@ namespace libdwarf { retrieve_symbol(cu_die, pc, dwversion, frame); } } else { - if(false) { // TODO: Proper condition + if(get_cache_mode() == cache_mode::prioritize_memory) { // walk for the cu and go from there walk_compilation_units([this, pc, &frame] (const die_object& cu_die) { Dwarf_Half offset_size = 0; diff --git a/src/unwind/unwind_with_dbghelp.cpp b/src/unwind/unwind_with_dbghelp.cpp index 17d957c..c6f035a 100644 --- a/src/unwind/unwind_with_dbghelp.cpp +++ b/src/unwind/unwind_with_dbghelp.cpp @@ -89,7 +89,13 @@ namespace detail { // HANDLE proc = GetCurrentProcess(); HANDLE thread = GetCurrentThread(); - get_syminit_manager().init(proc); + if(get_cache_mode() == cache_mode::prioritize_speed) { + get_syminit_manager().init(proc); + } else { + if(!SymInitialize(proc, NULL, TRUE)) { + throw std::logic_error("Cpptrace SymInitialize failed"); + } + } while(trace.size() < max_depth) { if( !StackWalk64( @@ -122,6 +128,11 @@ namespace detail { break; } } + if(get_cache_mode() != cache_mode::prioritize_speed) { + if(!SymCleanup(proc)) { + throw std::logic_error("Cpptrace SymCleanup failed"); + } + } return trace; } }