diff foosdk/sdk/foobar2000/shared/crash_info.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/shared/crash_info.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,652 @@
+#include "shared.h"
+#include <imagehlp.h>
+#include <forward_list>
+
+#if SIZE_MAX == UINT32_MAX
+#define STACKSPEC "%08X"
+#define PTRSPEC "%08Xh"
+#define OFFSETSPEC "%Xh"
+#elif SIZE_MAX == UINT64_MAX
+#define STACKSPEC "%016llX"
+#define PTRSPEC "%016llXh"
+#define OFFSETSPEC "%llXh"
+#else
+#error WTF?
+#endif
+
+
+static volatile bool g_didSuppress = false;
+static critical_section g_panicHandlersSync;
+static std::forward_list<fb2k::panicHandler*> g_panicHandlers;
+
+static void callPanicHandlers() {
+	insync(g_panicHandlersSync);
+	for( auto i = g_panicHandlers.begin(); i != g_panicHandlers.end(); ++ i ) {
+		try {
+			(*i)->onPanic();
+		} catch(...) {}
+	}
+}
+
+void SHARED_EXPORT uAddPanicHandler(fb2k::panicHandler* p) {
+	insync(g_panicHandlersSync);
+	g_panicHandlers.push_front(p);
+}
+
+void SHARED_EXPORT uRemovePanicHandler(fb2k::panicHandler* p) {
+	insync(g_panicHandlersSync);
+	g_panicHandlers.remove(p);
+}
+
+
+enum { EXCEPTION_BUG_CHECK = 0xaa67913c };
+
+#if FB2K_SUPPORT_CRASH_LOGS
+
+static const unsigned char utf8_header[3] = {0xEF,0xBB,0xBF};
+
+static __declspec(thread) char g_thread_call_stack[1024];
+static __declspec(thread) t_size g_thread_call_stack_length;
+
+static critical_section g_lastEventsSync;
+static pfc::chain_list_v2_t<pfc::string8> g_lastEvents;
+static constexpr t_size KLastEventCount = 200;
+
+static pfc::string8 version_string;
+
+static pfc::array_t<TCHAR> DumpPathBuffer;
+
+static long crash_no = 0;
+
+// Debug timer: GetTickCount() diff since app startup.
+// Intentionally kept as dumb as possible (originally meant to format system time nicely).
+// Do not want to do expensive preformat of every event that in >99% of scenarios isn't logged.
+// Cannot do formatting & system calls in crash handler.
+// So we just write amount of MS since app startup.
+static const uint64_t debugTimerInit = GetTickCount64();
+static pfc::format_int_t queryDebugTimer() { return pfc::format_int( GetTickCount64() - debugTimerInit ); }
+
+static pfc::string8 g_components;
+
+static void WriteFileString_internal(HANDLE hFile,const char * ptr,t_size len)
+{
+	DWORD bah;
+	WriteFile(hFile,ptr,(DWORD)len,&bah,0);
+}
+
+static HANDLE create_failure_log()
+{
+	bool rv = false;
+	if (DumpPathBuffer.get_size() == 0) return INVALID_HANDLE_VALUE;
+
+	TCHAR * path = DumpPathBuffer.get_ptr();
+
+	t_size lenWalk = _tcslen(path);
+
+	if (lenWalk == 0 || path[lenWalk-1] != '\\') {path[lenWalk++] = '\\';}
+
+	_tcscpy(path + lenWalk, _T("crash reports"));
+	lenWalk += _tcslen(path + lenWalk);
+
+	SetLastError(NO_ERROR);
+	if (!CreateDirectory(path, NULL)) {
+		if (GetLastError() != ERROR_ALREADY_EXISTS) return INVALID_HANDLE_VALUE;
+	}
+	path[lenWalk++] = '\\';
+
+	TCHAR * fn_out = path + lenWalk;
+	HANDLE hFile = INVALID_HANDLE_VALUE;
+	unsigned attempts = 0;
+	for(;;) {
+		wsprintf(fn_out,TEXT("failure_%08u.txt"),++attempts);
+		hFile = CreateFile(path,GENERIC_WRITE,0,0,CREATE_NEW,0,0);
+		if (hFile!=INVALID_HANDLE_VALUE) break;
+		if (attempts > 1000) break;
+	}
+	if (hFile!=INVALID_HANDLE_VALUE) WriteFileString_internal(hFile, (const char*)utf8_header, sizeof(utf8_header));
+	return hFile;
+}
+
+
+static void WriteFileString(HANDLE hFile,const char * str)
+{
+	const char * ptr = str;
+	for(;;)
+	{
+		const char * start = ptr;
+		ptr = strchr(ptr,'\n');
+		if (ptr)
+		{
+			if (ptr>start) {
+				t_size len = ptr-start;
+				if (ptr[-1] == '\r') --len;
+				WriteFileString_internal(hFile,start,len);
+			}
+			WriteFileString_internal(hFile,"\r\n",2);
+			ptr++;
+		}
+		else
+		{
+			WriteFileString_internal(hFile,start,strlen(start));
+			break;
+		}
+	}
+}
+
+static void WriteEvent(HANDLE hFile,const char * str) {
+	bool haveText = false;
+	const char * ptr = str;
+	bool isLineBreak = false;
+	while(*ptr) {
+		const char * base = ptr;
+		while(*ptr && *ptr != '\r' && *ptr != '\n') ++ptr;
+		if (ptr > base) {
+			if (isLineBreak) WriteFileString_internal(hFile,"\r\n",2);
+			WriteFileString_internal(hFile,base,ptr-base);
+			isLineBreak = false; haveText = true;
+		}
+		for(;;) {
+			if (*ptr == '\n') isLineBreak = haveText;
+			else if (*ptr != '\r') break;
+			++ptr;
+		}
+	}
+	if (haveText) WriteFileString_internal(hFile,"\r\n",2);
+}
+
+static bool read_int(t_size src, t_size* out)
+{
+	__try
+	{
+		*out = *(t_size*)src;
+		return true;
+	} __except (1) { return false; }
+}
+
+static bool hexdump8(char * out,size_t address,const char * msg,int from,int to)
+{
+	unsigned max = (to-from)*16;
+	if (IsBadReadPtr((const void*)(address+(from*16)),max)) return false;
+	out += sprintf(out,"\n%s (" PTRSPEC "):",msg,address);
+	unsigned n;
+	const unsigned char * src = (const unsigned char*)(address)+(from*16);
+	
+	for(n=0;n<max;n++)
+	{
+		if (n%16==0)
+		{
+			out += sprintf(out,"\n" PTRSPEC ": ",(size_t)src);
+		}
+
+		out += sprintf(out," %02X",*(src++));
+	}
+	*(out++) = '\n';
+	*out=0;
+	return true;
+}
+
+static bool hexdump_stack(char * out,size_t address,const char * msg,int from,int to)
+{
+	constexpr unsigned lineWords = 4;
+	constexpr unsigned wordBytes = sizeof(size_t);
+	constexpr unsigned lineBytes = lineWords * wordBytes;
+	const unsigned max = (to-from)*lineBytes;
+	if (IsBadReadPtr((const void*)(address+(from*lineBytes)),max)) return false;
+	out += sprintf(out,"\n%s (" PTRSPEC "):",msg,address);
+	unsigned n;
+	const unsigned char * src = (const unsigned char*)(address)+(from*16);
+	
+	for(n=0;n<max;n+=4)
+	{
+		if (n % lineBytes == 0)
+		{
+			out += sprintf(out,"\n" PTRSPEC ": ",(size_t)src);
+		}
+
+		out += sprintf(out," " STACKSPEC,*(size_t*)src);
+		src += wordBytes;
+	}
+	*(out++) = '\n';
+	*out=0;
+	return true;
+}
+
+static void call_stack_parse(size_t address,HANDLE hFile,char * temp,HANDLE hProcess)
+{
+	bool inited = false;
+	t_size data;
+	t_size count_done = 0;
+	while(count_done<1024 && read_int(address, &data))
+	{
+		if (!IsBadCodePtr((FARPROC)data))
+		{
+			bool found = false;
+			{
+				IMAGEHLP_MODULE mod = {};
+				mod.SizeOfStruct = sizeof(mod);
+				if (SymGetModuleInfo(hProcess,data,&mod))
+				{
+					if (!inited)
+					{
+						WriteFileString(hFile,"\nStack dump analysis:\n");
+						inited = true;
+					}
+					sprintf(temp, "Address: " PTRSPEC " (%s+" OFFSETSPEC ")", data, mod.ModuleName, data - mod.BaseOfImage);
+					//sprintf(temp,"Address: %08Xh, location: \"%s\", loaded at %08Xh - %08Xh\n",data,mod.ModuleName,mod.BaseOfImage,mod.BaseOfImage+mod.ImageSize);
+					WriteFileString(hFile,temp);
+					found = true;
+				}
+			}
+
+
+			if (found)
+			{
+				union
+				{
+					char buffer[128];
+					IMAGEHLP_SYMBOL symbol;
+				};
+				memset(buffer,0,sizeof(buffer));
+				symbol.SizeOfStruct = sizeof(symbol);
+				symbol.MaxNameLength = (DWORD)(buffer + sizeof(buffer) - symbol.Name);
+				DWORD_PTR offset = 0;
+				if (SymGetSymFromAddr(hProcess,data,&offset,&symbol))
+				{
+					buffer[PFC_TABSIZE(buffer)-1]=0;
+					if (symbol.Name[0])
+					{
+						sprintf(temp,", symbol: \"%s\" (+" OFFSETSPEC ")",symbol.Name,offset);
+						WriteFileString(hFile,temp);
+					}
+				}
+				WriteFileString(hFile, "\n");
+			}
+		}
+		address += sizeof(size_t);
+		count_done++;
+	}
+}
+
+static BOOL CALLBACK EnumModulesCallback(PCSTR ModuleName,ULONG_PTR BaseOfDll,PVOID UserContext)
+{
+	IMAGEHLP_MODULE mod = {};
+	mod.SizeOfStruct = sizeof(mod);
+	if (SymGetModuleInfo(GetCurrentProcess(),BaseOfDll,&mod))
+	{
+		char temp[1024];
+		char temp2[PFC_TABSIZE(mod.ModuleName)+1];
+		strcpy(temp2,mod.ModuleName);
+
+		{
+			t_size ptr;
+			for(ptr=strlen(temp2);ptr<PFC_TABSIZE(temp2)-1;ptr++)
+				temp2[ptr]=' ';
+			temp2[ptr]=0;
+		}
+
+		sprintf(temp,"%s loaded at " PTRSPEC " - " PTRSPEC "\n",temp2,mod.BaseOfImage,mod.BaseOfImage+mod.ImageSize);
+		WriteFileString((HANDLE)UserContext,temp);
+	}
+	return TRUE;
+}
+
+static bool SalvageString(t_size rptr, char* temp) {
+	__try {
+		const char* ptr = reinterpret_cast<const char*>(rptr);
+		t_size walk = 0;
+		for (; walk < 255; ++walk) {
+			char c = ptr[walk];
+			if (c == 0) break;
+			if (c < 0x20) c = ' ';
+			temp[walk] = c;
+		}
+		temp[walk] = 0;
+		return true;
+	} __except (1) { return false; }
+}
+
+static void DumpCPPExceptionData(HANDLE hFile, const ULONG_PTR* params, char* temp) {
+	t_size strPtr;
+	if (read_int(params[1] + sizeof(void*), &strPtr)) {
+		if (SalvageString(strPtr, temp)) {
+			WriteFileString(hFile, "Message: ");
+			WriteFileString(hFile, temp);
+			WriteFileString(hFile, "\n");
+		}
+	}
+}
+
+static void writeFailureTxt(LPEXCEPTION_POINTERS param, HANDLE hFile, DWORD lastError) {
+	char temp[2048];
+	{
+		t_size address = (t_size)param->ExceptionRecord->ExceptionAddress;
+		sprintf(temp, "Illegal operation:\nCode: %08Xh, flags: %08Xh, address: " PTRSPEC "\n", param->ExceptionRecord->ExceptionCode, param->ExceptionRecord->ExceptionFlags, address);
+		WriteFileString(hFile, temp);
+
+		if (param->ExceptionRecord->ExceptionCode == EXCEPTION_BUG_CHECK) {
+			WriteFileString(hFile, "Bug check\n");
+		} else if (param->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && param->ExceptionRecord->NumberParameters >= 2) {
+			sprintf(temp, "Access violation, operation: %s, address: " PTRSPEC "\n", param->ExceptionRecord->ExceptionInformation[0] ? "write" : "read", param->ExceptionRecord->ExceptionInformation[1]);
+			WriteFileString(hFile, temp);
+		} else if (param->ExceptionRecord->NumberParameters > 0) {
+			WriteFileString(hFile, "Additional parameters:");
+			for (DWORD walk = 0; walk < param->ExceptionRecord->NumberParameters; ++walk) {
+				sprintf(temp, " " PTRSPEC, param->ExceptionRecord->ExceptionInformation[walk]);
+				WriteFileString(hFile, temp);
+			}
+			WriteFileString(hFile, "\n");
+		}
+
+		if (param->ExceptionRecord->ExceptionCode == 0xE06D7363 && param->ExceptionRecord->NumberParameters >= 3) {	//C++ exception
+			DumpCPPExceptionData(hFile, param->ExceptionRecord->ExceptionInformation, temp);
+		}
+
+		if (lastError) {
+			sprintf(temp, "Last win32 error: %u\n", lastError);
+			WriteFileString(hFile, temp);
+		}
+
+		if (g_thread_call_stack[0] != 0) {
+			WriteFileString(hFile, "\nCall path:\n");
+			WriteFileString(hFile, g_thread_call_stack);
+			WriteFileString(hFile, "\n");
+		} else {
+			WriteFileString(hFile, "\nCall path not available.\n");
+		}
+
+
+		if (hexdump8(temp, address, "Code bytes", -4, +4)) WriteFileString(hFile, temp);
+#ifdef _M_IX86
+		if (hexdump_stack(temp, param->ContextRecord->Esp, "Stack", -2, +18)) WriteFileString(hFile, temp);
+		sprintf(temp, "\nRegisters:\nEAX: %08X, EBX: %08X, ECX: %08X, EDX: %08X\nESI: %08X, EDI: %08X, EBP: %08X, ESP: %08X\n", param->ContextRecord->Eax, param->ContextRecord->Ebx, param->ContextRecord->Ecx, param->ContextRecord->Edx, param->ContextRecord->Esi, param->ContextRecord->Edi, param->ContextRecord->Ebp, param->ContextRecord->Esp);
+		WriteFileString(hFile, temp);
+#endif
+
+#ifdef _M_X64
+		if (hexdump_stack(temp, param->ContextRecord->Rsp, "Stack", -2, +18)) WriteFileString(hFile, temp);
+		sprintf(temp, "\nRegisters:\nRAX: %016llX, RBX: %016llX, RCX: %016llX, RDX: %016llX\nRSI: %016llX, RDI: %016llX, RBP: %016llX, RSP: %016llX\n", param->ContextRecord->Rax, param->ContextRecord->Rbx, param->ContextRecord->Rcx, param->ContextRecord->Rdx, param->ContextRecord->Rsi, param->ContextRecord->Rdi, param->ContextRecord->Rbp, param->ContextRecord->Rsp);
+		WriteFileString(hFile, temp);
+#endif
+
+#ifdef _M_ARM64
+		if (hexdump_stack(temp, param->ContextRecord->Sp, "Stack", -2, +18)) WriteFileString(hFile, temp);
+#endif
+
+		WriteFileString(hFile, "\nTimestamp:\n");
+		WriteFileString(hFile, queryDebugTimer() );
+		WriteFileString(hFile, "ms\n");
+
+		{
+			const HANDLE hProcess = GetCurrentProcess();
+			if (SymInitialize(hProcess, NULL, TRUE))
+			{
+				{
+					IMAGEHLP_MODULE mod = {};
+					mod.SizeOfStruct = sizeof(mod);
+					if (!IsBadCodePtr((FARPROC)address) && SymGetModuleInfo(hProcess, address, &mod))
+					{
+						sprintf(temp, "\nCrash location:\nModule: %s\nOffset: " OFFSETSPEC "\n", mod.ModuleName, address - mod.BaseOfImage);
+						WriteFileString(hFile, temp);
+					} else
+					{
+						sprintf(temp, "\nUnable to identify crash location!\n");
+						WriteFileString(hFile, temp);
+					}
+				}
+
+				{
+					union
+					{
+						char buffer[128];
+						IMAGEHLP_SYMBOL symbol;
+					};
+					memset(buffer, 0, sizeof(buffer));
+					symbol.SizeOfStruct = sizeof(symbol);
+					symbol.MaxNameLength = (DWORD)(buffer + sizeof(buffer) - symbol.Name);
+					DWORD_PTR offset = 0;
+					if (SymGetSymFromAddr(hProcess, address, &offset, &symbol))
+					{
+						buffer[PFC_TABSIZE(buffer) - 1] = 0;
+						if (symbol.Name[0])
+						{
+							sprintf(temp, "Symbol: \"%s\" (+" OFFSETSPEC ")\n", symbol.Name, offset);
+							WriteFileString(hFile, temp);
+						}
+					}
+				}
+
+				WriteFileString(hFile, "\nLoaded modules:\n");
+				SymEnumerateModules(hProcess, EnumModulesCallback, hFile);
+
+#ifdef _M_IX86
+				call_stack_parse(param->ContextRecord->Esp, hFile, temp, hProcess);
+#endif
+
+#ifdef _M_X64
+				call_stack_parse(param->ContextRecord->Rsp, hFile, temp, hProcess);
+#endif
+
+				SymCleanup(hProcess);
+			} else {
+				WriteFileString(hFile, "\nFailed to get module/symbol info.\n");
+			}
+		}
+
+		WriteFileString(hFile, "\nEnvironment:\n");
+		WriteFileString(hFile, version_string);
+
+		WriteFileString(hFile, "\n");
+
+		if (!g_components.is_empty()) {
+			WriteFileString(hFile, "\nComponents:\n");
+			WriteFileString(hFile, g_components);
+		}
+
+		{
+			insync(g_lastEventsSync);
+			if (g_lastEvents.get_count() > 0) {
+				WriteFileString(hFile, "\nRecent events:\n");
+				for( auto & walk : g_lastEvents) WriteEvent(hFile, walk);
+			}
+		}
+	}
+}
+
+static bool GrabOSVersion(char * out) {
+	OSVERSIONINFO ver = {}; ver.dwOSVersionInfoSize = sizeof(ver);
+	if (!GetVersionEx(&ver)) return false;
+	*out = 0;
+	char temp[16];
+	strcat(out,"Windows ");
+	 _itoa(ver.dwMajorVersion,temp,10); strcat(out,temp);
+	 strcat(out,".");
+	 _itoa(ver.dwMinorVersion,temp,10); strcat(out,temp);
+	return true;
+}
+
+static void OnLogFileWritten() {
+	TCHAR exePath[MAX_PATH + 1] = {};
+	TCHAR params[1024];
+	GetModuleFileName(NULL, exePath, MAX_PATH);
+	exePath[MAX_PATH] = 0;
+	//unsafe...
+	wsprintf(params, _T("/crashed \"%s\""), DumpPathBuffer.get_ptr());
+	ShellExecute(NULL, NULL, exePath, params, NULL, SW_SHOW);
+}
+
+BOOL WriteMiniDumpHelper(HANDLE, LPEXCEPTION_POINTERS); //minidump.cpp
+
+
+void SHARED_EXPORT uDumpCrashInfo(LPEXCEPTION_POINTERS param)
+{
+	if (g_didSuppress) return;
+	const DWORD lastError = GetLastError();
+	if (InterlockedIncrement(&crash_no) > 1) {Sleep(10000);return;}
+	HANDLE hFile = create_failure_log();
+	if (hFile == INVALID_HANDLE_VALUE) return;
+
+	{
+		_tcscpy(_tcsrchr(DumpPathBuffer.get_ptr(), '.'), _T(".dmp"));
+		HANDLE hDump = CreateFile(DumpPathBuffer.get_ptr(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
+		if (hDump != INVALID_HANDLE_VALUE) {
+			const BOOL written = WriteMiniDumpHelper(hDump, param);
+			CloseHandle(hDump);
+			if (!written) { //don't bother proceeding if we don't have a valid minidump
+				DeleteFile(DumpPathBuffer.get_ptr()); 
+				CloseHandle(hFile);
+				_tcscpy(_tcsrchr(DumpPathBuffer.get_ptr(), '.'), _T(".txt"));
+				DeleteFile(DumpPathBuffer.get_ptr()); 
+				return;
+			} 
+		}
+		_tcscpy(_tcsrchr(DumpPathBuffer.get_ptr(), '.'), _T(".txt"));
+	}
+
+	writeFailureTxt(param, hFile, lastError);
+
+	CloseHandle(hFile);
+
+	OnLogFileWritten();
+}
+
+//No longer used.
+size_t SHARED_EXPORT uPrintCrashInfo(LPEXCEPTION_POINTERS param,const char * extra_info,char * outbase) {
+	*outbase = 0;
+	return 0;
+}
+
+
+LONG SHARED_EXPORT uExceptFilterProc(LPEXCEPTION_POINTERS param) {
+	if (g_didSuppress) return UnhandledExceptionFilter(param);
+	callPanicHandlers();
+	if (IsDebuggerPresent()) {
+		return UnhandledExceptionFilter(param);
+	} else {
+		if ( param->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW ) {
+			pfc::thread2 trd;
+			trd.startHere( [param] {
+				uDumpCrashInfo(param);
+				} );
+			trd.waitTillDone();
+		} else {
+			uDumpCrashInfo(param);
+		}
+		TerminateProcess(GetCurrentProcess(), 0);
+		return 0;// never reached
+	}
+}
+
+void SHARED_EXPORT uPrintCrashInfo_Init(const char * name)//called only by exe on startup
+{
+	version_string = pfc::format( "App: ", name, "\nArch: ", pfc::cpuArch());
+	
+	SetUnhandledExceptionFilter(uExceptFilterProc);
+}
+void SHARED_EXPORT uPrintCrashInfo_Suppress() {
+	g_didSuppress = true;
+}
+
+void SHARED_EXPORT uPrintCrashInfo_AddEnvironmentInfo(const char * p_info) {
+	version_string << "\n" << p_info;
+}
+
+void SHARED_EXPORT uPrintCrashInfo_SetComponentList(const char * p_info) {
+	g_components = p_info;
+}
+
+void SHARED_EXPORT uPrintCrashInfo_SetDumpPath(const char * p_path) {
+	pfc::stringcvt::string_os_from_utf8 temp(p_path);
+	DumpPathBuffer.set_size(temp.length() + 256);
+	_tcscpy(DumpPathBuffer.get_ptr(), temp.get_ptr());
+}
+
+static HANDLE hLogFile = INVALID_HANDLE_VALUE;
+
+static void logEvent(const char* message) {
+	if ( hLogFile != INVALID_HANDLE_VALUE ) {
+		DWORD wrote = 0;
+		WriteFile(hLogFile, message, (DWORD) strlen(message), &wrote, NULL );
+		WriteFile(hLogFile, "\r\n", 2, &wrote, NULL);
+	}
+}
+
+void SHARED_EXPORT uPrintCrashInfo_StartLogging(const char * path) {
+	insync(g_lastEventsSync);
+	PFC_ASSERT(hLogFile == INVALID_HANDLE_VALUE);
+	hLogFile = CreateFile( pfc::stringcvt::string_wide_from_utf8(path), GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
+	PFC_ASSERT(hLogFile != INVALID_HANDLE_VALUE);
+
+	for( auto & walk : g_lastEvents ) {
+		logEvent( walk.c_str() );
+	}
+}
+
+void SHARED_EXPORT uPrintCrashInfo_OnEvent(const char * message, t_size length) {
+
+	pfc::string8 msg = pfc::format("[", queryDebugTimer(), "ms] ");
+	msg.add_string( message, length );
+	uOutputDebugString(msg + "\n");
+
+	insync(g_lastEventsSync);
+	logEvent(msg);
+	while(g_lastEvents.get_count() >= KLastEventCount) g_lastEvents.remove(g_lastEvents.first());
+	g_lastEvents.insert_last( std::move(msg) );
+}
+
+static void callstack_add(const char * param)
+{
+	enum { MAX = PFC_TABSIZE(g_thread_call_stack) - 1} ;
+	t_size len = strlen(param);
+	if (g_thread_call_stack_length + len > MAX) len = MAX - g_thread_call_stack_length;
+	if (len>0)
+	{
+		memcpy(g_thread_call_stack+g_thread_call_stack_length,param,len);
+		g_thread_call_stack_length += len;
+		g_thread_call_stack[g_thread_call_stack_length]=0;
+	}
+}
+
+uCallStackTracker::uCallStackTracker(const char * name)
+{
+	param = g_thread_call_stack_length;
+	if (g_thread_call_stack_length>0) callstack_add("=>");
+	callstack_add(name);
+}
+
+uCallStackTracker::~uCallStackTracker()
+{
+	g_thread_call_stack_length = param;
+	g_thread_call_stack[param]=0;
+
+}
+
+extern "C" {LPCSTR SHARED_EXPORT uGetCallStackPath() {return g_thread_call_stack;} }
+
+#ifdef _DEBUG
+extern "C" {
+	void SHARED_EXPORT fb2kDebugSelfTest() {
+		auto ptr = SetUnhandledExceptionFilter(NULL);
+		PFC_ASSERT( ptr == uExceptFilterProc );
+		SetUnhandledExceptionFilter(ptr);
+	}
+}
+#endif
+
+#else
+
+void SHARED_EXPORT uDumpCrashInfo(LPEXCEPTION_POINTERS param) {}
+void SHARED_EXPORT uPrintCrashInfo_OnEvent(const char * message, t_size length) {}
+LONG SHARED_EXPORT uExceptFilterProc(LPEXCEPTION_POINTERS param) {
+	return UnhandledExceptionFilter(param);
+}
+uCallStackTracker::uCallStackTracker(const char * name) {}
+uCallStackTracker::~uCallStackTracker() {}
+extern "C" {
+	LPCSTR SHARED_EXPORT uGetCallStackPath() { return ""; } 
+	void SHARED_EXPORT fb2kDebugSelfTest() {}
+}
+#endif
+
+PFC_NORETURN void SHARED_EXPORT uBugCheck() {
+	fb2k_instacrash_scope(RaiseException(EXCEPTION_BUG_CHECK, EXCEPTION_NONCONTINUABLE, 0, NULL); );
+}