Skip to content

Commit d12cd31

Browse files
Cache expensive computations (jeremy-rifkin#18)
1 parent ec3bb29 commit d12cd31

File tree

4 files changed

+105
-57
lines changed

4 files changed

+105
-57
lines changed

src/full/full_trace_with_libbacktrace.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ namespace cpptrace {
5353
fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum);
5454
}
5555

56-
std::mutex state_mutex;
57-
5856
backtrace_state* get_backtrace_state() {
59-
const std::lock_guard<std::mutex> lock(state_mutex);
57+
static std::mutex mutex;
58+
const std::lock_guard<std::mutex> lock(mutex);
6059
// backtrace_create_state must be called only one time per program
6160
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
6261
static backtrace_state* state = nullptr;

src/platform/program_name.hpp

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,14 @@
44
#include <mutex>
55
#include <string>
66

7-
namespace cpptrace {
8-
namespace detail {
9-
inline std::mutex& get_program_name_mutex() { // stupid workaround for an inline variable
10-
static std::mutex mutex;
11-
return mutex;
12-
}
13-
}
14-
}
15-
167
#if defined(_WIN32)
178
#include <windows.h>
189

1910
namespace cpptrace {
2011
namespace detail {
2112
inline std::string program_name() {
22-
const std::lock_guard<std::mutex> lock(get_program_name_mutex());
13+
static std::mutex mutex;
14+
const std::lock_guard<std::mutex> lock(mutex);
2315
static std::string name;
2416
static bool did_init = false;
2517
static bool valid = false;
@@ -46,7 +38,8 @@ namespace cpptrace {
4638
namespace cpptrace {
4739
namespace detail {
4840
inline const char* program_name() {
49-
const std::lock_guard<std::mutex> lock(get_program_name_mutex());
41+
static std::mutex mutex;
42+
const std::lock_guard<std::mutex> lock(mutex);
5043
static std::string name;
5144
static bool did_init = false;
5245
static bool valid = false;
@@ -73,7 +66,8 @@ namespace cpptrace {
7366
namespace cpptrace {
7467
namespace detail {
7568
inline const char* program_name() {
76-
const std::lock_guard<std::mutex> lock(get_program_name_mutex());
69+
static std::mutex mutex;
70+
const std::lock_guard<std::mutex> lock(mutex);
7771
static std::string name;
7872
static bool did_init = false;
7973
static bool valid = false;

src/symbols/symbols_with_addr2line.cpp

Lines changed: 95 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
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 {

src/symbols/symbols_with_libbacktrace.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ namespace cpptrace {
4242
fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum);
4343
}
4444

45-
std::mutex state_mutex;
46-
4745
backtrace_state* get_backtrace_state() {
48-
const std::lock_guard<std::mutex> lock(state_mutex);
46+
static std::mutex mutex;
47+
const std::lock_guard<std::mutex> lock(mutex);
4948
// backtrace_create_state must be called only one time per program
5049
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
5150
static backtrace_state* state = nullptr;

0 commit comments

Comments
 (0)