summary refs log tree commit diff
path: root/src/Logger.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Logger.c
initial commit
Diffstat (limited to 'src/Logger.c')
-rw-r--r--src/Logger.c1386
1 files changed, 1386 insertions, 0 deletions
diff --git a/src/Logger.c b/src/Logger.c
new file mode 100644
index 0000000..d3107a4
--- /dev/null
+++ b/src/Logger.c
@@ -0,0 +1,1386 @@
+#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 <windows.h>
+	#include <imagehlp.h>
+	static HANDLE curProcess = CUR_PROCESS_HANDLE;
+#elif defined CC_BUILD_OPENBSD || defined CC_BUILD_HAIKU || defined CC_BUILD_SERENITY
+	#include <signal.h>
+	/* These operating systems don't provide sys/ucontext.h */
+	/*  But register constants be found from includes in <signal.h> */
+	#elif defined CC_BUILD_OS2
+	#include <signal.h>
+	#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 <sys/ucontext.h>
+	#include <signal.h>
+#elif defined CC_BUILD_POSIX
+	#include <signal.h>
+	#include <sys/ucontext.h>
+#endif
+
+#ifdef CC_BUILD_DARWIN
+/* Need this to detect macOS < 10.4, and switch to NS* api instead if so */
+#include <AvailabilityMacros.h>
+#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 <dlfcn.h>
+#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 <execinfo.h>
+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 <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#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 <mach-o/dyld.h>
+
+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 <unwind.h>
+
+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