#include // va_list, va_start, va_end #include // FILE, stderr, stdout | vfprintf #include // exit #include "alloc.h" #include "string.h" #include "print.h" #include "types.h" void vprint(const char* format, va_list args) { ULE_TYPES_H_FTAG; vfprintf(stdout, format, args); } void vprintln(const char* format, va_list args) { ULE_TYPES_H_FTAG; vprint(format, args); print("\n"); } /** * The entire purpose of this is so we don't have to #import everywhere * +we intend to replace printf at some point with this */ void print(const char* format, ...) { ULE_TYPES_H_FTAG; if (format == null) { print("null"); return; } va_list args; va_start(args, format); vprint(format, args); va_end(args); } void println(const char* format, ...) { ULE_TYPES_H_FTAG; if (format == null) { print("null\n"); return; } va_list args; va_start(args, format); vprintln(format, args); va_end(args); } void printBitsLE(const size_t count, void* ptr) { u8* b = (u8*) ptr; u8 byte; for (s32 i = count - 1; i >= 0; i--) { for (s32 j = 7; j >= 0; j--) { byte = (b[i] >> j) & 1; print("%u", byte); } } println(); } /** * Prints a stack trace. * Implementation varies for Win32 vs. *nix */ #define BACKTRACE_MAX_FRAMES 63 #ifdef _WIN32 #include #include // if |string| is non-null, then the stack trace will be concatenated to it instead of being printed to stdout. void trace(String* string) { ULE_TYPES_H_FTAG; #define BACKTRACE_MAX_FUNCTION_NAME_LENGTH 1024 HANDLE processHandle = GetCurrentProcess(); SymInitialize(processHandle, null, true); SymSetOptions(SYMOPT_LOAD_LINES); void* stack[BACKTRACE_MAX_FRAMES]; unsigned short numFrames = CaptureStackBackTrace(0, BACKTRACE_MAX_FRAMES, stack, null); char buffer[sizeof(SYMBOL_INFO) + BACKTRACE_MAX_FUNCTION_NAME_LENGTH*sizeof(TCHAR)]; SYMBOL_INFO* symbol = (SYMBOL_INFO*) buffer; symbol->MaxNameLen = BACKTRACE_MAX_FUNCTION_NAME_LENGTH; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); // @TODO I believe that 'displacement' and line.LineNumber are supposed to be source code column numbers // and line numbers respectively, but displacement doesn't work at all (seems like) and line.LineNumber // is consistently off by a few lines. Perhaps it's post-processed source? I don't know. For now, // filename + line.LineNUmber are printed and we hope that's enough, and understand the line #s are only // approximate. DWORD displacement; IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); for (u32 i = 0; i < numFrames; i++) { DWORD64 address = (DWORD64) stack[i]; SymFromAddr(processHandle, address, null, symbol); if (SymGetLineFromAddr64(processHandle, address, &displacement, &line)) { if (string == null) { print(" %-30s %s:%u\n", symbol->Name, line.FileName, line.LineNumber); } else { string->appendf(" %-30s %s:%u\n", symbol->Name, line.FileName, line.LineNumber); } } else { if (string == null) { warn("SymGetLineFromAddr64 returned error code %lu.\n", GetLastError()); print(" %-30s unknown file\n", symbol->Name); } else { warn("SymGetLineFromAddr64 returned error code %lu.\n", GetLastError()); string->appendf(" %-30s unknown file\n", symbol->Name); } } } #undef BACKTRACE_MAX_FUNCTION_NAME_LENGTH } #include #include void writeMinidump(void* exceptionPointers) { // 'EXCEPTION_POINTERS*' actually // create a file EXCEPTION_POINTERS* ep = (EXCEPTION_POINTERS*) exceptionPointers; HANDLE hFile = CreateFile(_T("MiniDump.dmp"), GENERIC_READ | GENERIC_WRITE, 0, null, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, null); if ((hFile != null) && (hFile != INVALID_HANDLE_VALUE)) { // carry on with creating the minidump MINIDUMP_EXCEPTION_INFORMATION mdei; mdei.ThreadId = GetCurrentThreadId(); mdei.ExceptionPointers = ep; mdei.ClientPointers = FALSE; MINIDUMP_TYPE mdt = MiniDumpNormal; BOOL rv = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (ep != 0) ? &mdei : 0, 0, 0); if (!rv) { println(_T("MiniDumpWriteDump failed. Error: %u"), GetLastError()); } else { println(_T("MiniDump created.")); } CloseHandle(hFile); } else { println(_T("Failed to CreateFile for MiniDump. Error: %u"), GetLastError()); } } #else void writeMinidump(void* exceptionPointers) {} // stub... does nothing on Unix // OSX and Linux stacktrace stuff. #include // backtrace, backtrace_symbols #include // abi::__cxa_demangle // if |string| is non-null, then the stack trace will be concatenated to it instead of being printed to stdout. void trace(String* string) { ULE_TYPES_H_FTAG; void* stack[BACKTRACE_MAX_FRAMES]; u32 stackSize = backtrace(stack, BACKTRACE_MAX_FRAMES); // resolve addresses into strings containing "filename(function+address)" // this array must be free()-ed char** traces = backtrace_symbols(stack, stackSize); // iterate over the returned symbol lines. skip the first, it is the address of this function for (u32 i = 1; i < stackSize; i++) { // the names as provided by 'backtrace_symbols' are mangled for some reason. // we have to demangle them, using this weird api // example mangled names (wrapped in double quotes): // "2 shard_tracy 0x00000001032d5618 _ZL17drawSettingsPanelv + 904" // "2 shard 0x000000010ed8be34 _ZN6ShaderC2EPKcS1_P5ArrayIS1_ES1_ + 1108" // "12 shard 0x000000010ed5edeb main + 43" // // the rule for finding the 'first' character is annoying, because it's usually but not always starting with an underscore, // and when it is an underscore it's usually but not always the first underscore in the string. char buffer[1024]; const char* mangledNameEnd = String::lastCharOccurence(traces[i], '+'); // it actually ends one char before. const char* mangledNameBegin = null; if (mangledNameEnd != null && ((mangledNameEnd - traces[i]) > 2)) { const char* cursor = mangledNameEnd - 2; while (cursor != traces[i]) { if (*cursor == '_') { mangledNameBegin = cursor; } else if (*cursor == ' ') { mangledNameBegin = cursor + 1; break; } cursor--; } } if (mangledNameBegin == null || mangledNameEnd == null) { // we can't demangle this name for some reason, just copy the mangled name to the buffer size_t length = String::len(traces[i]); String::memcpy(buffer, (void*)traces[i], length); buffer[length] = '\0'; } else { size_t length = mangledNameEnd - mangledNameBegin - 1; String::memcpy(buffer, (void*)mangledNameBegin, length); buffer[length] = '\0'; } s32 status = -1; char* trace = abi::__cxa_demangle(buffer, null, null, &status); if (trace == null) { // @HACK, both 'main' and 'start' symbols will fail to be demangled, and we don't really care about printing them // in most cases. your application (certainly true for us) will have its own endpoint which itself is of questionable // usefulness to print, but 'main' and 'start' are even less useful. if (String::memeq((const unsigned char*)(mangledNameBegin), (const unsigned char*)"main", sizeof("main") - 1)) { continue; } else if (String::memeq((const unsigned char*)(mangledNameBegin), (const unsigned char*)"start", sizeof("start") - 1)) { continue; } else { warn("warning: failed to demangle name: %s, exit status of attempt: %d", traces[i], status); // just write back the original trace, un-demangled. trace = traces[i]; } } if (string == null) { print(" %s\n", trace); } else { string->appendf(" %s\n", trace); } } pFree(traces); } #undef BACKTRACE_MAX_FRAMES #endif void _debug( const char* date, const char* time, const char* filename, const int line, const char* format, ... ) { ULE_TYPES_H_FTAG; if (format == null) { print("[%s, %s] [%s:%d] null\n", date, time, filename, line); return; } va_list args; va_start(args, format); print("[%s, %s] [%s:%d] ", date, time, filename, line); vprintln(format, args); va_end(args); } void _warn( const char* date, const char* time, const char* filename, const int line, const char* format, ... ) { ULE_TYPES_H_FTAG; if (format == null) { print("[%s, %s] [%s:%d] %swarning:%s null\n", date, time, filename, line, ANSI_YELLOW, ANSI_RESET); return; } va_list args; va_start(args, format); print("[%s, %s] [%s:%d] %swarning:%s ", date, time, filename, line, ANSI_YELLOW, ANSI_RESET); vprintln(format, args); va_end(args); } static void (*customDie)(const char* string) = null; // if you want to override what happens by default when your program calls 'die', you can do so here. // just keep in mind the intention is for 'die' to be for when your program has encountered a fatal, unrecoverable error. void setCustomDieBehavior(void (*dieBehavior)(const char* string)) { customDie = dieBehavior; } // for fatal errors which may occur at runtime, even on a release binary. // if a fatal error should not occur at runtime on a release binary, consider preferring 'massert' // it's unclear when you should use asserts vs. die actually. idk man, they kinda do the same thing right now void _die( const char* date, const char* time, const char* filename, const int line, const char* format, ... ) { ULE_TYPES_H_FTAG; if (format == null) { if (customDie == null) { print("[%s, %s] [%s:%d] %serror:%s (unspecified error)", date, time, filename, line, ANSI_RED, ANSI_RESET); trace(); exit(1); return; } else { String string = String128f("error: (unspecified error)\n"); trace(&string); customDie(string.c_str()); return; } } va_list args; va_start(args, format); if (customDie == null) { println("[%s, %s] [%s:%d] %serror:%s ", date, time, filename, line, ANSI_RED, ANSI_RESET); vprintln(format, args); println(); va_end(args); trace(); exit(1); } else { String string = String128f(""); string.appendfv(format, args); string.append("\n"); trace(&string); va_end(args); customDie(string.c_str()); } } void print(bool b) { ULE_TYPES_H_FTAG; print("%s", b ? "true" : "false"); } void print(char c) { ULE_TYPES_H_FTAG; print("%c", c); } void print(signed int i) { ULE_TYPES_H_FTAG; print("%d", i); } void print(unsigned int i) { ULE_TYPES_H_FTAG; print("%u", i); } void print(float f) { ULE_TYPES_H_FTAG; print("%.14g", f); } void print(double d) { ULE_TYPES_H_FTAG; print("%.14g", d); } void print(void* p) { ULE_TYPES_H_FTAG; print("%p", p); } void print(char* s) { ULE_TYPES_H_FTAG; print("%s", s); } #ifndef _WIN32 void print(size_t i) { ULE_TYPES_H_FTAG; print("%u", i); } void println(size_t i) { ULE_TYPES_H_FTAG; print(i); print("\n"); } #endif void println(bool b) { ULE_TYPES_H_FTAG; print(b); print("\n"); } void println(char c) { ULE_TYPES_H_FTAG; print(c); print("\n"); } void println(signed int i) { ULE_TYPES_H_FTAG; print(i); print("\n"); } void println(unsigned int i) { ULE_TYPES_H_FTAG; print(i); print("\n"); } void println(float f) { ULE_TYPES_H_FTAG; print(f); print("\n"); } void println(double d) { ULE_TYPES_H_FTAG; print(d); print("\n"); } void println(void* p) { ULE_TYPES_H_FTAG; print(p); print("\n"); } void println(char* s) { ULE_TYPES_H_FTAG; print(s); print("\n"); } void println() { ULE_TYPES_H_FTAG; print("\n"); } #ifdef ULE_CONFIG_OPTION_USE_GLM void print(glm::vec<2, float, (glm::qualifier) 3> v) { ULE_TYPES_H_FTAG; print("vec2: %.14g,%.14g", v.x, v.y); } void print(glm::vec<3, float, (glm::qualifier) 3> v) { ULE_TYPES_H_FTAG; print("vec3: %.14g,%.14g,%.14g", v.x, v.y, v.z); } void print(glm::vec<4, float, (glm::qualifier) 3> v) { ULE_TYPES_H_FTAG; print("vec4: %.14g,%.14g,%.14g,%.14g", v.x, v.y, v.z, v.w); } void print(glm::mat<2, 2, float, (glm::qualifier) 3> m) { ULE_TYPES_H_FTAG; print("mat2: "); print(m[0]); print(m[1]); } void print(glm::mat<3, 3, float, (glm::qualifier) 3> m) { ULE_TYPES_H_FTAG; print("mat3: "); print(m[0]); print(m[1]); print(m[2]); } void print(glm::mat<4, 4, float, (glm::qualifier) 3> m) { ULE_TYPES_H_FTAG; print("mat4: "); print(m[0]); print(m[1]); print(m[2]); print(m[3]); } void println(glm::vec<2, float, (glm::qualifier) 3> v) { ULE_TYPES_H_FTAG; print(v); print("\n"); } void println(glm::vec<3, float, (glm::qualifier) 3> v) { ULE_TYPES_H_FTAG; print(v); print("\n"); } void println(glm::vec<4, float, (glm::qualifier) 3> v) { ULE_TYPES_H_FTAG; print(v); print("\n"); } void println(glm::mat<2, 2, float, (glm::qualifier) 3> m) { ULE_TYPES_H_FTAG; print(m); print("\n"); } void println(glm::mat<3, 3, float, (glm::qualifier) 3> m) { ULE_TYPES_H_FTAG; print(m); print("\n"); } void println(glm::mat<4, 4, float, (glm::qualifier) 3> m) { ULE_TYPES_H_FTAG; print(m); print("\n"); } #endif // ULE_CONFIG_OPTION_USE_GLM