You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
13 KiB
343 lines
13 KiB
|
|
#include <stdarg.h> // va_list, va_start, va_end
|
|
#include <stdio.h> // FILE, stderr, stdout | vfprintf
|
|
#include <stdlib.h> // 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 <stdio.h> 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);
|
|
}
|
|
|
|
/**
|
|
* Prints a stack trace.
|
|
* Implementation varies for Win32 vs. *nix
|
|
*/
|
|
#define BACKTRACE_MAX_FRAMES 63
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <dbghelp.h>
|
|
// 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);
|
|
|
|
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 - 1) * sizeof(TCHAR)];
|
|
SYMBOL_INFO* symbol = (SYMBOL_INFO*) buffer;
|
|
symbol->MaxNameLen = BACKTRACE_MAX_FUNCTION_NAME_LENGTH;
|
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
|
|
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("\tat %s in %s: line: %lu: address %0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
|
|
|
|
} else {
|
|
string->appendf("\tat %s in %s: line: %lu: address %0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
|
|
}
|
|
} else {
|
|
if (string == null) {
|
|
print("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
|
|
print("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
|
|
|
|
} else {
|
|
string->appendf("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
|
|
string->appendf("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
|
|
}
|
|
}
|
|
}
|
|
#undef BACKTRACE_MAX_FUNCTION_NAME_LENGTH
|
|
}
|
|
#else
|
|
// OSX and Linux stacktrace stuff.
|
|
#include <execinfo.h> // backtrace, backtrace_symbols
|
|
#include <cxxabi.h> // 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 {
|
|
println("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* format, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (format == null) {
|
|
print("%sdebug:%s null\n", ANSI_BLUE, ANSI_RESET);
|
|
return;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
print("%sdebug:%s ", ANSI_BLUE, ANSI_RESET);
|
|
vprintln(format, args);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
void _warn(const char* format, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (format == null) {
|
|
print("%swarning:%s null\n", ANSI_YELLOW, ANSI_RESET);
|
|
return;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
print("%swarning:%s ", 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;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#include <Minidumpapiset.h>
|
|
#include <tchar.h>
|
|
static void writeMinidump(EXCEPTION_POINTERS* pep) {
|
|
// create a file
|
|
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 = pep;
|
|
mdei.ClientPointers = FALSE;
|
|
|
|
MINIDUMP_TYPE mdt = MiniDumpNormal;
|
|
BOOL rv = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 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());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 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* format, ...) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (format == null) {
|
|
if (customDie == null) {
|
|
print("%serror:%s (unspecified error)\n", 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("%serror:%s", 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
|
|
|