mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-01 07:40:18 -04:00
Implement macos crashlog (wow this was hard)
This commit is contained in:
parent
fd3fd9536e
commit
9b58b3376b
3 changed files with 280 additions and 27 deletions
loader/src
|
@ -2,19 +2,36 @@
|
|||
|
||||
#ifdef GEODE_IS_MACOS
|
||||
|
||||
#include <Geode/utils/string.hpp>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <ghc/fs_fwd.hpp>
|
||||
#include <execinfo.h>
|
||||
#include <dlfcn.h>
|
||||
#include <cxxabi.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <mach-o/dyld_images.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// https://gist.github.com/jvranish/4441299
|
||||
|
||||
static std::string_view getSignalCodeString(int sig, siginfo_t* siginfo) {
|
||||
switch(sig) {
|
||||
static constexpr size_t FRAME_SIZE = 64;
|
||||
static std::mutex s_mutex;
|
||||
static std::condition_variable s_cv;
|
||||
static int s_signal = 0;
|
||||
static siginfo_t* s_siginfo = nullptr;
|
||||
static ucontext_t* s_context = nullptr;
|
||||
static size_t s_backtraceSize = 0;
|
||||
static std::array<void*, FRAME_SIZE> s_backtrace;
|
||||
|
||||
static std::string_view getSignalCodeString() {
|
||||
switch(s_signal) {
|
||||
case SIGSEGV: return "SIGSEGV: Segmentation Fault";
|
||||
case SIGINT: return "SIGINT: Interactive attention signal, (usually ctrl+c)";
|
||||
case SIGFPE:
|
||||
switch(siginfo->si_code) {
|
||||
switch(s_siginfo->si_code) {
|
||||
case FPE_INTDIV: return "SIGFPE: (integer divide by zero)";
|
||||
case FPE_INTOVF: return "SIGFPE: (integer overflow)";
|
||||
case FPE_FLTDIV: return "SIGFPE: (floating-point divide by zero)";
|
||||
|
@ -26,7 +43,7 @@ static std::string_view getSignalCodeString(int sig, siginfo_t* siginfo) {
|
|||
default: return "SIGFPE: Arithmetic Exception";
|
||||
}
|
||||
case SIGILL:
|
||||
switch(siginfo->si_code) {
|
||||
switch(s_siginfo->si_code) {
|
||||
case ILL_ILLOPC: return "SIGILL: (illegal opcode)";
|
||||
case ILL_ILLOPN: return "SIGILL: (illegal operand)";
|
||||
case ILL_ILLADR: return "SIGILL: (illegal addressing mode)";
|
||||
|
@ -43,45 +60,278 @@ static std::string_view getSignalCodeString(int sig, siginfo_t* siginfo) {
|
|||
}
|
||||
}
|
||||
|
||||
static std::string getInfo(int sig, siginfo_t* siginfo, ucontext_t* context, Mod* faultyMod) {
|
||||
static std::string getImageName(struct dyld_image_info const* image) {
|
||||
if (image == nullptr) {
|
||||
return "<Unknown>";
|
||||
}
|
||||
std::string imageName = image->imageFilePath;
|
||||
if (imageName.empty()) {
|
||||
imageName = "<Unknown>";
|
||||
}
|
||||
return imageName;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/28846503/getting-sizeofimage-and-entrypoint-of-dylib-module
|
||||
size_t getImageSize(struct mach_header_64 const* header) {
|
||||
if (header == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t sz = sizeof(struct mach_header_64); // Size of the header
|
||||
sz += header->sizeofcmds; // Size of the load commands
|
||||
|
||||
auto lc = (struct load_command const*) (header + 1);
|
||||
for (uint32_t i = 0; i < header->ncmds; i++) {
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
sz += ((struct segment_command_64 const*) lc)->vmsize; // Size of segments
|
||||
}
|
||||
lc = (struct load_command const*) ((char *) lc + lc->cmdsize);
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
static std::vector<struct dyld_image_info const*> getAllImages() {
|
||||
std::vector<struct dyld_image_info const*> images;
|
||||
struct task_dyld_info dyldInfo;
|
||||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&dyldInfo, &count) == KERN_SUCCESS) {
|
||||
struct dyld_all_image_infos* imageInfos = (struct dyld_all_image_infos*)dyldInfo.all_image_info_addr;
|
||||
|
||||
for (size_t i = 0; i < imageInfos->infoArrayCount; ++i) {
|
||||
images.push_back(&imageInfos->infoArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
static struct dyld_image_info const* imageFromAddress(void const* addr) {
|
||||
if (addr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto loadedImages = getAllImages();
|
||||
std::sort(loadedImages.begin(), loadedImages.end(), [](auto const a, auto const b) {
|
||||
return (uintptr_t)a->imageLoadAddress < (uintptr_t)b->imageLoadAddress;
|
||||
});
|
||||
auto iter = std::upper_bound(loadedImages.begin(), loadedImages.end(), addr, [](auto const addr, auto const image) {
|
||||
return (uintptr_t)addr < (uintptr_t)image->imageLoadAddress;
|
||||
});
|
||||
|
||||
if (iter == loadedImages.begin()) {
|
||||
return nullptr;
|
||||
}
|
||||
--iter;
|
||||
|
||||
auto image = *iter;
|
||||
// auto imageSize = getImageSize((struct mach_header_64 const*)image->imageLoadAddress);
|
||||
auto imageAddress = (uintptr_t)image->imageLoadAddress;
|
||||
if ((uintptr_t)addr >= imageAddress/* && (uintptr_t)addr < imageAddress + imageSize*/) {
|
||||
return image;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Mod* modFromAddress(void const* addr) {
|
||||
if (addr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto image = imageFromAddress(addr);
|
||||
if (image == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ghc::filesystem::path imagePath = getImageName(image);
|
||||
if (!ghc::filesystem::exists(imagePath)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto geodePath = dirs::getGameDir() / "Frameworks" / "Geode.dylib";
|
||||
if (ghc::filesystem::equivalent(imagePath, geodePath)) {
|
||||
return Mod::get();
|
||||
}
|
||||
|
||||
for (auto& mod : Loader::get()->getAllMods()) {
|
||||
if (!mod->isLoaded() || !ghc::filesystem::exists(mod->getBinaryPath())) {
|
||||
continue;
|
||||
}
|
||||
if (ghc::filesystem::equivalent(imagePath, mod->getBinaryPath())) {
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::string getInfo(void* address, Mod* faultyMod) {
|
||||
std::stringstream stream;
|
||||
void* address = reinterpret_cast<void*>(context->uc_mcontext->__ss.__rip);
|
||||
stream // << "Faulty Lib: " << getModuleName(handleFromAddress(address), true) << "\n"
|
||||
<< "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n"
|
||||
<< "Signal Code: " << std::hex << sig << " (" << getSignalCodeString(sig, siginfo) << ")" << std::dec << "\n";
|
||||
stream << "Faulty Lib: " << getImageName(imageFromAddress(address)) << "\n";
|
||||
stream << "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n";
|
||||
stream << "Instruction Address: " << address << "\n";
|
||||
stream << "Signal Code: " << std::hex << s_signal << " (" << getSignalCodeString() << ")" << std::dec << "\n";
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
extern "C" void signalHandler(int signal, siginfo_t* signal_info, void* vcontext) {
|
||||
extern "C" void signalHandler(int signal, siginfo_t* signalInfo, void* vcontext) {
|
||||
auto context = reinterpret_cast<ucontext_t*>(vcontext);
|
||||
|
||||
static constexpr size_t frameSize = 64;
|
||||
|
||||
auto array = std::array<void*, frameSize>();
|
||||
auto size = backtrace(array.data(), frameSize);
|
||||
s_backtraceSize = backtrace(s_backtrace.data(), FRAME_SIZE);
|
||||
|
||||
// for some reason this is needed, dont ask me why
|
||||
array[2] = reinterpret_cast<void*>(context->uc_mcontext->__ss.__rip);
|
||||
|
||||
if (size < frameSize) {
|
||||
array[size] = nullptr;
|
||||
s_backtrace[2] = reinterpret_cast<void*>(context->uc_mcontext->__ss.__rip);
|
||||
if (s_backtraceSize < FRAME_SIZE) {
|
||||
s_backtrace[s_backtraceSize] = nullptr;
|
||||
}
|
||||
|
||||
auto messages = backtrace_symbols(array.data(), size);
|
||||
if (size < frameSize) {
|
||||
messages[size] = nullptr;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(s_mutex);
|
||||
s_signal = signal;
|
||||
s_siginfo = signalInfo;
|
||||
s_context = context;
|
||||
}
|
||||
|
||||
s_cv.notify_all();
|
||||
std::unique_lock<std::mutex> lock(s_mutex);
|
||||
s_cv.wait(lock, [] { return s_signal == 0; });
|
||||
std::_Exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/8278691/how-to-fix-backtrace-line-number-error-in-c
|
||||
std::string executeCommand(std::string const& cmd) {
|
||||
std::stringstream stream;
|
||||
std::array<char, 1024> buf;
|
||||
|
||||
if (FILE* ptr = popen(cmd.c_str(), "r")) {
|
||||
while (fgets( buf.data(), buf.size(), ptr ) != NULL) {
|
||||
stream << buf.data();
|
||||
}
|
||||
pclose(ptr);
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string addr2Line() {
|
||||
std::stringstream stream;
|
||||
stream << "atos -p " << getpid() << " ";
|
||||
for (int i = 1; i < s_backtraceSize; ++i) {
|
||||
stream << s_backtrace[i] << " ";
|
||||
}
|
||||
// std::cout << "command: " << stream.str() << std::endl;
|
||||
return executeCommand(stream.str());
|
||||
}
|
||||
|
||||
static std::string getStacktrace() {
|
||||
std::stringstream stacktrace;
|
||||
|
||||
auto messages = backtrace_symbols(s_backtrace.data(), s_backtraceSize);
|
||||
if (s_backtraceSize < FRAME_SIZE) {
|
||||
messages[s_backtraceSize] = nullptr;
|
||||
}
|
||||
|
||||
free(messages);
|
||||
std::stringstream lines(addr2Line());
|
||||
|
||||
std::abort();
|
||||
for (int i = 1; i < s_backtraceSize; ++i) {
|
||||
auto message = std::string(messages[i]);
|
||||
|
||||
auto stream = std::stringstream(message);
|
||||
int index;
|
||||
std::string binary;
|
||||
uintptr_t address;
|
||||
std::string function;
|
||||
uintptr_t offset;
|
||||
std::string line;
|
||||
|
||||
stream >> index;
|
||||
|
||||
std::getline(lines, line);
|
||||
std::getline(stream, binary);
|
||||
auto cutoff = binary.find("0x");
|
||||
stream = std::stringstream(binary.substr(cutoff));
|
||||
binary = geode::utils::string::trim(binary.substr(0, cutoff));
|
||||
stream >> std::hex >> address >> std::dec;
|
||||
|
||||
// std::getline(stream, function);
|
||||
// cutoff = function.find("+");
|
||||
// stream = std::stringstream(function.substr(cutoff));
|
||||
// stream >> offset;
|
||||
// function = geode::utils::string::trim(function.substr(0, cutoff));
|
||||
|
||||
// {
|
||||
// int status;
|
||||
// auto demangle = abi::__cxa_demangle(function.c_str(), 0, 0, &status);
|
||||
// if (status == 0) {
|
||||
// function = demangle;
|
||||
// }
|
||||
// free(demangle);
|
||||
// }
|
||||
|
||||
// stacktrace << "- " << binary;
|
||||
stacktrace << " @ " << std::showbase << std::hex << address << std::dec;
|
||||
stacktrace << ": " << line << "\n";
|
||||
// stacktrace << " (" << function << " + " << offset << ")\n";
|
||||
// todo: debug symbols with line number maybe?
|
||||
}
|
||||
|
||||
free(messages);
|
||||
|
||||
return stacktrace.str();
|
||||
}
|
||||
|
||||
static std::string getRegisters() {
|
||||
std::stringstream registers;
|
||||
|
||||
auto context = s_context;
|
||||
auto& ss = context->uc_mcontext->__ss;
|
||||
|
||||
// geez
|
||||
registers << std::showbase << std::hex /*<< std::setfill('0') << std::setw(16) */;
|
||||
registers << "rax: " << ss.__rax << "\n";
|
||||
registers << "rbx: " << ss.__rbx << "\n";
|
||||
registers << "rcx: " << ss.__rcx << "\n";
|
||||
registers << "rdx: " << ss.__rdx << "\n";
|
||||
registers << "rdi: " << ss.__rdi << "\n";
|
||||
registers << "rsi: " << ss.__rsi << "\n";
|
||||
registers << "rbp: " << ss.__rbp << "\n";
|
||||
registers << "rsp: " << ss.__rsp << "\n";
|
||||
registers << "r8: " << ss.__r8 << "\n";
|
||||
registers << "r9: " << ss.__r9 << "\n";
|
||||
registers << "r10: " << ss.__r10 << "\n";
|
||||
registers << "r11: " << ss.__r11 << "\n";
|
||||
registers << "r12: " << ss.__r12 << "\n";
|
||||
registers << "r13: " << ss.__r13 << "\n";
|
||||
registers << "r14: " << ss.__r14 << "\n";
|
||||
registers << "r15: " << ss.__r15 << "\n";
|
||||
registers << "rip: " << ss.__rip << "\n";
|
||||
registers << "rflags: " << ss.__rflags << "\n";
|
||||
registers << "cs: " << ss.__cs << "\n";
|
||||
registers << "fs: " << ss.__fs << "\n";
|
||||
registers << "gs: " << ss.__gs << "\n";
|
||||
|
||||
return registers.str();
|
||||
}
|
||||
|
||||
static void handlerThread() {
|
||||
std::unique_lock<std::mutex> lock(s_mutex);
|
||||
s_cv.wait(lock, [] { return s_signal != 0; });
|
||||
|
||||
auto signalAddress = reinterpret_cast<void*>(s_context->uc_mcontext->__ss.__rip);
|
||||
Mod* faultyMod = nullptr;
|
||||
for (int i = 1; i < s_backtraceSize; ++i) {
|
||||
auto mod = modFromAddress(s_backtrace[i]);
|
||||
if (mod != nullptr) {
|
||||
faultyMod = mod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
|
||||
|
||||
s_signal = 0;
|
||||
s_cv.notify_all();
|
||||
}
|
||||
|
||||
static bool s_lastLaunchCrashed;
|
||||
|
||||
bool crashlog::setupPlatformHandler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = signalHandler;
|
||||
action.sa_sigaction = &signalHandler;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGSEGV, &action, nullptr);
|
||||
|
@ -92,7 +342,8 @@ bool crashlog::setupPlatformHandler() {
|
|||
sigaction(SIGTERM, &action, nullptr);
|
||||
sigaction(SIGABRT, &action, nullptr);
|
||||
|
||||
|
||||
std::thread(&handlerThread).detach();
|
||||
|
||||
auto lastCrashedFile = crashlog::getCrashLogDirectory() / "last-crashed";
|
||||
if (ghc::filesystem::exists(lastCrashedFile)) {
|
||||
s_lastLaunchCrashed = true;
|
||||
|
|
|
@ -144,7 +144,7 @@ static void printAddr(std::ostream& stream, void const* addr, bool fullPath = tr
|
|||
}
|
||||
|
||||
// https://stackoverflow.com/a/50208684/9124836
|
||||
static std::string walkStack(PCONTEXT context) {
|
||||
static std::string getStacktrace(PCONTEXT context) {
|
||||
std::stringstream stream;
|
||||
STACKFRAME64 stack;
|
||||
memset(&stack, 0, sizeof(STACKFRAME64));
|
||||
|
|
|
@ -233,6 +233,8 @@ bool ModCell::init(
|
|||
if (!ModListCell::init(list, size))
|
||||
return false;
|
||||
|
||||
__builtin_trap();
|
||||
|
||||
m_mod = mod;
|
||||
|
||||
this->setupInfo(mod->getModInfo(), false, display);
|
||||
|
|
Loading…
Add table
Reference in a new issue