66
77#include < cstdint>
88#include < cstdio>
9+ #include < functional>
10+ #include < mutex>
911#include < string>
1012#include < unordered_map>
1113#include < utility>
12- #include < functional>
1314#include < vector>
1415
1516#if IS_LINUX || IS_APPLE
@@ -57,26 +58,34 @@ namespace cpptrace {
5758 }
5859
5960 bool has_addr2line () {
60- // Detects if addr2line exists by trying to invoke addr2line --help
61- constexpr int magic = 42 ;
62- // NOLINTNEXTLINE(misc-include-cleaner)
63- const pid_t pid = fork ();
64- if (pid == -1 ) { return false ; }
65- if (pid == 0 ) { // child
66- close (STDOUT_FILENO);
67- close (STDERR_FILENO); // atos --help writes to stderr
68- // TODO: path
69- #if !IS_APPLE
70- execlp (" addr2line" , " addr2line" , " --help" , nullptr );
71- #else
72- execlp (" atos" , " atos" , " --help" , nullptr );
73- #endif
74- _exit (magic);
61+ static std::mutex mutex;
62+ static bool has_addr2line = false ;
63+ static bool checked = false ;
64+ std::lock_guard<std::mutex> lock (mutex);
65+ if (!checked) {
66+ checked = true ;
67+ // Detects if addr2line exists by trying to invoke addr2line --help
68+ constexpr int magic = 42 ;
69+ // NOLINTNEXTLINE(misc-include-cleaner)
70+ const pid_t pid = fork ();
71+ if (pid == -1 ) { return false ; }
72+ if (pid == 0 ) { // child
73+ close (STDOUT_FILENO);
74+ close (STDERR_FILENO); // atos --help writes to stderr
75+ // TODO: path
76+ #if !IS_APPLE
77+ execlp (" addr2line" , " addr2line" , " --help" , nullptr );
78+ #else
79+ execlp (" atos" , " atos" , " --help" , nullptr );
80+ #endif
81+ _exit (magic);
82+ }
83+ int status;
84+ waitpid (pid, &status, 0 );
85+ // NOLINTNEXTLINE(misc-include-cleaner)
86+ has_addr2line = WEXITSTATUS (status) == 0 ;
7587 }
76- int status;
77- waitpid (pid, &status, 0 );
78- // NOLINTNEXTLINE(misc-include-cleaner)
79- return WEXITSTATUS (status) == 0 ;
88+ return has_addr2line;
8089 }
8190
8291 struct pipe_t {
@@ -140,10 +149,42 @@ namespace cpptrace {
140149 // We have to parse the Mach-O to find the offset of the text section.....
141150 // I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for
142151 // now that there is only one, and I'm using only the first section entry within that load command.
143- return get_text_vmaddr (entry.obj_path .c_str ());
152+ static std::mutex mutex;
153+ std::lock_guard<std::mutex> lock (mutex);
154+ static std::unordered_map<std::string, uintptr_t > cache;
155+ auto it = cache.find (entry.obj_path );
156+ if (it == cache.end ()) {
157+ // arguably it'd be better to release the lock while computing this, but also arguably it's good to not
158+ // have two threads try to do the same computation
159+ auto base = get_text_vmaddr (entry.obj_path .c_str ());
160+ cache.insert (it, {entry.obj_path , base});
161+ return base;
162+ } else {
163+ return it->second ;
164+ }
144165 }
145166 #endif
146167 #elif IS_WINDOWS
168+ std::string get_module_name (HMODULE handle) {
169+ static std::mutex mutex;
170+ std::lock_guard<std::mutex> lock (mutex);
171+ static std::unordered_map<HMODULE, std::string> cache;
172+ auto it = cache.find (handle);
173+ if (it == cache.end ()) {
174+ char path[MAX_PATH];
175+ if (GetModuleFileNameA (handle, path, sizeof (path))) {
176+ // /fprintf(stderr, "path: %s base: %p\n", path, handle);
177+ cache.insert (it, {handle, path});
178+ return path;
179+ } else {
180+ fprintf (stderr, " %s\n " , std::system_error (GetLastError (), std::system_category ()).what ());
181+ cache.insert (it, {handle, " " });
182+ return " " ;
183+ }
184+ } else {
185+ return it->second ;
186+ }
187+ }
147188 // aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
148189 std::vector<dlframe> backtrace_frames (const std::vector<void *>& addrs) {
149190 // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
@@ -159,16 +200,8 @@ namespace cpptrace {
159200 static_cast <const char *>(addr),
160201 &handle
161202 )) {
162- char path[MAX_PATH];
163- // TODO: Memoize
164- if (GetModuleFileNameA (handle, path, sizeof (path))) {
165- // /fprintf(stderr, "path: %s base: %p\n", path, handle);
166- frame.obj_path = path;
167- frame.obj_base = reinterpret_cast <uintptr_t >(handle);
168- frame.symbol = " " ;
169- } else {
170- fprintf (stderr, " %s\n " , std::system_error (GetLastError (), std::system_category ()).what ());
171- }
203+ frame.obj_base = reinterpret_cast <uintptr_t >(handle);
204+ frame.obj_path = get_module_name (handle);
172205 } else {
173206 fprintf (stderr, " %s\n " , std::system_error (GetLastError (), std::system_category ()).what ());
174207 }
@@ -178,10 +211,19 @@ namespace cpptrace {
178211 }
179212
180213 bool has_addr2line () {
181- // TODO: Memoize
182- // TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
183- FILE* p = popen (" addr2line --version" , " r" );
184- return pclose (p) == 0 ;
214+ static std::mutex mutex;
215+ static bool has_addr2line = false ;
216+ static bool checked = false ;
217+ std::lock_guard<std::mutex> lock (mutex);
218+ if (!checked) {
219+ // TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
220+ checked = true ;
221+ FILE* p = popen (" addr2line --version" , " r" );
222+ if (p) {
223+ has_addr2line = pclose (p) == 0 ;
224+ }
225+ }
226+ return has_addr2line;
185227 }
186228
187229 std::string resolve_addresses (const std::string& addresses, const std::string& executable) {
@@ -200,12 +242,10 @@ namespace cpptrace {
200242 return output;
201243 }
202244
203- // TODO: Refactor into backtrace_frames...
204- // TODO: Memoize
205- uintptr_t get_module_image_base (const dlframe &entry) {
245+ uintptr_t pe_get_module_image_base (const std::string& obj_path) {
206246 // PE header values are little endian
207247 bool do_swap = !is_little_endian ();
208- FILE* file = fopen (entry. obj_path .c_str (), " rb" );
248+ FILE* file = fopen (obj_path.c_str (), " rb" );
209249 char magic[2 ];
210250 internal_verify (fread (magic, 1 , 2 , file) == 2 ); // file + 0x0
211251 internal_verify (memcmp (magic, " MZ" , 2 ) == 0 );
@@ -251,6 +291,22 @@ namespace cpptrace {
251291 fclose (file);
252292 return image_base;
253293 }
294+
295+ uintptr_t get_module_image_base (const dlframe &entry) {
296+ static std::mutex mutex;
297+ std::lock_guard<std::mutex> lock (mutex);
298+ static std::unordered_map<std::string, uintptr_t > cache;
299+ auto it = cache.find (entry.obj_path );
300+ if (it == cache.end ()) {
301+ // arguably it'd be better to release the lock while computing this, but also arguably it's good to not
302+ // have two threads try to do the same computation
303+ auto base = pe_get_module_image_base (entry.obj_path );
304+ cache.insert (it, {entry.obj_path , base});
305+ return base;
306+ } else {
307+ return it->second ;
308+ }
309+ }
254310 #endif
255311
256312 struct symbolizer ::impl {
0 commit comments