Skip to content

Commit 18accf2

Browse files
committed
[cpu linux] Implement stack walking
Implement stack walking using libunwind and libiberty. These two libraries would be need dependencies. The libunwind library is quite common on a linux system and libiberty is part of the GNU compiler. Add tests for stack_walker.
1 parent b74eac3 commit 18accf2

File tree

5 files changed

+555
-3
lines changed

5 files changed

+555
-3
lines changed

.gdbinit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ handle SIG32 nostop noprint
88
handle SIG36 nostop noprint
99
# Ignore PosixThread user callback event
1010
handle SIG37 nostop noprint
11+
# Ignore Threaded Stack Walker Capture event
12+
handle SIGUSR1 nostop noprint

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ addons:
2525
#- libvulkan-dev
2626
- libx11-dev
2727
- liblz4-dev
28+
- libiberty-dev
29+
- libunwind-dev
2830

2931
jobs:
3032
include:

premake5.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ filter("platforms:Linux")
9898
"lz4",
9999
"pthread",
100100
"rt",
101+
"unwind",
102+
"iberty",
101103
})
102104
linkoptions({
103105
({os.outputof("pkg-config --libs gtk+-3.0")})[1],

src/xenia/cpu/stack_walker_posix.cc

Lines changed: 280 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,293 @@
99

1010
#include "xenia/cpu/stack_walker.h"
1111

12+
#include <condition_variable>
13+
#include <mutex>
14+
#include <unordered_set>
15+
#define UNW_LOCAL_ONLY
16+
#include <libunwind.h>
17+
#include <signal.h>
18+
19+
#include "stack_walker.h"
1220
#include "xenia/base/logging.h"
21+
#include "xenia/cpu/backend/code_cache.h"
1322

1423
namespace xe {
1524
namespace cpu {
1625

26+
const int CAPTURE_SIGNAL = SIGUSR1;
27+
28+
struct PosixStackCapture {
29+
unw_context_t context_;
30+
std::vector<unw_cursor_t> cursors_;
31+
32+
PosixStackCapture(unw_context_t&& context, size_t frame_offset,
33+
size_t frame_count)
34+
: context_(context) {
35+
unw_cursor_t cursor;
36+
unw_init_local(&cursor, &context);
37+
38+
for (size_t i = 0; i < frame_offset; ++i) {
39+
if (unw_step(&cursor) < 0) {
40+
return;
41+
}
42+
}
43+
for (uint32_t i = 0; i < frame_count; ++i) {
44+
int step_result = unw_step(&cursor);
45+
if (step_result == 0) {
46+
break;
47+
} else if (step_result < 0) {
48+
switch (-step_result) {
49+
case UNW_EUNSPEC:
50+
return;
51+
case UNW_ENOINFO:
52+
return;
53+
case UNW_EBADVERSION:
54+
return;
55+
case UNW_EINVALIDIP:
56+
return;
57+
case UNW_EBADFRAME:
58+
return;
59+
case UNW_ESTOPUNWIND:
60+
return;
61+
default:
62+
return;
63+
}
64+
}
65+
cursors_.push_back(cursor);
66+
}
67+
}
68+
};
69+
} // namespace cpu
70+
} // namespace xe
71+
72+
namespace std {
73+
// TODO(bwrsandman): This needs to be expanded on to avoid collisions of stacks
74+
// with same ip and depth
75+
template <>
76+
struct hash<xe::cpu::PosixStackCapture> {
77+
typedef xe::cpu::PosixStackCapture argument_t;
78+
typedef std::size_t result_t;
79+
result_t operator()(argument_t const& capture) const noexcept {
80+
if (capture.cursors_.empty()) {
81+
return 0;
82+
}
83+
result_t result = 0;
84+
unw_word_t ip;
85+
unw_cursor_t front_cursor = capture.cursors_.front();
86+
unw_get_reg(&front_cursor, UNW_REG_IP, &ip);
87+
auto h1 = std::hash<unw_word_t>{}(ip);
88+
auto h2 = std::hash<size_t>{}(capture.cursors_.size());
89+
return h1 ^ (h2 << 1);
90+
}
91+
};
92+
93+
template <>
94+
struct equal_to<xe::cpu::PosixStackCapture> {
95+
typedef xe::cpu::PosixStackCapture argument_t;
96+
typedef std::size_t result_t;
97+
result_t operator()(argument_t const& capture_1,
98+
argument_t const& capture_2) const noexcept {
99+
if (capture_1.cursors_.size() != capture_2.cursors_.size()) {
100+
return false;
101+
}
102+
unw_word_t reg_1, reg_2;
103+
unw_cursor_t front_cursor_1 = capture_1.cursors_.front();
104+
unw_cursor_t front_cursor_2 = capture_2.cursors_.front();
105+
for (uint32_t i = 0; i < UNW_REG_LAST; ++i) {
106+
unw_get_reg(&front_cursor_1, i, &reg_1);
107+
unw_get_reg(&front_cursor_2, i, &reg_2);
108+
if (reg_1 != reg_2) {
109+
return false;
110+
}
111+
}
112+
return true;
113+
}
114+
};
115+
} // namespace std
116+
117+
namespace xe {
118+
namespace cpu {
119+
120+
#include <libiberty/demangle.h>
121+
#include <memory>
122+
std::string demangle(const char* mangled_name) {
123+
if (mangled_name == std::string()) {
124+
return "";
125+
}
126+
std::unique_ptr<char, decltype(&std::free)> ptr(
127+
cplus_demangle(mangled_name, DMGL_NO_OPTS), &std::free);
128+
return ptr ? ptr.get() : mangled_name;
129+
}
130+
131+
static struct signal_handler_arg_t {
132+
std::mutex mutex;
133+
std::condition_variable cv;
134+
volatile bool set;
135+
unw_context_t context_;
136+
} signal_handler_arg = {};
137+
138+
class PosixStackWalker : public StackWalker {
139+
public:
140+
explicit PosixStackWalker(backend::CodeCache* code_cache) {
141+
// Get the boundaries of the code cache so we can quickly tell if a symbol
142+
// is ours or not.
143+
// We store these globally so that the Sym* callbacks can access them.
144+
// They never change, so it's fine even if they are touched from multiple
145+
// threads.
146+
// code_cache_ = code_cache;
147+
// code_cache_min_ = code_cache_->base_address();
148+
// code_cache_max_ = code_cache_->base_address() +
149+
// code_cache_->total_size();
150+
}
151+
152+
bool Initialize() { return true; }
153+
154+
size_t CaptureStackTrace(uint64_t* frame_host_pcs, size_t frame_offset,
155+
size_t frame_count,
156+
uint64_t* out_stack_hash) override {
157+
if (out_stack_hash) {
158+
*out_stack_hash = 0;
159+
}
160+
161+
unw_context_t context;
162+
if (unw_getcontext(&context) != 0) {
163+
return false;
164+
}
165+
166+
auto capture =
167+
PosixStackCapture(std::move(context), frame_offset, frame_count);
168+
169+
for (uint32_t i = 0; i < capture.cursors_.size(); ++i) {
170+
frame_host_pcs[i] = reinterpret_cast<uintptr_t>(&capture.cursors_[i]);
171+
}
172+
// Two identical stack traces will generate identical hash values.
173+
if (out_stack_hash) {
174+
*out_stack_hash = captures_.hash_function()(capture);
175+
}
176+
177+
auto size = capture.cursors_.size();
178+
179+
captures_.insert(std::move(capture));
180+
181+
return size;
182+
}
183+
184+
size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
185+
size_t frame_offset, size_t frame_count,
186+
const X64Context* in_host_context,
187+
X64Context* out_host_context,
188+
uint64_t* out_stack_hash) override {
189+
if (out_stack_hash) {
190+
*out_stack_hash = 0;
191+
}
192+
193+
// Install signal capture
194+
struct sigaction action {};
195+
struct sigaction previous_action {};
196+
action.sa_flags = SA_SIGINFO;
197+
action.sa_sigaction = [](int signal, siginfo_t* info, void* context) {
198+
std::unique_lock<std::mutex> lock(signal_handler_arg.mutex);
199+
unw_getcontext(&signal_handler_arg.context_);
200+
signal_handler_arg.set = true;
201+
signal_handler_arg.cv.notify_one();
202+
};
203+
sigemptyset(&action.sa_mask);
204+
sigaction(CAPTURE_SIGNAL, &action, &previous_action);
205+
206+
// Send signal
207+
pthread_kill(reinterpret_cast<pthread_t>(thread_handle), CAPTURE_SIGNAL);
208+
209+
// Wait to have data back
210+
unw_context_t uc;
211+
{
212+
std::unique_lock<std::mutex> lock(signal_handler_arg.mutex);
213+
signal_handler_arg.cv.wait(lock);
214+
uc = signal_handler_arg.context_;
215+
signal_handler_arg.set = false;
216+
}
217+
218+
// Restore original handler if it existed
219+
sigaction(CAPTURE_SIGNAL, &previous_action, nullptr);
220+
221+
// Skip signal callback frame
222+
++frame_offset;
223+
224+
auto capture = PosixStackCapture(std::move(signal_handler_arg.context_),
225+
frame_offset, frame_count);
226+
227+
for (uint32_t i = 0; i < capture.cursors_.size(); ++i) {
228+
frame_host_pcs[i] = reinterpret_cast<uintptr_t>(&capture.cursors_[i]);
229+
}
230+
// Two identical stack traces will generate identical hash values.
231+
if (out_stack_hash) {
232+
*out_stack_hash = captures_.hash_function()(capture);
233+
}
234+
235+
auto size = capture.cursors_.size();
236+
237+
captures_.insert(std::move(capture));
238+
239+
return size;
240+
}
241+
242+
bool ResolveStack(uint64_t* frame_host_pcs, StackFrame* frames,
243+
size_t frame_count) override {
244+
for (size_t i = 0; i < frame_count; ++i) {
245+
auto& frame = frames[i];
246+
unw_cursor_t& cursor =
247+
*reinterpret_cast<unw_cursor_t*>(frame_host_pcs[i]);
248+
std::memset(&frame, 0, sizeof(frame));
249+
frame.host_pc = frame_host_pcs[i];
250+
251+
// If in the generated range, we know it's ours.
252+
// if (frame.host_pc >= code_cache_min_ && frame.host_pc <
253+
// code_cache_max_) {
254+
//
255+
// } else {
256+
// // Host symbol, which means either emulator or system.
257+
// frame.type = StackFrame::Type::kHost;
258+
// }
259+
std::array<char, sizeof(frame.host_symbol.name)> host_symbol_name = {};
260+
auto ok = unw_get_proc_name(&cursor, host_symbol_name.data(),
261+
host_symbol_name.size(),
262+
&frame.host_symbol.address);
263+
switch (ok) {
264+
case UNW_ESUCCESS:
265+
break;
266+
case UNW_EUNSPEC:
267+
return false;
268+
case UNW_ENOINFO:
269+
return false;
270+
case UNW_ENOMEM:
271+
return false;
272+
default:
273+
return false;
274+
}
275+
auto demangled_host_symbol_name = demangle(host_symbol_name.data());
276+
std::strncpy(frame.host_symbol.name, demangled_host_symbol_name.c_str(),
277+
sizeof(frame.host_symbol.name));
278+
// unw_proc_info_t info = {};
279+
// ok = unw_get_proc_info(&cursor, &info);
280+
}
281+
return true;
282+
}
283+
284+
// static xe::cpu::backend::CodeCache* code_cache_;
285+
// static uint32_t code_cache_min_;
286+
// static uint32_t code_cache_max_;
287+
std::unordered_set<PosixStackCapture> captures_;
288+
};
289+
17290
std::unique_ptr<StackWalker> StackWalker::Create(
18291
backend::CodeCache* code_cache) {
19-
XELOGD("Stack walker unimplemented on posix");
20-
return nullptr;
292+
auto stack_walker = std::make_unique<PosixStackWalker>(code_cache);
293+
if (!stack_walker->Initialize()) {
294+
XELOGE("Unable to initialize stack walker: debug/save states disabled");
295+
return nullptr;
296+
}
297+
return std::unique_ptr<StackWalker>(stack_walker.release());
21298
}
22299

23300
} // namespace cpu
24-
} // namespace xe
301+
} // namespace xe

0 commit comments

Comments
 (0)