Skip to content

Commit 04e8eeb

Browse files
committed
clean up macos crashlogs
1 parent ea8ab36 commit 04e8eeb

5 files changed

Lines changed: 102 additions & 92 deletions

File tree

loader/src/internal/crashlog.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ std::string_view crashlog::Image::shortName() const {
2929
return std::string_view{name_}.substr(slash + 1);
3030
}
3131

32+
bool crashlog::Image::isGameBinary() const {
33+
return this->address == base::get();
34+
}
35+
3236
ptrdiff_t crashlog::StackFrame::imageOffset() const {
3337
if (image == nullptr) {
3438
return 0;

loader/src/internal/crashlog.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ namespace crashlog {
4141

4242
std::string_view name() const;
4343
std::string_view shortName() const;
44+
bool isGameBinary() const;
4445
};
4546

4647
struct StackFrame {

loader/src/internal/crashlogApple.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "crashlogUnix.hpp"
22
#include <Geode/utils/ZStringView.hpp>
3+
#include <Geode/utils/string.hpp>
34

45
#include <mach-o/dyld_images.h>
56
#include <mach-o/dyld.h>
@@ -206,3 +207,18 @@ static std::vector<uintptr_t> const& getFunctionStarts() {
206207
return funcs;
207208
}
208209

210+
/// Parses a string in format "0 Geode.dylib 0x12345678 symbol + offset" into just the symbol and offset
211+
static std::pair<std::string_view, uintptr_t> parseBacktraceSymbol(std::string_view line) {
212+
size_t plusPos = line.rfind(" + ");
213+
if (plusPos == std::string_view::npos) return {};
214+
215+
auto leftPart = line.substr(0, plusPos);
216+
auto offsetPart = line.substr(plusPos + 3); // 3 to skip " + "
217+
auto offset = geode::utils::numFromString<uintptr_t>(offsetPart).unwrapOr(0);
218+
219+
size_t lastSpacePos = leftPart.rfind(' ');
220+
if (lastSpacePos == std::string_view::npos) return {};
221+
222+
auto symbol = leftPart.substr(lastSpacePos + 1);
223+
return { symbol, offset };
224+
}

loader/src/internal/crashlogUnix.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <signal.h>
44
#include <Geode/utils/StringBuffer.hpp>
55
#include <crashlog.hpp>
6+
#include <cxxabi.h>
67

78
inline std::string_view getSignalCodeString(int signal, siginfo_t* siginfo) {
89
switch(signal) {
@@ -68,6 +69,10 @@ inline void writeSignalDetail(crashlog::Buffer& stream, crashlog::CrashContext&
6869
case SEGV_ACCERR: {
6970
stream.append("invalid permissions for mapped object");
7071
} break;
72+
73+
default: {
74+
stream.append("unknown reason (code {})", siginfo->si_code);
75+
} break;
7176
}
7277

7378
stream.append(')');
@@ -90,6 +95,10 @@ inline void writeSignalDetail(crashlog::Buffer& stream, crashlog::CrashContext&
9095
case BUS_OBJERR: {
9196
stream.append("object-specific hardware error");
9297
} break;
98+
99+
default: {
100+
stream.append("unknown reason (code {})", siginfo->si_code);
101+
} break;
93102
}
94103

95104
stream.append(')');
@@ -102,3 +111,14 @@ inline void writeSignalDetail(crashlog::Buffer& stream, crashlog::CrashContext&
102111
}
103112

104113
}
114+
115+
inline std::string demangle(const char* name) {
116+
std::string out;
117+
int status;
118+
auto demangle = abi::__cxa_demangle(name, 0, 0, &status);
119+
if (status == 0) {
120+
out = demangle;
121+
}
122+
free(demangle);
123+
return out;
124+
}

loader/src/platform/mac/crashlog.mm

Lines changed: 61 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
#include <filesystem>
88
#include <execinfo.h>
99
#include <dlfcn.h>
10-
#include <cxxabi.h>
1110
#include <algorithm>
11+
#include <asp/iter.hpp>
1212

1313
#include <mach-o/dyld_images.h>
1414
#include <mach-o/dyld.h>
@@ -81,114 +81,83 @@
8181
std::_Exit(EXIT_FAILURE);
8282
}
8383

84+
8485
std::vector<StackFrame> CrashContext::getStacktrace() {
85-
// this function is evil
8686
std::vector<StackFrame> frames;
8787

8888
auto messages = backtrace_symbols(s_backtrace.data(), s_backtraceSize);
8989

90-
auto gameDir = geode::utils::string::pathToString(dirs::getGameDir());
91-
std::stringstream lines(addr2Line());
92-
93-
for (int i = 1; i < s_backtraceSize; ++i) {
94-
auto message = std::string(messages[i]);
95-
96-
auto stream = std::stringstream(message);
97-
int index;
98-
std::string binary;
99-
uintptr_t address;
100-
std::string function;
101-
uintptr_t offset;
102-
std::string line;
103-
104-
stream >> index;
105-
106-
if (!lines.eof()) {
107-
std::getline(lines, line);
108-
}
109-
std::getline(stream, binary);
110-
auto cutoff = binary.find("0x");
111-
stream = std::stringstream(binary.substr(cutoff));
112-
binary = geode::utils::string::trim(binary.substr(0, cutoff));
113-
stream >> std::hex >> address >> std::dec;
90+
for (int i = 1; i < s_backtraceSize; i++) {
91+
void* address = s_backtrace[i];
92+
std::string_view symbolStr{messages[i]};
11493

11594
StackFrame frame{};
116-
frame.address = address;
117-
auto image = g_context.imageFromAddress(reinterpret_cast<void*>(address));
118-
frame.image = image;
119-
120-
if (!line.empty()) {
121-
// log::debug("address: {}", address);
122-
// log::debug("image: {}", image);
123-
124-
if (image) {
125-
auto baseAddress = image->address;
126-
127-
uintptr_t offset = address - (uintptr_t)baseAddress;
128-
129-
if (base::get() == (uintptr_t)baseAddress) {
130-
// find closest function start
131-
auto const& funcs = getFunctionStarts();
132-
auto iter = std::upper_bound(funcs.begin(), funcs.end(), offset);
133-
if (iter != funcs.begin()) {
134-
--iter;
135-
auto funcOffset = *iter;
136-
auto funcName = crashlog::lookupFunctionByOffset(funcOffset);
137-
frame.offset = offset - funcOffset;
138-
139-
if (funcName.empty()) {
140-
frame.symbol = fmt::format("sub_{:x}", funcOffset);
141-
} else {
142-
frame.symbol = funcName;
143-
}
144-
}
95+
frame.address = reinterpret_cast<uintptr_t>(address);
96+
frame.image = g_context.imageFromAddress(address);
97+
98+
// if this is inside GD code, use function starts and look up the function in the known function list
99+
if (frame.image && frame.image->isGameBinary()) {
100+
uintptr_t imageOffset = frame.imageOffset();
101+
// find closest function start
102+
auto const& funcs = getFunctionStarts();
103+
auto iter = std::upper_bound(funcs.begin(), funcs.end(), imageOffset);
104+
if (iter != funcs.begin()) {
105+
--iter;
106+
auto funcOffset = *iter;
107+
auto funcName = crashlog::lookupFunctionByOffset(funcOffset);
108+
frame.offset = imageOffset - funcOffset;
109+
110+
if (funcName.empty()) {
111+
frame.symbol = fmt::format("sub_{:x}", funcOffset);
112+
} else {
113+
frame.symbol = funcName;
145114
}
146115
}
147-
148-
if (frame.symbol.empty()) {
149-
// try to extract the symbol name
150-
auto endSymName = line.find(" (in ");
151-
if (endSymName != std::string::npos && !line.starts_with("0x")) {
152-
frame.symbol = line.substr(0, endSymName);
116+
} else {
117+
// parse from backtrace_symbols, and use dladdr as fallback
118+
auto [symbol, offset] = parseBacktraceSymbol(symbolStr);
119+
120+
Dl_info info{};
121+
if (symbol.empty() && dladdr(address, &info)) {
122+
if (info.dli_sname) {
123+
symbol = info.dli_sname;
124+
offset = reinterpret_cast<uintptr_t>(address) - reinterpret_cast<uintptr_t>(info.dli_saddr);
153125
}
126+
}
154127

155-
// now if that succeeded, try to extract the file/line information
156-
auto lastParen = line.rfind(')');
157-
auto lineInfoSplit = line.rfind(':', lastParen);
158-
auto startParen = line.rfind('(', lineInfoSplit);
128+
auto demangled = demangle(std::string{symbol}.c_str());
129+
frame.symbol = demangled.empty() ? symbol : demangled;
130+
frame.offset = offset;
131+
}
159132

160-
if (startParen != std::string::npos && lastParen != std::string::npos && lineInfoSplit != std::string::npos) {
161-
auto inner = std::string_view{line}.substr(startParen + 1, lastParen - startParen - 1);
162-
auto colon = inner.rfind(':');
163-
auto file = inner.substr(0, colon);
164-
auto lineNum = inner.substr(colon + 1);
133+
frames.push_back(std::move(frame));
134+
}
165135

166-
frame.file = file;
167-
frame.line = utils::numFromString<uint32_t>(lineNum).unwrapOr(0);
168-
}
169-
}
136+
// parse extra information (debug data)
137+
138+
auto debugOutput = addr2Line();
139+
140+
for (auto [i, line] : asp::iter::split(debugOutput, '\n').enumerate()) {
141+
// grab the right parens
142+
auto lastParen = line.rfind(')');
143+
auto startParen = line.rfind('(', lastParen);
144+
if (startParen == std::string::npos || lastParen == std::string::npos) {
145+
continue;
170146
}
171-
else {
172-
std::getline(stream, function);
173-
cutoff = function.find("+");
174-
stream = std::stringstream(function.substr(cutoff));
175-
stream >> offset;
176-
function = geode::utils::string::trim(function.substr(0, cutoff));
177-
178-
{
179-
int status;
180-
auto demangle = abi::__cxa_demangle(function.c_str(), 0, 0, &status);
181-
if (status == 0) {
182-
function = demangle;
183-
}
184-
free(demangle);
185-
}
186147

187-
frame.symbol = function;
188-
frame.offset = offset;
148+
auto inner = std::string_view{line}.substr(startParen + 1, lastParen - startParen - 1);
149+
auto colon = inner.rfind(':');
150+
if (colon == std::string::npos) {
151+
continue;
189152
}
190153

191-
frames.push_back(std::move(frame));
154+
auto file = inner.substr(0, colon);
155+
auto lineStr = inner.substr(colon + 1);
156+
auto lineNum = geode::utils::numFromString<uint32_t>(lineStr).unwrapOr(0);
157+
158+
auto& frame = frames[i];
159+
frame.file = file;
160+
frame.line = lineNum;
192161
}
193162

194163
free(messages);

0 commit comments

Comments
 (0)