Skip to content

Commit a6f06d8

Browse files
authored
Major reliability improvement for logging stack traces (#287)
1 parent ea03d7e commit a6f06d8

12 files changed

Lines changed: 1044 additions & 437 deletions
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#ifndef __APPLE__
2+
# error This file is only for Darwin (macOS/iOS) platforms
3+
#endif
4+
5+
#include "Backtrace.hh"
6+
#include <execinfo.h>
7+
#include <dlfcn.h>
8+
#include "betterassert.hh"
9+
10+
namespace fleece {
11+
Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) {
12+
Backtrace::frameInfo frame = {};
13+
frame.pc = addr;
14+
15+
Dl_info info = {};
16+
if ( !dladdr(addr, &info) ) { return frame; }
17+
18+
if ( info.dli_fname ) {
19+
frame.library = info.dli_fname;
20+
if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1;
21+
}
22+
23+
frame.function = info.dli_sname;
24+
if ( info.dli_saddr )
25+
frame.offset = reinterpret_cast<size_t>(frame.pc) - reinterpret_cast<size_t>(info.dli_saddr);
26+
27+
if ( info.dli_fbase ) {
28+
auto pc = reinterpret_cast<uintptr_t>(frame.pc);
29+
if ( !stack_top ) pc -= 1;
30+
frame.imageOffset = pc - reinterpret_cast<uintptr_t>(info.dli_fbase);
31+
}
32+
33+
return frame;
34+
}
35+
36+
int Backtrace::raw_capture(void** buffer, int max, void* context) {
37+
if ( max == 0 ) return 0;
38+
return backtrace(buffer, max);
39+
}
40+
41+
const char* Backtrace::getSymbol(unsigned i) const {
42+
precondition(i < _addrs.size());
43+
if ( !_symbols ) _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size()));
44+
45+
if ( _symbols ) {
46+
const char* s = _symbols[i];
47+
48+
// Skip line number and whitespace:
49+
while ( *s && isdigit(*s) ) ++s;
50+
while ( *s && isspace(*s) ) ++s;
51+
52+
return s;
53+
}
54+
return nullptr;
55+
}
56+
} // namespace fleece
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#if !defined(__linux__) && !defined(__ANDROID__)
2+
# error "This implementation is meant for Linux and Android only"
3+
#endif
4+
5+
#include "Backtrace.hh"
6+
#include <dlfcn.h> // dladdr()
7+
#include <unwind.h> // part of compiler runtime, no extra link needed
8+
#include <backtrace.h>
9+
#include <mutex>
10+
#include <cstring>
11+
12+
namespace fleece {
13+
using namespace std;
14+
15+
struct BacktraceState {
16+
void** current;
17+
void** end;
18+
};
19+
20+
static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
21+
BacktraceState* state = static_cast<BacktraceState*>(arg);
22+
uintptr_t pc = _Unwind_GetIP(context);
23+
if ( pc ) {
24+
if ( state->current == state->end ) return _URC_END_OF_STACK;
25+
*state->current++ = reinterpret_cast<void*>(pc);
26+
}
27+
return _URC_NO_REASON;
28+
}
29+
30+
int Backtrace::raw_capture(void** buffer, int max, void* context) {
31+
BacktraceState state = {buffer, buffer + max};
32+
_Unwind_Backtrace(unwindCallback, &state);
33+
return int(state.current - buffer);
34+
}
35+
36+
static backtrace_state* getBacktraceState() {
37+
static std::once_flag once;
38+
static backtrace_state* state;
39+
std::call_once(once, [] { state = backtrace_create_state(nullptr, /*threaded=*/1, nullptr, nullptr); });
40+
return state;
41+
}
42+
43+
struct ResolvedInfo {
44+
const char* function = nullptr;
45+
const char* file = nullptr;
46+
int line = 0;
47+
uintptr_t symval = 0; // function start address, for offset calculation
48+
};
49+
50+
static ResolvedInfo backtraceResolve(uintptr_t pc) {
51+
ResolvedInfo info;
52+
backtrace_pcinfo(
53+
getBacktraceState(), pc,
54+
[](void* data, uintptr_t, const char* file, int line, const char* fn) -> int {
55+
auto* r = static_cast<ResolvedInfo*>(data);
56+
if ( fn && !r->function ) r->function = fn;
57+
if ( file && !r->file ) {
58+
r->file = file;
59+
r->line = line;
60+
}
61+
return 0;
62+
},
63+
nullptr, &info);
64+
65+
// Always call syminfo: provides symval (function start) for offset, and function name fallback
66+
backtrace_syminfo(
67+
getBacktraceState(), pc,
68+
[](void* data, uintptr_t, const char* sym, uintptr_t symval, uintptr_t) {
69+
auto* r = static_cast<ResolvedInfo*>(data);
70+
if ( !r->function && sym ) r->function = sym;
71+
if ( symval ) r->symval = symval;
72+
},
73+
nullptr, &info);
74+
return info;
75+
}
76+
77+
Backtrace::frameInfo Backtrace::getFrame(const void* addr, bool stack_top) {
78+
Backtrace::frameInfo frame = {};
79+
frame.pc = addr;
80+
81+
// dladdr gives us the library name regardless of symbol visibility,
82+
// and a fallback saddr/sname for exported symbols.
83+
Dl_info dlInfo = {};
84+
if ( !dladdr(frame.pc, &dlInfo) ) { return frame; }
85+
86+
if ( dlInfo.dli_fname ) {
87+
frame.library = dlInfo.dli_fname;
88+
if ( const char* slash = strrchr(frame.library, '/') ) frame.library = slash + 1;
89+
}
90+
91+
#ifdef __ANDROID__
92+
frame.function = info.dli_sname;
93+
if ( info.dli_saddr )
94+
frame.offset = reinterpret_cast<size_t>(frame.pc) - reinterpret_cast<size_t>(info.dli_saddr);
95+
96+
if ( info.dli_fbase ) {
97+
auto pc = reinterpret_cast<uintptr_t>(frame.pc);
98+
if ( !stack_top ) pc -= 1;
99+
frame.imageOffset = pc - reinterpret_cast<uintptr_t>(info.dli_fbase);
100+
}
101+
#else
102+
uintptr_t pc = reinterpret_cast<uintptr_t>(frame.pc);
103+
if ( !stack_top ) pc -= 1; // return address → call site
104+
if ( dlInfo.dli_fbase ) frame.imageOffset = pc - reinterpret_cast<uintptr_t>(dlInfo.dli_fbase);
105+
auto resolved = backtraceResolve(pc);
106+
frame.function = resolved.function;
107+
frame.file = resolved.file;
108+
frame.line = resolved.line;
109+
// Prefer symval from libbacktrace (works for hidden-visibility symbols);
110+
// fall back to dladdr's saddr for exported symbols.
111+
if ( resolved.symval ) frame.offset = reinterpret_cast<uintptr_t>(frame.pc) - resolved.symval;
112+
else if ( dlInfo.dli_saddr )
113+
frame.offset = (size_t)frame.pc - (size_t)dlInfo.dli_saddr;
114+
#endif
115+
116+
return frame;
117+
}
118+
119+
const char* Backtrace::getSymbol(unsigned i) const {
120+
// delegate to getFrame which already uses libbacktrace
121+
static thread_local string tName;
122+
auto frame = getFrame(i);
123+
if ( frame.function ) {
124+
tName = frame.function;
125+
return tName.c_str();
126+
}
127+
128+
return nullptr;
129+
}
130+
131+
string FunctionName(const void* pc) {
132+
#if !defined(__ANDROID__)
133+
uintptr_t addr = reinterpret_cast<uintptr_t>(pc) - 1;
134+
if ( const char* name = backtraceResolve(addr).function ) return Unmangle(name);
135+
return {};
136+
#else
137+
Dl_info info = {};
138+
dladdr(pc, &info);
139+
if ( info.dli_sname ) return Unmangle(info.dli_sname);
140+
return {};
141+
#endif
142+
}
143+
} // namespace fleece

0 commit comments

Comments
 (0)