#include "Logger.h" #include "String.h" #include "Platform.h" #include "Window.h" #include "Funcs.h" #include "Stream.h" #include "Errors.h" #include "Utils.h" #if defined CC_BUILD_WEB /* Can't see native CPU state with javascript */ #elif defined CC_BUILD_WIN #define WIN32_LEAN_AND_MEAN #define NOSERVICE #define NOMCX #define NOIME #define CUR_PROCESS_HANDLE ((HANDLE)-1) /* GetCurrentProcess() always returns -1 */ #include #include static HANDLE curProcess = CUR_PROCESS_HANDLE; #elif defined CC_BUILD_OPENBSD || defined CC_BUILD_HAIKU || defined CC_BUILD_SERENITY #include /* These operating systems don't provide sys/ucontext.h */ /* But register constants be found from includes in */ #elif defined CC_BUILD_OS2 #include #include <386/ucontext.h> #elif defined CC_BUILD_LINUX || defined CC_BUILD_ANDROID /* Need to define this to get REG_ constants */ #define _GNU_SOURCE #include #include #elif defined CC_BUILD_POSIX #include #include #endif #ifdef CC_BUILD_DARWIN /* Need this to detect macOS < 10.4, and switch to NS* api instead if so */ #include #endif /* Only show up to 50 frames in backtrace */ #define MAX_BACKTRACE_FRAMES 50 static void AbortCommon(cc_result result, const char* raw_msg, void* ctx); /*########################################################################################################################* *----------------------------------------------------------Warning--------------------------------------------------------* *#########################################################################################################################*/ void Logger_DialogWarn(const cc_string* msg) { cc_string dst; char dstBuffer[512]; String_InitArray_NT(dst, dstBuffer); String_Copy(&dst, msg); dst.buffer[dst.length] = '\0'; Window_ShowDialog(Logger_DialogTitle, dst.buffer); } const char* Logger_DialogTitle = "Error"; Logger_DoWarn Logger_WarnFunc = Logger_DialogWarn; /* Returns a description for some ClassiCube specific error codes */ static const char* GetCCErrorDesc(cc_result res) { switch (res) { case ERR_END_OF_STREAM: return "End of stream"; case ERR_NOT_SUPPORTED: return "Operation not supported"; case ERR_INVALID_ARGUMENT: return "Invalid argument"; case ERR_OUT_OF_MEMORY: return "Out of memory"; case OGG_ERR_INVALID_SIG: return "Only OGG music files supported"; case OGG_ERR_VERSION: return "Invalid OGG format version"; case AUDIO_ERR_MP3_SIG: return "MP3 audio files are not supported"; case WAV_ERR_STREAM_HDR: return "Only WAV sound files supported"; case WAV_ERR_STREAM_TYPE: return "Invalid WAV type"; case WAV_ERR_DATA_TYPE: return "Unsupported WAV audio format"; case ZIP_ERR_TOO_MANY_ENTRIES: return "Cannot load .zip files with over 1024 entries"; case PNG_ERR_INVALID_SIG: return "Only PNG images supported"; case PNG_ERR_INVALID_HDR_SIZE: return "Invalid PNG header size"; case PNG_ERR_TOO_WIDE: return "PNG image too wide"; case PNG_ERR_TOO_TALL: return "PNG image too tall"; case PNG_ERR_INTERLACED: return "Interlaced PNGs unsupported"; case PNG_ERR_REACHED_IEND: return "Incomplete PNG image data"; case PNG_ERR_NO_DATA: return "No image in PNG"; case PNG_ERR_INVALID_SCANLINE: return "Invalid PNG scanline type"; case PNG_ERR_16BITSAMPLES: return "16 bpp PNGs unsupported"; case NBT_ERR_UNKNOWN: return "Unknown NBT tag type"; case CW_ERR_ROOT_TAG: return "Invalid root NBT tag"; case CW_ERR_STRING_LEN: return "NBT string too long"; case ERR_DOWNLOAD_INVALID: return "Website denied download or doesn't exist"; case ERR_NO_AUDIO_OUTPUT: return "No audio output devices plugged in"; case ERR_INVALID_DATA_URL: return "Cannot download from invalid URL"; case ERR_INVALID_OPEN_URL: return "Cannot navigate to invalid URL"; case NBT_ERR_EXPECTED_I8: return "Expected Int8 NBT tag"; case NBT_ERR_EXPECTED_I16: return "Expected Int16 NBT tag"; case NBT_ERR_EXPECTED_I32: return "Expected Int32 NBT tag"; case NBT_ERR_EXPECTED_F32: return "Expected Float32 NBT tag"; case NBT_ERR_EXPECTED_STR: return "Expected String NBT tag"; case NBT_ERR_EXPECTED_ARR: return "Expected ByteArray NBT tag"; case NBT_ERR_ARR_TOO_SMALL:return "ByteArray NBT tag too small"; case HTTP_ERR_NO_SSL: return "HTTPS URLs are not currently supported"; case SOCK_ERR_UNKNOWN_HOST: return "Host could not be resolved to an IP address"; } return NULL; } /* Appends more detailed information about an error if possible */ static void AppendErrorDesc(cc_string* msg, cc_result res, Logger_DescribeError describeErr) { const char* cc_err; cc_string err; char errBuffer[128]; String_InitArray(err, errBuffer); cc_err = GetCCErrorDesc(res); if (cc_err) { String_Format1(msg, "\n Error meaning: %c", cc_err); } else if (describeErr(res, &err)) { String_Format1(msg, "\n Error meaning: %s", &err); } } void Logger_FormatWarn(cc_string* msg, cc_result res, const char* action, Logger_DescribeError describeErr) { String_Format2(msg, "Error %e when %c", &res, action); AppendErrorDesc(msg, res, describeErr); } void Logger_FormatWarn2(cc_string* msg, cc_result res, const char* action, const cc_string* path, Logger_DescribeError describeErr) { String_Format3(msg, "Error %e when %c '%s'", &res, action, path); AppendErrorDesc(msg, res, describeErr); } static cc_bool DescribeSimple(cc_result res, cc_string* dst) { return false; } void Logger_SimpleWarn(cc_result res, const char* action) { Logger_Warn(res, action, DescribeSimple); } void Logger_SimpleWarn2(cc_result res, const char* action, const cc_string* path) { Logger_Warn2(res, action, path, DescribeSimple); } void Logger_Warn(cc_result res, const char* action, Logger_DescribeError describeErr) { cc_string msg; char msgBuffer[256]; String_InitArray(msg, msgBuffer); Logger_FormatWarn(&msg, res, action, describeErr); Logger_WarnFunc(&msg); } void Logger_Warn2(cc_result res, const char* action, const cc_string* path, Logger_DescribeError describeErr) { cc_string msg; char msgBuffer[256]; String_InitArray(msg, msgBuffer); Logger_FormatWarn2(&msg, res, action, path, describeErr); Logger_WarnFunc(&msg); } void Logger_DynamicLibWarn(const char* action, const cc_string* path) { cc_string err; char errBuffer[128]; cc_string msg; char msgBuffer[256]; String_InitArray(msg, msgBuffer); String_InitArray(err, errBuffer); String_Format2(&msg, "Error %c '%s'", action, path); if (DynamicLib_DescribeError(&err)) { String_Format1(&msg, ":\n %s", &err); } Logger_WarnFunc(&msg); } void Logger_SysWarn(cc_result res, const char* action) { Logger_Warn(res, action, Platform_DescribeError); } void Logger_SysWarn2(cc_result res, const char* action, const cc_string* path) { Logger_Warn2(res, action, path, Platform_DescribeError); } /*########################################################################################################################* *------------------------------------------------------Frame dumping------------------------------------------------------* *#########################################################################################################################*/ static void PrintFrame(cc_string* str, cc_uintptr addr, cc_uintptr symAddr, const char* symName, const char* modName) { cc_string module; int offset; if (!modName) modName = "???"; module = String_FromReadonly(modName); Utils_UNSAFE_GetFilename(&module); String_Format2(str, "%x - %s", &addr, &module); if (symName && symName[0]) { offset = (int)(addr - symAddr); String_Format2(str, "(%c+%i)" _NL, symName, &offset); } else { String_AppendConst(str, _NL); } } #if defined CC_BUILD_WIN struct SymbolAndName { IMAGEHLP_SYMBOL symbol; char name[256]; }; static void DumpFrame(HANDLE process, cc_string* trace, cc_uintptr addr) { char strBuffer[512]; cc_string str; String_InitArray(str, strBuffer); struct SymbolAndName s = { 0 }; s.symbol.MaxNameLength = 255; s.symbol.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); SymGetSymFromAddr(process, addr, NULL, &s.symbol); IMAGEHLP_MODULE m = { 0 }; m.SizeOfStruct = sizeof(IMAGEHLP_MODULE); SymGetModuleInfo(process, addr, &m); PrintFrame(&str, addr, s.symbol.Address, s.symbol.Name, m.ModuleName); String_AppendString(trace, &str); /* This function only works for .pdb debug info anyways */ /* This function is also missing on Windows 9X */ #if _MSC_VER IMAGEHLP_LINE line = { 0 }; DWORD lineOffset; line.SizeOfStruct = sizeof(IMAGEHLP_LINE); if (SymGetLineFromAddr(process, addr, &lineOffset, &line)) { String_Format2(&str, " line %i in %c\r\n", &line.LineNumber, line.FileName); } #endif Logger_Log(&str); } #elif defined MAC_OS_X_VERSION_MIN_REQUIRED && (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* dladdr does not exist prior to macOS tiger */ static void DumpFrame(cc_string* trace, void* addr) { cc_string str; char strBuffer[384]; String_InitArray(str, strBuffer); /* alas NSModuleForSymbol doesn't work with raw addresses */ PrintFrame(&str, (cc_uintptr)addr, 0, NULL, NULL); String_AppendString(trace, &str); Logger_Log(&str); } #elif defined CC_BUILD_IRIX /* IRIX doesn't expose a nice interface for dladdr */ static void DumpFrame(cc_string* trace, void* addr) { cc_uintptr addr_ = (cc_uintptr)addr; String_Format1(trace, "%x", &addr_); } #elif defined CC_BUILD_POSIX && !defined CC_BUILD_OS2 /* need to define __USE_GNU for dladdr */ #ifndef __USE_GNU #define __USE_GNU #endif #include #undef __USE_GNU static void DumpFrame(cc_string* trace, void* addr) { cc_string str; char strBuffer[384]; Dl_info s; String_InitArray(str, strBuffer); s.dli_sname = NULL; s.dli_fname = NULL; dladdr(addr, &s); PrintFrame(&str, (cc_uintptr)addr, (cc_uintptr)s.dli_saddr, s.dli_sname, s.dli_fname); String_AppendString(trace, &str); Logger_Log(&str); } #else /* No backtrace support implemented */ #endif /*########################################################################################################################* *-------------------------------------------------------Backtracing-------------------------------------------------------* *#########################################################################################################################*/ #if defined CC_BUILD_WIN /* This callback function is used so stack Walking works using StackWalk properly on Windows 9x: */ /* - on Windows 9x process ID is passed instead of process handle as the "process" argument */ /* - the SymXYZ functions expect a process ID on Windows 9x, so that works fine */ /* - if NULL is passed as the "ReadMemory" argument, the default callback using ReadProcessMemory is used */ /* - however, ReadProcessMemory expects a process handle, and so that will fail since it's given a process ID */ /* So to work around this, instead manually call ReadProcessMemory with the current process handle */ static BOOL __stdcall ReadMemCallback(HANDLE process, DWORD_PTR baseAddress, PVOID buffer, DWORD size, PDWORD numBytesRead) { SIZE_T numRead = 0; BOOL ok = ReadProcessMemory(CUR_PROCESS_HANDLE, (LPCVOID)baseAddress, buffer, size, &numRead); *numBytesRead = (DWORD)numRead; /* DWORD always 32 bits */ return ok; } static cc_uintptr spRegister; static int GetFrames(CONTEXT* ctx, cc_uintptr* addrs, int max) { STACKFRAME frame = { 0 }; int count, type; HANDLE thread; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat; #if defined _M_IX86 type = IMAGE_FILE_MACHINE_I386; frame.AddrPC.Offset = ctx->Eip; frame.AddrFrame.Offset = ctx->Ebp; frame.AddrStack.Offset = ctx->Esp; spRegister = ctx->Esp; #elif defined _M_X64 type = IMAGE_FILE_MACHINE_AMD64; frame.AddrPC.Offset = ctx->Rip; frame.AddrFrame.Offset = ctx->Rsp; frame.AddrStack.Offset = ctx->Rsp; spRegister = ctx->Rsp; #else /* Always available after XP, so use that */ return RtlCaptureStackBackTrace(0, max, (void**)addrs, NULL); #endif thread = GetCurrentThread(); for (count = 0; count < max; count++) { if (!StackWalk(type, curProcess, thread, &frame, ctx, ReadMemCallback, SymFunctionTableAccess, SymGetModuleBase, NULL)) break; if (!frame.AddrFrame.Offset) break; addrs[count] = frame.AddrPC.Offset; } return count; } void Logger_Backtrace(cc_string* trace, void* ctx) { cc_uintptr addrs[MAX_BACKTRACE_FRAMES]; int i, frames; SymInitialize(curProcess, NULL, TRUE); /* TODO only in MSVC.. */ frames = GetFrames((CONTEXT*)ctx, addrs, MAX_BACKTRACE_FRAMES); for (i = 0; i < frames; i++) { DumpFrame(curProcess, trace, addrs[i]); } String_AppendConst(trace, _NL); } #elif defined CC_BUILD_ANDROID #define CC_BACKTRACE_UNWIND #elif defined CC_BUILD_DARWIN /* backtrace is only available on macOS since 10.5 */ void Logger_Backtrace(cc_string* trace, void* ctx) { void* addrs[MAX_BACKTRACE_FRAMES]; unsigned i, frames; /* See lldb/tools/debugserver/source/MacOSX/stack_logging.h */ /* backtrace uses this internally too, and exists since macOS 10.1 */ extern void thread_stack_pcs(void** buffer, unsigned max, unsigned* nb); thread_stack_pcs(addrs, MAX_BACKTRACE_FRAMES, &frames); /* Skip frames don't want to include in backtrace */ /* frame 0 = thread_stack_pcs */ /* frame 1 = Logger_Backtrace */ for (i = 2; i < frames; i++) { DumpFrame(trace, addrs[i]); } String_AppendConst(trace, _NL); } #elif defined CC_BUILD_SERENITY void Logger_Backtrace(cc_string* trace, void* ctx) { String_AppendConst(trace, "-- backtrace unimplemented --"); /* TODO: Backtrace using LibSymbolication */ } #elif defined CC_BUILD_IRIX void Logger_Backtrace(cc_string* trace, void* ctx) { String_AppendConst(trace, "-- backtrace unimplemented --"); /* TODO implement backtrace using exc_unwind https://nixdoc.net/man-pages/IRIX/man3/exception.3.html */ } #elif defined CC_BACKTRACE_BUILTIN /* Implemented later at end of the file */ #elif defined CC_BUILD_POSIX && defined _GLIBC_ #include void Logger_Backtrace(cc_string* trace, void* ctx) { void* addrs[MAX_BACKTRACE_FRAMES]; int i, frames = backtrace(addrs, MAX_BACKTRACE_FRAMES); for (i = 0; i < frames; i++) { DumpFrame(trace, addrs[i]); } String_AppendConst(trace, _NL); } #elif defined CC_BUILD_POSIX /* musl etc - rely on unwind from GCC instead */ #define CC_BACKTRACE_UNWIND #else void Logger_Backtrace(cc_string* trace, void* ctx) { } #endif /*########################################################################################################################* *-----------------------------------------------------CPU registers-------------------------------------------------------* *#########################################################################################################################*/ /* Unfortunately, operating systems vary wildly in how they name and access registers for dumping */ /* So this is the simplest way to avoid duplicating code on each platform */ #define Dump_X86() \ String_Format3(str, "eax=%x ebx=%x ecx=%x" _NL, REG_GET(ax,AX), REG_GET(bx,BX), REG_GET(cx,CX));\ String_Format3(str, "edx=%x esi=%x edi=%x" _NL, REG_GET(dx,DX), REG_GET(si,SI), REG_GET(di,DI));\ String_Format3(str, "eip=%x ebp=%x esp=%x" _NL, REG_GET(ip,IP), REG_GET(bp,BP), REG_GET(sp,SP)); #define Dump_X64() \ String_Format3(str, "rax=%x rbx=%x rcx=%x" _NL, REG_GET(ax,AX), REG_GET(bx,BX), REG_GET(cx,CX));\ String_Format3(str, "rdx=%x rsi=%x rdi=%x" _NL, REG_GET(dx,DX), REG_GET(si,SI), REG_GET(di,DI));\ String_Format3(str, "rip=%x rbp=%x rsp=%x" _NL, REG_GET(ip,IP), REG_GET(bp,BP), REG_GET(sp,SP));\ String_Format3(str, "r8 =%x r9 =%x r10=%x" _NL, REG_GET(8,8), REG_GET(9,9), REG_GET(10,10));\ String_Format3(str, "r11=%x r12=%x r13=%x" _NL, REG_GET(11,11), REG_GET(12,12), REG_GET(13,13));\ String_Format2(str, "r14=%x r15=%x" _NL, REG_GET(14,14), REG_GET(15,15)); #define Dump_PPC() \ String_Format4(str, "r0 =%x r1 =%x r2 =%x r3 =%x" _NL, REG_GNUM(0), REG_GNUM(1), REG_GNUM(2), REG_GNUM(3)); \ String_Format4(str, "r4 =%x r5 =%x r6 =%x r7 =%x" _NL, REG_GNUM(4), REG_GNUM(5), REG_GNUM(6), REG_GNUM(7)); \ String_Format4(str, "r8 =%x r9 =%x r10=%x r11=%x" _NL, REG_GNUM(8), REG_GNUM(9), REG_GNUM(10), REG_GNUM(11)); \ String_Format4(str, "r12=%x r13=%x r14=%x r15=%x" _NL, REG_GNUM(12), REG_GNUM(13), REG_GNUM(14), REG_GNUM(15)); \ String_Format4(str, "r16=%x r17=%x r18=%x r19=%x" _NL, REG_GNUM(16), REG_GNUM(17), REG_GNUM(18), REG_GNUM(19)); \ String_Format4(str, "r20=%x r21=%x r22=%x r23=%x" _NL, REG_GNUM(20), REG_GNUM(21), REG_GNUM(22), REG_GNUM(23)); \ String_Format4(str, "r24=%x r25=%x r26=%x r27=%x" _NL, REG_GNUM(24), REG_GNUM(25), REG_GNUM(26), REG_GNUM(27)); \ String_Format4(str, "r28=%x r29=%x r30=%x r31=%x" _NL, REG_GNUM(28), REG_GNUM(29), REG_GNUM(30), REG_GNUM(31)); \ String_Format3(str, "pc =%x lr =%x ctr=%x" _NL, REG_GET_PC(), REG_GET_LR(), REG_GET_CTR()); #define Dump_ARM32() \ String_Format3(str, "r0 =%x r1 =%x r2 =%x" _NL, REG_GNUM(0), REG_GNUM(1), REG_GNUM(2));\ String_Format3(str, "r3 =%x r4 =%x r5 =%x" _NL, REG_GNUM(3), REG_GNUM(4), REG_GNUM(5));\ String_Format3(str, "r6 =%x r7 =%x r8 =%x" _NL, REG_GNUM(6), REG_GNUM(7), REG_GNUM(8));\ String_Format3(str, "r9 =%x r10=%x fp =%x" _NL, REG_GNUM(9), REG_GNUM(10), REG_GET_FP());\ String_Format3(str, "ip =%x sp =%x lr =%x" _NL, REG_GET_IP(), REG_GET_SP(), REG_GET_LR());\ String_Format1(str, "pc =%x" _NL, REG_GET_PC()); #define Dump_ARM64() \ String_Format4(str, "r0 =%x r1 =%x r2 =%x r3 =%x" _NL, REG_GNUM(0), REG_GNUM(1), REG_GNUM(2), REG_GNUM(3)); \ String_Format4(str, "r4 =%x r5 =%x r6 =%x r7 =%x" _NL, REG_GNUM(4), REG_GNUM(5), REG_GNUM(6), REG_GNUM(7)); \ String_Format4(str, "r8 =%x r9 =%x r10=%x r11=%x" _NL, REG_GNUM(8), REG_GNUM(9), REG_GNUM(10), REG_GNUM(11)); \ String_Format4(str, "r12=%x r13=%x r14=%x r15=%x" _NL, REG_GNUM(12), REG_GNUM(13), REG_GNUM(14), REG_GNUM(15)); \ String_Format4(str, "r16=%x r17=%x r18=%x r19=%x" _NL, REG_GNUM(16), REG_GNUM(17), REG_GNUM(18), REG_GNUM(19)); \ String_Format4(str, "r20=%x r21=%x r22=%x r23=%x" _NL, REG_GNUM(20), REG_GNUM(21), REG_GNUM(22), REG_GNUM(23)); \ String_Format4(str, "r24=%x r25=%x r26=%x r27=%x" _NL, REG_GNUM(24), REG_GNUM(25), REG_GNUM(26), REG_GNUM(27)); \ String_Format4(str, "r28=%x fp =%x lr =%x sp =%x" _NL, REG_GNUM(28), REG_GET_FP(), REG_GET_LR(), REG_GET_SP()); \ String_Format1(str, "pc =%x" _NL, REG_GET_PC()); #define Dump_Alpha() \ String_Format4(str, "v0 =%x t0 =%x t1 =%x t2 =%x" _NL, REG_GNUM(0), REG_GNUM(1), REG_GNUM(2), REG_GNUM(3)); \ String_Format4(str, "t3 =%x t4 =%x t5 =%x t6 =%x" _NL, REG_GNUM(4), REG_GNUM(5), REG_GNUM(6), REG_GNUM(7)); \ String_Format4(str, "t7 =%x s0 =%x s1 =%x s2 =%x" _NL, REG_GNUM(8), REG_GNUM(9), REG_GNUM(10), REG_GNUM(11)); \ String_Format4(str, "s3 =%x s4 =%x s5 =%x a0 =%x" _NL, REG_GNUM(12), REG_GNUM(13), REG_GNUM(14), REG_GNUM(16)); \ String_Format4(str, "a1 =%x a2 =%x a3 =%x a4 =%x" _NL, REG_GNUM(17), REG_GNUM(18), REG_GNUM(19), REG_GNUM(20)); \ String_Format4(str, "a5 =%x t8 =%x t9 =%x t10=%x" _NL, REG_GNUM(21), REG_GNUM(22), REG_GNUM(23), REG_GNUM(24)); \ String_Format4(str, "t11=%x ra =%x pv =%x at =%x" _NL, REG_GNUM(25), REG_GNUM(26), REG_GNUM(27), REG_GNUM(28)); \ String_Format4(str, "gp =%x fp =%x sp =%x pc =%x" _NL, REG_GNUM(29), REG_GET_FP(), REG_GET_SP(), REG_GET_PC()); #define Dump_SPARC() \ String_Format4(str, "o0=%x o1=%x o2=%x o3=%x" _NL, REG_GET(o0,O0), REG_GET(o1,O1), REG_GET(o2,O2), REG_GET(o3,O3)); \ String_Format4(str, "o4=%x o5=%x o6=%x o7=%x" _NL, REG_GET(o4,O4), REG_GET(o5,O5), REG_GET(o6,O6), REG_GET(o7,O7)); \ String_Format4(str, "g1=%x g2=%x g3=%x g4=%x" _NL, REG_GET(g1,G1), REG_GET(g2,G2), REG_GET(g3,G3), REG_GET(g4,G4)); \ String_Format4(str, "g5=%x g6=%x g7=%x y =%x" _NL, REG_GET(g5,G5), REG_GET(g6,G6), REG_GET(g7,G7), REG_GET( y, Y)); \ String_Format2(str, "pc=%x nc=%x" _NL, REG_GET(pc,PC), REG_GET(npc,nPC)); #define Dump_RISCV() \ String_Format4(str, "pc =%x r1 =%x r2 =%x r3 =%x" _NL, REG_GET_PC(), REG_GNUM(1), REG_GNUM(2), REG_GNUM(3)); \ String_Format4(str, "r4 =%x r5 =%x r6 =%x r7 =%x" _NL, REG_GNUM(4), REG_GNUM(5), REG_GNUM(6), REG_GNUM(7)); \ String_Format4(str, "r8 =%x r9 =%x r10=%x r11=%x" _NL, REG_GNUM(8), REG_GNUM(9), REG_GNUM(10), REG_GNUM(11)); \ String_Format4(str, "r12=%x r13=%x r14=%x r15=%x" _NL, REG_GNUM(12), REG_GNUM(13), REG_GNUM(14), REG_GNUM(15)); \ String_Format4(str, "r16=%x r17=%x r18=%x r19=%x" _NL, REG_GNUM(16), REG_GNUM(17), REG_GNUM(18), REG_GNUM(19)); \ String_Format4(str, "r20=%x r21=%x r22=%x r23=%x" _NL, REG_GNUM(20), REG_GNUM(21), REG_GNUM(22), REG_GNUM(23)); \ String_Format4(str, "r24=%x r25=%x r26=%x r27=%x" _NL, REG_GNUM(24), REG_GNUM(25), REG_GNUM(26), REG_GNUM(27)); \ String_Format4(str, "r28=%x r29=%x r30=%x r31=%x" _NL, REG_GNUM(28), REG_GNUM(29), REG_GNUM(30), REG_GNUM(31)); #define Dump_MIPS() \ String_Format4(str, "r0 =%x r1 =%x r2 =%x r3 =%x" _NL, REG_GNUM(0), REG_GNUM(1), REG_GNUM(2), REG_GNUM(3)); \ String_Format4(str, "r4 =%x r5 =%x r6 =%x r7 =%x" _NL, REG_GNUM(4), REG_GNUM(5), REG_GNUM(6), REG_GNUM(7)); \ String_Format4(str, "r8 =%x r9 =%x r10=%x r11=%x" _NL, REG_GNUM(8), REG_GNUM(9), REG_GNUM(10), REG_GNUM(11)); \ String_Format4(str, "r12=%x r13=%x r14=%x r15=%x" _NL, REG_GNUM(12), REG_GNUM(13), REG_GNUM(14), REG_GNUM(15)); \ String_Format4(str, "r16=%x r17=%x r18=%x r19=%x" _NL, REG_GNUM(16), REG_GNUM(17), REG_GNUM(18), REG_GNUM(19)); \ String_Format4(str, "r20=%x r21=%x r22=%x r23=%x" _NL, REG_GNUM(20), REG_GNUM(21), REG_GNUM(22), REG_GNUM(23)); \ String_Format4(str, "r24=%x r25=%x r26=%x r27=%x" _NL, REG_GNUM(24), REG_GNUM(25), REG_GNUM(26), REG_GNUM(27)); \ String_Format4(str, "r28=%x sp =%x fp =%x ra =%x" _NL, REG_GNUM(28), REG_GNUM(29), REG_GNUM(30), REG_GNUM(31)); \ String_Format3(str, "pc =%x lo =%x hi =%x" _NL, REG_GET_PC(), REG_GET_LO(), REG_GET_HI()); #if defined CC_BUILD_WIN /* See CONTEXT in WinNT.h */ static void PrintRegisters(cc_string* str, void* ctx) { CONTEXT* r = (CONTEXT*)ctx; #if defined _M_IX86 #define REG_GET(reg, ign) &r->E ## reg Dump_X86() #elif defined _M_X64 #define REG_GET(reg, ign) &r->R ## reg Dump_X64() #elif defined _M_ARM #define REG_GNUM(num) &r->R ## num #define REG_GET_FP() &r->R11 #define REG_GET_IP() &r->R12 #define REG_GET_SP() &r->Sp #define REG_GET_LR() &r->Lr #define REG_GET_PC() &r->Pc Dump_ARM32() #elif defined _M_ARM64 #define REG_GNUM(num) &r->X[num] #define REG_GET_FP() &r->Fp #define REG_GET_LR() &r->Lr #define REG_GET_SP() &r->Sp #define REG_GET_PC() &r->Pc Dump_ARM64() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_DARWIN && __DARWIN_UNIX03 /* See /usr/include/mach/i386/_structs.h (macOS 10.5+) */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(reg, ign) &r->__ss.__e##reg Dump_X86() #elif defined __x86_64__ #define REG_GET(reg, ign) &r->__ss.__r##reg Dump_X64() #elif defined __aarch64__ #define REG_GNUM(num) &r->__ss.__x[num] #define REG_GET_FP() &r->__ss.__fp #define REG_GET_LR() &r->__ss.__lr #define REG_GET_SP() &r->__ss.__sp #define REG_GET_PC() &r->__ss.__pc Dump_ARM64() #elif defined __arm__ #define REG_GNUM(num) &r->__ss.__r[num] #define REG_GET_FP() &r->__ss.__r[11] #define REG_GET_IP() &r->__ss.__r[12] #define REG_GET_SP() &r->__ss.__sp #define REG_GET_LR() &r->__ss.__lr #define REG_GET_PC() &r->__ss.__pc Dump_ARM32() #elif defined __ppc__ #define REG_GNUM(num) &r->__ss.__r##num #define REG_GET_PC() &r->__ss.__srr0 #define REG_GET_LR() &r->__ss.__lr #define REG_GET_CTR() &r->__ss.__ctr Dump_PPC() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_DARWIN /* See /usr/include/mach/i386/thread_status.h (macOS 10.4) */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(reg, ign) &r->ss.e##reg Dump_X86() #elif defined __x86_64__ #define REG_GET(reg, ign) &r->ss.r##reg Dump_X64() #elif defined __ppc__ #define REG_GNUM(num) &r->ss.r##num #define REG_GET_PC() &r->ss.srr0 #define REG_GET_LR() &r->ss.lr #define REG_GET_CTR() &r->ss.ctr Dump_PPC() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_LINUX || defined CC_BUILD_ANDROID /* See /usr/include/sys/ucontext.h */ static void PrintRegisters(cc_string* str, void* ctx) { #if __PPC__ && __WORDSIZE == 32 /* See sysdeps/unix/sysv/linux/powerpc/sys/ucontext.h in glibc */ mcontext_t r = *((ucontext_t*)ctx)->uc_mcontext.uc_regs; #else mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; #endif #if defined __i386__ #define REG_GET(ign, reg) &r.gregs[REG_E##reg] Dump_X86() #elif defined __x86_64__ #define REG_GET(ign, reg) &r.gregs[REG_R##reg] Dump_X64() #elif defined __aarch64__ #define REG_GNUM(num) &r.regs[num] #define REG_GET_FP() &r.regs[29] #define REG_GET_LR() &r.regs[30] #define REG_GET_SP() &r.sp #define REG_GET_PC() &r.pc Dump_ARM64() #elif defined __arm__ #define REG_GNUM(num) &r.arm_r##num #define REG_GET_FP() &r.arm_fp #define REG_GET_IP() &r.arm_ip #define REG_GET_SP() &r.arm_sp #define REG_GET_LR() &r.arm_lr #define REG_GET_PC() &r.arm_pc Dump_ARM32() #elif defined __alpha__ #define REG_GNUM(num) &r.sc_regs[num] #define REG_GET_FP() &r.sc_regs[15] #define REG_GET_PC() &r.sc_pc #define REG_GET_SP() &r.sc_regs[30] Dump_Alpha() #elif defined __sparc__ #define REG_GET(ign, reg) &r.gregs[REG_##reg] Dump_SPARC() #elif defined __PPC__ && __WORDSIZE == 32 #define REG_GNUM(num) &r.gregs[num] #define REG_GET_PC() &r.gregs[32] #define REG_GET_LR() &r.gregs[35] #define REG_GET_CTR() &r.gregs[34] Dump_PPC() #elif defined __PPC__ #define REG_GNUM(num) &r.gp_regs[num] #define REG_GET_PC() &r.gp_regs[32] /* TODO this might be wrong, compare with PT_LNK in */ /* https://elixir.bootlin.com/linux/v4.19.122/source/arch/powerpc/include/uapi/asm/ptrace.h#L102 */ #define REG_GET_LR() &r.gp_regs[35] #define REG_GET_CTR() &r.gp_regs[34] Dump_PPC() #elif defined __mips__ #define REG_GNUM(num) &r.gregs[num] #define REG_GET_PC() &r.pc #define REG_GET_LO() &r.mdlo #define REG_GET_HI() &r.mdhi Dump_MIPS() #elif defined __riscv #define REG_GNUM(num) &r.__gregs[num] #define REG_GET_PC() &r.__gregs[REG_PC] Dump_RISCV() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_SOLARIS /* See /usr/include/sys/regset.h */ /* -> usr/src/uts/[ARCH]/sys/mcontext.h */ /* -> usr/src/uts/[ARCH]/sys/regset.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t* r = &((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(ign, reg) &r->gregs[E##reg] Dump_X86() #elif defined __x86_64__ #define REG_GET(ign, reg) &r->gregs[REG_R##reg] Dump_X64() #elif defined __sparc__ #define REG_GET(ign, reg) &r->gregs[REG_##reg] Dump_SPARC() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_NETBSD /* See /usr/include/[ARCH]/mcontext.h */ /* -> src/sys/arch/[ARCH]/include/mcontext.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t* r = &((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(ign, reg) &r->__gregs[_REG_E##reg] Dump_X86() #elif defined __x86_64__ #define REG_GET(ign, reg) &r->__gregs[_REG_R##reg] Dump_X64() #elif defined __aarch64__ #define REG_GNUM(num) &r->__gregs[num] #define REG_GET_FP() &r->__gregs[_REG_FP] #define REG_GET_LR() &r->__gregs[_REG_LR] #define REG_GET_SP() &r->__gregs[_REG_SP] #define REG_GET_PC() &r->__gregs[_REG_PC] Dump_ARM64() #elif defined __arm__ #define REG_GNUM(num) &r->__gregs[num] #define REG_GET_FP() &r->__gregs[_REG_FP] #define REG_GET_IP() &r->__gregs[12] #define REG_GET_SP() &r->__gregs[_REG_SP] #define REG_GET_LR() &r->__gregs[_REG_LR] #define REG_GET_PC() &r->__gregs[_REG_PC] Dump_ARM32() #elif defined __powerpc__ #define REG_GNUM(num) &r->__gregs[num] #define REG_GET_PC() &r->__gregs[_REG_PC] #define REG_GET_LR() &r->__gregs[_REG_LR] #define REG_GET_CTR() &r->__gregs[_REG_CTR] Dump_PPC() #elif defined __mips__ #define REG_GNUM(num) &r->__gregs[num] #define REG_GET_PC() &r->__gregs[_REG_EPC] #define REG_GET_LO() &r->__gregs[_REG_MDLO] #define REG_GET_HI() &r->__gregs[_REG_MDHI] Dump_MIPS() #elif defined __riscv #define REG_GNUM(num) &r->__gregs[num] #define REG_GET_PC() &r->__gregs[_REG_PC] Dump_RISCV() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_FREEBSD /* See /usr/include/machine/ucontext.h */ /* -> src/sys/[ARCH]/include/ucontext.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t* r = &((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(reg, ign) &r->mc_e##reg Dump_X86() #elif defined __x86_64__ #define REG_GET(reg, ign) &r->mc_r##reg Dump_X64() #elif defined __aarch64__ #define REG_GNUM(num) &r->mc_gpregs.gp_x[num] #define REG_GET_FP() &r->mc_gpregs.gp_x[29] #define REG_GET_LR() &r->mc_gpregs.gp_lr #define REG_GET_SP() &r->mc_gpregs.gp_sp #define REG_GET_PC() &r->mc_gpregs.gp_elr Dump_ARM64() #elif defined __arm__ #define REG_GNUM(num) &r->__gregs[num] #define REG_GET_FP() &r->__gregs[_REG_FP] #define REG_GET_IP() &r->__gregs[12] #define REG_GET_SP() &r->__gregs[_REG_SP] #define REG_GET_LR() &r->__gregs[_REG_LR] #define REG_GET_PC() &r->__gregs[_REG_PC] Dump_ARM32() #elif defined __powerpc__ #define REG_GNUM(num) &r->mc_frame[##num] #define REG_GET_PC() &r->mc_srr0 #define REG_GET_LR() &r->mc_lr #define REG_GET_CTR() &r->mc_ctr Dump_PPC() #elif defined __mips__ #define REG_GNUM(num) &r->mc_regs[num] #define REG_GET_PC() &r->mc_pc #define REG_GET_LO() &r->mullo #define REG_GET_HI() &r->mulhi Dump_MIPS() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_OPENBSD /* See /usr/include/machine/signal.h */ /* -> src/sys/arch/[ARCH]/include/signal.h */ static void PrintRegisters(cc_string* str, void* ctx) { ucontext_t* r = (ucontext_t*)ctx; #if defined __i386__ #define REG_GET(reg, ign) &r->sc_e##reg Dump_X86() #elif defined __x86_64__ #define REG_GET(reg, ign) &r->sc_r##reg Dump_X64() #elif defined __aarch64__ #define REG_GNUM(num) &r->sc_x[num] #define REG_GET_FP() &r->sc_x[29] #define REG_GET_LR() &r->sc_lr #define REG_GET_SP() &r->sc_sp #define REG_GET_PC() &r->sc_elr Dump_ARM64() #elif defined __arm__ #define REG_GNUM(num) &r->sc_r##num #define REG_GET_FP() &r->sc_r11 #define REG_GET_IP() &r->sc_r12 #define REG_GET_SP() &r->sc_usr_sp #define REG_GET_LR() &r->sc_usr_lr #define REG_GET_PC() &r->sc_pc Dump_ARM32() #elif defined __powerpc__ #define REG_GNUM(num) &r->sc_frame.fixreg[num] #define REG_GET_PC() &r->sc_frame.srr0 #define REG_GET_LR() &r->sc_frame.lr #define REG_GET_CTR() &r->sc_frame.ctr Dump_PPC() #elif defined __mips__ #define REG_GNUM(num) &r->sc_regs[num] #define REG_GET_PC() &r->sc_pc #define REG_GET_LO() &r->mullo #define REG_GET_HI() &r->mulhi Dump_MIPS() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_HAIKU /* See /headers/posix/arch/[ARCH]/signal.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t* r = &((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(reg, ign) &r->e##reg Dump_X86() #elif defined __x86_64__ #define REG_GET(reg, ign) &r->r##reg Dump_X64() #elif defined __aarch64__ #define REG_GNUM(num) &r->x[num] #define REG_GET_FP() &r->x[29] #define REG_GET_LR() &r->lr #define REG_GET_SP() &r->sp #define REG_GET_PC() &r->elr Dump_ARM64() #elif defined __arm__ #define REG_GNUM(num) &r->r##num #define REG_GET_FP() &r->r11 #define REG_GET_IP() &r->r12 #define REG_GET_SP() &r->r13 #define REG_GET_LR() &r->r14 #define REG_GET_PC() &r->r15 Dump_ARM32() #elif defined __riscv #define REG_GNUM(num) &r->x[num - 1] #define REG_GET_PC() &r->pc Dump_RISCV() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_SERENITY static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t* r = &((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ #define REG_GET(reg, ign) &r->e##reg Dump_X86() #elif defined __x86_64__ #define REG_GET(reg, ign) &r->r##reg Dump_X64() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_IRIX /* See /usr/include/sys/ucontext.h */ /* https://nixdoc.net/man-pages/IRIX/man5/UCONTEXT.5.html */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t* r = &((ucontext_t*)ctx)->uc_mcontext; #define REG_GNUM(num) &r->__gregs[CTX_EPC] #define REG_GET_PC() &r->__gregs[CTX_MDLO] #define REG_GET_LO() &r->__gregs[CTX_MDLO] #define REG_GET_HI() &r->__gregs[CTX_MDHI] Dump_MIPS() } #else static void PrintRegisters(cc_string* str, void* ctx) { /* Register dumping not implemented */ } #endif static void DumpRegisters(void* ctx) { cc_string str; char strBuffer[768]; String_InitArray(str, strBuffer); String_AppendConst(&str, "-- registers --" _NL); PrintRegisters(&str, ctx); Logger_Log(&str); } /*########################################################################################################################* *------------------------------------------------Module/Memory map handling-----------------------------------------------* *#########################################################################################################################*/ #if defined CC_BUILD_WIN static void DumpStack(void) { static const cc_string stack = String_FromConst("-- stack --\r\n"); cc_string str; char strBuffer[128]; cc_uint8 buffer[0x10]; SIZE_T numRead; int i, j; Logger_Log(&stack); spRegister &= ~0x0F; spRegister -= 0x40; /* Dump 128 bytes near stack pointer */ for (i = 0; i < 8; i++, spRegister += 0x10) { String_InitArray(str, strBuffer); String_Format1(&str, "0x%x)", &spRegister); ReadProcessMemory(CUR_PROCESS_HANDLE, (LPCVOID)spRegister, buffer, 0x10, &numRead); for (j = 0; j < 0x10; j++) { if ((j & 0x03) == 0) String_Append(&str, ' '); String_AppendHex(&str, buffer[j]); String_Append(&str, ' '); } String_AppendConst(&str, "\r\n"); Logger_Log(&str); } } static BOOL CALLBACK DumpModule(const char* name, ULONG_PTR base, ULONG size, void* userCtx) { cc_string str; char strBuffer[256]; cc_uintptr beg, end; beg = base; end = base + (size - 1); String_InitArray(str, strBuffer); String_Format3(&str, "%c = %x-%x\r\n", name, &beg, &end); Logger_Log(&str); return true; } static BOOL (WINAPI *_EnumerateLoadedModules)(HANDLE process, PENUMLOADED_MODULES_CALLBACK callback, PVOID userContext); static void DumpMisc(void) { static const cc_string modules = String_FromConst("-- modules --\r\n"); if (spRegister >= 0xFFFF) DumpStack(); if (!_EnumerateLoadedModules) return; Logger_Log(&modules); _EnumerateLoadedModules(curProcess, DumpModule, NULL); } #elif defined CC_BUILD_LINUX || defined CC_BUILD_SOLARIS || defined CC_BUILD_ANDROID #include #include #include #ifdef CC_BUILD_ANDROID static int SkipRange(const cc_string* str) { /* Android has a lot of ranges in /maps, which produces 100-120 kb of logs for one single crash! */ /* Example of different types of ranges: 7a2df000-7a2eb000 r-xp 00000000 fd:01 419 /vendor/lib/libdrm.so 7a2eb000-7a2ec000 r--p 0000b000 fd:01 419 /vendor/lib/libdrm.so 7a2ec000-7a2ed000 rw-p 0000c000 fd:01 419 /vendor/lib/libdrm.so 7a3d5000-7a4d1000 rw-p 00000000 00:00 0 7a4d1000-7a4d2000 ---p 00000000 00:00 0 [anon:thread stack guard] To cut down crash logs to more relevant information, unnecessary '/' entries are ignored */ cc_string path; /* Always include ranges without a / */ int idx = String_IndexOf(str, '/'); if (idx == -1) return false; path = String_UNSAFE_SubstringAt(str, idx); return /* Ignore fonts */ String_ContainsConst(&path, "/system/fonts/") /* Ignore shared memory (e.g. '/dev/ashmem/dalvik-thread local mark stack (deleted)') */ || String_ContainsConst(&path, "/dev/ashmem/") /* Ignore /dev/mali0 ranges (~200 entries on some devices) */ || String_ContainsConst(&path, "/dev/mali0") /* Ignore /system/lib/ (~350 entries) and /system/lib64 (~700 entries) */ || String_ContainsConst(&path, "/system/lib") /* Ignore /system/framework (~130 to ~160 entries) */ || String_ContainsConst(&path, "/system/framework/") /* Ignore /apex/com.android.art/javalib/ (~240 entries) */ || String_ContainsConst(&path, "/apex/com.") /* Ignore /dev/dri/renderD128 entries (~100 to ~120 entries) */ || String_ContainsConst(&path, "/dri/renderD128") /* Ignore /data/dalvik-cache entries (~80 entries) */ || String_ContainsConst(&path, "/data/dalvik-cache/") /* Ignore /vendor/lib entries (~80 entries) */ || String_ContainsConst(&path, "/vendor/lib"); } #else static int SkipRange(const cc_string* str) { return /* Ignore GPU iris driver i915 GEM buffers (~60,000 entries for one user) */ String_ContainsConst(str, "anon_inode:i915.gem"); } #endif static void DumpMisc(void) { static const cc_string memMap = String_FromConst("-- memory map --\n"); cc_string str; char strBuffer[320]; int n, fd; Logger_Log(&memMap); /* dumps all known ranges of memory */ fd = open("/proc/self/maps", O_RDONLY); if (fd < 0) return; String_InitArray(str, strBuffer); while ((n = read(fd, str.buffer, str.capacity)) > 0) { str.length = n; if (!SkipRange(&str)) Logger_Log(&str); } close(fd); } #elif defined CC_BUILD_DARWIN #include static void DumpMisc(void) { static const cc_string modules = String_FromConst("-- modules --\n"); static const cc_string newLine = String_FromConst(_NL); cc_uint32 i, count; const char* path; cc_string str; /* TODO: Add Logger_LogRaw / Logger_LogConst too */ Logger_Log(&modules); count = _dyld_image_count(); /* dumps absolute path of each module */ for (i = 0; i < count; i++) { path = _dyld_get_image_name(i); if (!path) continue; str = String_FromReadonly(path); Logger_Log(&str); Logger_Log(&newLine); } } #else static void DumpMisc(void) { } #endif /*########################################################################################################################* *--------------------------------------------------Unhandled error logging------------------------------------------------* *#########################################################################################################################*/ #if defined CC_BUILD_WIN static const char* ExceptionDescribe(cc_uint32 code) { switch (code) { case EXCEPTION_ACCESS_VIOLATION: return "ACCESS_VIOLATION"; case EXCEPTION_ILLEGAL_INSTRUCTION: return "ILLEGAL_INSTRUCTION"; case EXCEPTION_INT_DIVIDE_BY_ZERO: return "DIVIDE_BY_ZERO"; } return NULL; } static LONG WINAPI UnhandledFilter(struct _EXCEPTION_POINTERS* info) { cc_string msg; char msgBuffer[128 + 1]; const char* desc; cc_uint32 code; cc_uintptr addr; DWORD i, numArgs; code = (cc_uint32)info->ExceptionRecord->ExceptionCode; addr = (cc_uintptr)info->ExceptionRecord->ExceptionAddress; desc = ExceptionDescribe(code); String_InitArray_NT(msg, msgBuffer); if (desc) { String_Format2(&msg, "Unhandled %c error at %x", desc, &addr); } else { String_Format2(&msg, "Unhandled exception 0x%h at %x", &code, &addr); } numArgs = info->ExceptionRecord->NumberParameters; if (numArgs) { numArgs = min(numArgs, EXCEPTION_MAXIMUM_PARAMETERS); String_AppendConst(&msg, " ["); for (i = 0; i < numArgs; i++) { String_Format1(&msg, "0x%x,", &info->ExceptionRecord->ExceptionInformation[i]); } String_Append(&msg, ']'); } msg.buffer[msg.length] = '\0'; AbortCommon(0, msg.buffer, info->ContextRecord); return EXCEPTION_EXECUTE_HANDLER; /* TODO: different flag */ } void Logger_Hook(void) { static const struct DynamicLibSym funcs[] = { #ifdef _IMAGEHLP64 { "EnumerateLoadedModules64", (void**)&_EnumerateLoadedModules}, #else { "EnumerateLoadedModules", (void**)&_EnumerateLoadedModules }, #endif }; static const cc_string imagehlp = String_FromConst("IMAGEHLP.DLL"); OSVERSIONINFOA osInfo; void* lib; SetUnhandledExceptionFilter(UnhandledFilter); DynamicLib_LoadAll(&imagehlp, funcs, Array_Elems(funcs), &lib); /* Windows 9x requires process IDs instead - see old DBGHELP docs */ /* https://documentation.help/DbgHelp/documentation.pdf */ osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); osInfo.dwPlatformId = 0; GetVersionExA(&osInfo); if (osInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { curProcess = (HANDLE)((cc_uintptr)GetCurrentProcessId()); } } #elif defined CC_BUILD_POSIX static const char* SignalDescribe(int type) { switch (type) { case SIGSEGV: return "SIGSEGV"; case SIGBUS: return "SIGBUS"; case SIGILL: return "SIGILL"; case SIGABRT: return "SIGABRT"; case SIGFPE: return "SIGFPE"; } return NULL; } static void SignalHandler(int sig, siginfo_t* info, void* ctx) { cc_string msg; char msgBuffer[128 + 1]; const char* desc; int type, code; cc_uintptr addr; /* Uninstall handler to avoid chance of infinite loop */ signal(SIGSEGV, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGFPE, SIG_DFL); type = info->si_signo; code = info->si_code; addr = (cc_uintptr)info->si_addr; desc = SignalDescribe(type); String_InitArray_NT(msg, msgBuffer); if (desc) { String_Format3(&msg, "Unhandled signal %c (code %i) at %x", desc, &code, &addr); } else { String_Format3(&msg, "Unhandled signal %i (code %i) at %x", &type, &code, &addr); } msg.buffer[msg.length] = '\0'; #if defined CC_BUILD_ANDROID /* deliberate Dalvik VM abort, try to log a nicer error for this */ if (type == SIGSEGV && addr == 0xDEADD00D) Platform_TryLogJavaError(); #endif AbortCommon(0, msg.buffer, ctx); } void Logger_Hook(void) { struct sigaction sa, old; sa.sa_sigaction = SignalHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_SIGINFO; sigaction(SIGSEGV, &sa, &old); sigaction(SIGBUS, &sa, &old); sigaction(SIGILL, &sa, &old); sigaction(SIGABRT, &sa, &old); sigaction(SIGFPE, &sa, &old); } #else void Logger_Hook(void) { /* TODO can signals be supported somehow for PSP/3DS? */ } #endif /*########################################################################################################################* *-------------------------------------------------Deliberate crash logging------------------------------------------------* *#########################################################################################################################*/ #if defined CC_BUILD_WIN #if __GNUC__ /* Don't want compiler doing anything fancy with registers */ void __attribute__((optimize("O0"))) Logger_Abort2(cc_result result, const char* raw_msg) { #else void Logger_Abort2(cc_result result, const char* raw_msg) { #endif CONTEXT ctx; #if _M_IX86 && __GNUC__ /* Stack frame layout on x86: */ /* [ebp] is previous frame's EBP */ /* [ebp+4] is previous frame's EIP (return address) */ /* address of [ebp+8] is previous frame's ESP */ __asm__( "mov 0(%%ebp), %%eax \n\t" /* mov eax, [ebp] */ "mov %%eax, %0 \n\t" /* mov [ctx.Ebp], eax */ "mov 4(%%ebp), %%eax \n\t" /* mov eax, [ebp+4] */ "mov %%eax, %1 \n\t" /* mov [ctx.Eip], eax */ "lea 8(%%ebp), %%eax \n\t" /* lea eax, [ebp+8] */ "mov %%eax, %2" /* mov [ctx.Esp], eax */ : "=m" (ctx.Ebp), "=m" (ctx.Eip), "=m" (ctx.Esp) : : "eax", "memory" ); ctx.ContextFlags = CONTEXT_CONTROL; #else /* This method is guaranteed to exist on 64 bit windows. */ /* NOTE: This is missing in 32 bit Windows 2000 however, */ /* so an alternative is provided for MinGW above so that */ /* the game can be cross-compiled for Windows 98 / 2000 */ RtlCaptureContext(&ctx); #endif AbortCommon(result, raw_msg, &ctx); } #else void Logger_Abort2(cc_result result, const char* raw_msg) { AbortCommon(result, raw_msg, NULL); } #endif /*########################################################################################################################* *----------------------------------------------------------Common---------------------------------------------------------* *#########################################################################################################################*/ #ifdef CC_BUILD_MINFILES void Logger_Log(const cc_string* msg) { Platform_Log(msg->buffer, msg->length); } static void LogCrashHeader(void) { } static void CloseLogFile(void) { } #else static struct Stream logStream; static cc_bool logOpen; void Logger_Log(const cc_string* msg) { static const cc_string path = String_FromConst("client.log"); if (!logOpen) { logOpen = true; Stream_AppendFile(&logStream, &path); } if (!logStream.meta.file) return; Stream_Write(&logStream, (const cc_uint8*)msg->buffer, msg->length); } static void LogCrashHeader(void) { cc_string msg; char msgBuffer[96]; struct DateTime now; String_InitArray(msg, msgBuffer); String_AppendConst(&msg, _NL "----------------------------------------" _NL); Logger_Log(&msg); msg.length = 0; DateTime_CurrentLocal(&now); String_Format3(&msg, "Crash time: %p2/%p2/%p4 ", &now.day, &now.month, &now.year); String_Format3(&msg, "%p2:%p2:%p2" _NL, &now.hour, &now.minute, &now.second); Logger_Log(&msg); } static void CloseLogFile(void) { if (logStream.meta.file) logStream.Close(&logStream); } #endif #if CC_GFX_BACKEND == CC_GFX_BACKEND_D3D11 #define GFX_BACKEND " (Direct3D11)" #elif CC_GFX_BACKEND == CC_GFX_BACKEND_D3D9 #define GFX_BACKEND " (Direct3D9)" #elif CC_GFX_BACKEND == CC_GFX_BACKEND_GL2 #define GFX_BACKEND " (ModernGL)" #elif CC_GFX_BACKEND == CC_GFX_BACKEND_GL1 #define GFX_BACKEND " (OpenGL)" #else #define GFX_BACKEND " (Unknown)" #endif static void AbortCommon(cc_result result, const char* raw_msg, void* ctx) { static const cc_string backtrace = String_FromConst("-- backtrace --" _NL); cc_string msg; char msgBuffer[3070 + 1]; String_InitArray_NT(msg, msgBuffer); String_AppendConst(&msg, "ClassiCube crashed." _NL); if (raw_msg) String_Format1(&msg, "Reason: %c" _NL, raw_msg); #ifdef CC_COMMIT_SHA String_AppendConst(&msg, "Commit SHA: " CC_COMMIT_SHA GFX_BACKEND _NL); #endif if (result) { String_Format1(&msg, "%h" _NL, &result); } else { result = 1; } LogCrashHeader(); Logger_Log(&msg); String_AppendConst(&msg, "Full details of the crash have been logged to 'client.log'.\n"); String_AppendConst(&msg, "Please report this on the ClassiCube forums or to UnknownShadow200.\n\n"); if (ctx) DumpRegisters(ctx); /* These two function calls used to be in a separate DumpBacktrace function */ /* However that was not always inlined by the compiler, which resulted in a */ /* useless strackframe being logged - so manually inline Logger_Backtrace call */ Logger_Log(&backtrace); Logger_Backtrace(&msg, ctx); DumpMisc(); CloseLogFile(); msg.buffer[msg.length] = '\0'; Window_ShowDialog("We're sorry", msg.buffer); Process_Exit(result); } void Logger_Abort(const char* raw_msg) { Logger_Abort2(0, raw_msg); } void Logger_FailToStart(const char* raw_msg) { cc_string msg = String_FromReadonly(raw_msg); Window_ShowDialog("Failed to start ClassiCube", raw_msg); LogCrashHeader(); Logger_Log(&msg); Process_Exit(1); } #if defined CC_BACKTRACE_UNWIND #include static _Unwind_Reason_Code UnwindFrame(struct _Unwind_Context* ctx, void* arg) { cc_uintptr addr = _Unwind_GetIP(ctx); if (!addr) return _URC_END_OF_STACK; DumpFrame((cc_string*)arg, (void*)addr); return _URC_NO_REASON; } void Logger_Backtrace(cc_string* trace, void* ctx) { _Unwind_Backtrace(UnwindFrame, trace); String_AppendConst(trace, _NL); } #endif #if defined CC_BACKTRACE_BUILTIN static CC_NOINLINE void* GetReturnAddress(int level) { /* "... a value of 0 yields the return address of the current function, a value of 1 yields the return address of the caller of the current function" */ switch(level) { case 0: return __builtin_return_address(1); case 1: return __builtin_return_address(2); case 2: return __builtin_return_address(3); case 3: return __builtin_return_address(4); case 4: return __builtin_return_address(5); case 5: return __builtin_return_address(6); case 6: return __builtin_return_address(7); case 7: return __builtin_return_address(8); case 8: return __builtin_return_address(9); case 9: return __builtin_return_address(10); case 10: return __builtin_return_address(11); case 11: return __builtin_return_address(12); case 12: return __builtin_return_address(13); case 13: return __builtin_return_address(14); case 14: return __builtin_return_address(15); case 15: return __builtin_return_address(16); case 16: return __builtin_return_address(17); case 17: return __builtin_return_address(18); case 18: return __builtin_return_address(19); case 19: return __builtin_return_address(20); case 20: return __builtin_return_address(21); default: return NULL; } } static CC_NOINLINE void* GetFrameAddress(int level) { /* "... a value of 0 yields the frame address of the current function, a value of 1 yields the frame address of the caller of the current function, and so forth." */ switch(level) { case 0: return __builtin_frame_address(1); case 1: return __builtin_frame_address(2); case 2: return __builtin_frame_address(3); case 3: return __builtin_frame_address(4); case 4: return __builtin_frame_address(5); case 5: return __builtin_frame_address(6); case 6: return __builtin_frame_address(7); case 7: return __builtin_frame_address(8); case 8: return __builtin_frame_address(9); case 9: return __builtin_frame_address(10); case 10: return __builtin_frame_address(11); case 11: return __builtin_frame_address(12); case 12: return __builtin_frame_address(13); case 13: return __builtin_frame_address(14); case 14: return __builtin_frame_address(15); case 15: return __builtin_frame_address(16); case 16: return __builtin_frame_address(17); case 17: return __builtin_frame_address(18); case 18: return __builtin_frame_address(19); case 19: return __builtin_frame_address(20); case 20: return __builtin_frame_address(21); default: return NULL; } } void Logger_Backtrace(cc_string* trace, void* ctx) { void* addrs[MAX_BACKTRACE_FRAMES]; int i, frames; /* See https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html */ /* Note "Calling this function with a nonzero argument can have unpredictable effects, including crashing the calling program" */ /* So this probably only works on x86/x86_64 */ for (i = 0; GetFrameAddress(i + 1) && i < MAX_BACKTRACE_FRAMES; i++) { addrs[i] = GetReturnAddress(i); if (!addrs[i]) break; } frames = i; for (i = 0; i < frames; i++) { DumpFrame(trace, addrs[i]); } String_AppendConst(trace, _NL); } #endif