forked from jeremy-rifkin/cpptrace
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathobject.cpp
More file actions
175 lines (168 loc) · 6.64 KB
/
object.cpp
File metadata and controls
175 lines (168 loc) · 6.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include "object.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "module_base.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#if IS_LINUX
#include <link.h> // needed for dladdr1's link_map info
#endif
#elif IS_WINDOWS
#include <windows.h>
#endif
namespace cpptrace {
namespace detail {
#if IS_LINUX || IS_APPLE
#if defined(CPPTRACE_HAS_DL_FIND_OBJECT) || defined(CPPTRACE_HAS_DLADDR1)
std::string resolve_l_name(const char* l_name) {
if(l_name != nullptr && l_name[0] != 0) {
return l_name;
} else {
// empty l_name, this means it's the currently running executable
// TODO: Caching and proper handling
char buffer[CPPTRACE_PATH_MAX + 1]{};
auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
if(res == -1) {
return ""; // TODO
} else {
return buffer;
}
}
}
#endif
// dladdr queries are needed to get pre-ASLR addresses and targets to run symbol resolution on
// _dl_find_object is preferred if at all possible as it is much faster (added in glibc 2.35)
// dladdr1 is preferred if possible because it allows for a more accurate object path to be resolved (glibc 2.3.3)
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple
object_frame get_frame_object_info(frame_ptr address) {
// Use _dl_find_object when we can, it's orders of magnitude faster
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
dl_find_object result;
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread safe
frame.object_path = resolve_l_name(result.dlfo_link_map->l_name);
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
}
return frame;
}
#elif defined(CPPTRACE_HAS_DLADDR1)
object_frame get_frame_object_info(frame_ptr address) {
// https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26
Dl_info info;
link_map* link_map_info;
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
if(
// thread safe
dladdr1(reinterpret_cast<void*>(address), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP)
) {
frame.object_path = resolve_l_name(link_map_info->l_name);
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
base.drop_error();
}
}
return frame;
}
#else
// glibc dladdr may not return an accurate dli_fname as it uses argv[0] for addresses in the main executable
// https://github.com/bminor/glibc/blob/caed1f5c0b2e31b5f4e0f21fea4b2c9ecd3b5b30/elf/dl-addr.c#L33-L36
// macos doesn't have dladdr1 but its dli_fname behaves more sensibly, same with some other libc's like musl
object_frame get_frame_object_info(frame_ptr address) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
Dl_info info;
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
if(dladdr(reinterpret_cast<void*>(address), &info)) { // thread safe
frame.object_path = info.dli_fname;
auto base = get_module_image_base(info.dli_fname);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
base.drop_error();
}
}
return frame;
}
#endif
#else
std::string get_module_name(HMODULE handle) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> cache;
auto it = cache.find(handle);
if(it == cache.end()) {
char path[MAX_PATH];
if(GetModuleFileNameA(handle, path, sizeof(path))) {
cache.insert(it, {handle, path});
return path;
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
cache.insert(it, {handle, ""});
return "";
}
} else {
return it->second;
}
}
object_frame get_frame_object_info(frame_ptr address) {
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
HMODULE handle;
// Multithread safe as long as another thread doesn't come along and free the module
if(GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<const char*>(address),
&handle
)) {
frame.object_path = get_module_name(handle);
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(handle)
+ base.unwrap_value();
} else {
base.drop_error();
}
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
return frame;
}
#endif
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses) {
std::vector<object_frame> frames;
frames.reserve(addresses.size());
for(const frame_ptr address : addresses) {
frames.push_back(get_frame_object_info(address));
}
return frames;
}
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
auto base = get_module_image_base(frame.object_path);
if(base.is_error()) {
throw base.unwrap_error(); // This throw is intentional
}
return {
frame.raw_address,
frame.address_relative_to_object_start + base.unwrap_value(),
frame.object_path
};
}
}
}