|
7 | 7 | #include <filesystem> |
8 | 8 | #include <execinfo.h> |
9 | 9 | #include <dlfcn.h> |
10 | | -#include <cxxabi.h> |
11 | 10 | #include <algorithm> |
| 11 | +#include <asp/iter.hpp> |
12 | 12 |
|
13 | 13 | #include <mach-o/dyld_images.h> |
14 | 14 | #include <mach-o/dyld.h> |
|
81 | 81 | std::_Exit(EXIT_FAILURE); |
82 | 82 | } |
83 | 83 |
|
| 84 | + |
84 | 85 | std::vector<StackFrame> CrashContext::getStacktrace() { |
85 | | - // this function is evil |
86 | 86 | std::vector<StackFrame> frames; |
87 | 87 |
|
88 | 88 | auto messages = backtrace_symbols(s_backtrace.data(), s_backtraceSize); |
89 | 89 |
|
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]}; |
114 | 93 |
|
115 | 94 | 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; |
145 | 114 | } |
146 | 115 | } |
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); |
153 | 125 | } |
| 126 | + } |
154 | 127 |
|
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 | + } |
159 | 132 |
|
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 | + } |
165 | 135 |
|
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; |
170 | 146 | } |
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 | | - } |
186 | 147 |
|
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; |
189 | 152 | } |
190 | 153 |
|
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; |
192 | 161 | } |
193 | 162 |
|
194 | 163 | free(messages); |
|
0 commit comments