diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 357f6969..6cd2754f 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -15,11 +15,12 @@ jobs: strategy: matrix: platform: - - { name: 'Linux', arch: 'x64', os: ubuntu-latest, werror: true } - - { name: 'Linux', arch: 'arm64', os: ubuntu-latest, werror: true, cmake-toolchain-file: 'cmake/toolchains/linux-aarch64.cmake', apt-packages: 'gcc-aarch64-linux-gnu g++-aarch64-linux-gnu', cross: true } - - { name: 'MacOS', arch: 'arm64-x64', os: macos-latest, werror: true, cmake-args: '-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"' } - - { name: 'Windows', arch: 'Win32', os: windows-latest } - - { name: 'Windows', arch: 'x64', os: windows-latest } + - { name: 'Linux', arch: 'x64', os: ubuntu-latest, werror: true } + - { name: 'Linux', arch: 'arm64', os: ubuntu-latest, werror: true, cmake-toolchain-file: 'cmake/toolchains/linux-aarch64.cmake', apt-packages: 'gcc-aarch64-linux-gnu g++-aarch64-linux-gnu', cross: true } + - { name: 'MacOS', arch: 'arm64-x64', os: macos-latest, werror: true, cmake-args: '-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"' } + - { name: 'Windows', arch: 'x86', os: windows-latest, msvc-arch: 'Win32' } + - { name: 'Windows', arch: 'x64', os: windows-latest, msvc-arch: 'x64' } +# - { name: 'Windows', arch: 'arm64', os: windows-latest, msvc-arch: 'amd64_arm64', cross: true } defaults: run: @@ -33,9 +34,9 @@ jobs: - name: Set up Ninja uses: aseprite/get-ninja@main - uses: ilammy/msvc-dev-cmd@v1.13.0 - if: runner.os == 'Windows' + if: ${{ !!matrix.platform.msvc-arch }} with: - arch: ${{ matrix.platform.arch }} + arch: ${{ matrix.platform.msvc-arch }} - name: Install Linux dependencies if: ${{ runner.os == 'Linux' }} run: | diff --git a/src/harness/os/windows.c b/src/harness/os/windows.c index 81f32070..2610691a 100644 --- a/src/harness/os/windows.c +++ b/src/harness/os/windows.c @@ -2,76 +2,222 @@ // this has to be first #include - -#include +#include #include "harness/config.h" #include "harness/os.h" +#include "harness/trace.h" + +#include /* errno, strerror */ +#include /* _access_s, F_OK */ +#include +#include /* errno_t, FILE, fgets, fopen_s, fprintf*/ +#include /* _splitpath */ +#include /* strcpy, strerror, strlen, strrchr */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN64 -#define Esp Rsp -#define Eip Rip -#define Ebp Rbp +#ifndef F_OK +#define F_OK 0 #endif void dr_dprintf(char* fmt_string, ...); static int stack_nbr = 0; static char windows_program_name[1024]; +static char path_addr2line[1024]; static char dirname_buf[_MAX_DIR]; static char fname_buf[_MAX_FNAME]; -static int addr2line(char const* const program_name, void const* const addr) { - char addr2line_cmd[512] = { 0 }; +#if defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86) +#define DETHRACE_CPU_X86 1 +#elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define DETHRACE_CPU_X64 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define DETHRACE_CPU_ARM64 1 +#elif defined(__arm__) || defined(_M_ARM) +#define DETHRACE_CPU_ARM32 1 +#endif + +#if !(defined(DETHRACE_CPU_X86) || defined(DETHRACE_CPU_X64) || defined(DETHRACE_CPU_ARM32) || defined(DETHRACE_CPU_ARM64)) +#pragma message("Unsupported architecture: don't know how to StackWalk") +#endif + +static BOOL print_addr2line_address_location(HANDLE const hProcess, const DWORD64 address) { + char addr2line_cmd[1024] = { 0 }; + const char *program_name = windows_program_name; + IMAGEHLP_MODULE64 module_info; + + if (path_addr2line[0] == '\0') { + return FALSE; + } + + memset(&module_info, 0, sizeof(module_info)); + module_info.SizeOfStruct = sizeof(module_info); + if (SymGetModuleInfo64(hProcess, address, &module_info)) { + program_name = module_info.ImageName; + } + + sprintf(addr2line_cmd, "\"%.256s\" -f -p -e %.256s %lx", path_addr2line, program_name, (long int)address); + + system(addr2line_cmd); + return TRUE; +} + +static void printf_windows_message(const char *format, ...) { + va_list ap; + char win_msg[512]; + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + win_msg, sizeof(win_msg)/sizeof(*win_msg), + NULL); + size_t win_msg_len = strlen(win_msg); + while (win_msg[win_msg_len-1] == '\r' || win_msg[win_msg_len-1] == '\n' || win_msg[win_msg_len-1] == ' ') { + win_msg[win_msg_len-1] = '\0'; + win_msg_len--; + } + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fprintf(stderr, " (%s)\n", win_msg); +} + +static void init_dbghelp(HANDLE const hProcess) { + + if (!SymInitialize(hProcess, NULL, FALSE)) { + printf_windows_message("SymInitialize failed"); + } + + if (!SymRefreshModuleList(hProcess)) { + printf_windows_message("SymRefreshModuleList failed"); + } +} + +static void cleanup_dbghelp(HANDLE const hProcess) { + + SymCleanup(hProcess); +} + +static BOOL print_dbghelp_address_location(HANDLE const hProcess, const DWORD64 address) { + IMAGEHLP_MODULE64 module_info; + union { + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(CHAR)]; + SYMBOL_INFO symbol_info; + } symbol; + DWORD64 dwDisplacement; + DWORD lineColumn = 0; + IMAGEHLP_LINE64 line; + const char *image_file_name; + const char *symbol_name; + const char *file_name; + char line_number[16]; + + memset(&module_info, 0, sizeof(module_info)); + module_info.SizeOfStruct = sizeof(module_info); + if (!SymGetModuleInfo64(hProcess, address, &module_info)) { + return FALSE; + } + image_file_name = OS_Basename(module_info.ImageName); + + memset(&symbol, 0, sizeof(symbol)); + symbol.symbol_info.SizeOfStruct = sizeof(symbol.symbol_info); + symbol.symbol_info.MaxNameLen = MAX_SYM_NAME; + if (!SymFromAddr(hProcess, address, &dwDisplacement, &symbol.symbol_info)) { + return FALSE; + } + symbol_name = symbol.symbol_info.Name; + + line.SizeOfStruct = sizeof(line); + if (SymGetLineFromAddr64(hProcess, address, &lineColumn, &line)) { + file_name = line.FileName; + snprintf(line_number, sizeof(line_number), "Line %u", (unsigned int)line.LineNumber); + } else { + file_name = ""; + line_number[0] = '\0'; + } + + fprintf(stderr, "0x%lx %s!%s+0x%lx %s %s\n", (long unsigned int)address, image_file_name, symbol_name, (long unsigned int)dwDisplacement, file_name, line_number); + return TRUE; +} - sprintf(addr2line_cmd, "addr2line -f -p -e %.256s %p", program_name, addr); +static void print_address_location(HANDLE hProcess, DWORD64 address) { + IMAGEHLP_MODULE64 module_info; fprintf(stderr, "%d: ", stack_nbr++); - return system(addr2line_cmd); + if (print_dbghelp_address_location(hProcess, address)) { + return; + } + if (print_addr2line_address_location(hProcess, address)) { + return; + } + + memset(&module_info, 0, sizeof(module_info)); + module_info.SizeOfStruct = sizeof(module_info); + if (SymGetModuleInfo64(hProcess, address, &module_info)) { + fprintf(stderr, "%s 0x%lx\n", module_info.ImageName, (long unsigned int)address); + return; + } + fprintf(stderr, "0x%lx\n", (long unsigned int)address); } static void print_stacktrace(CONTEXT* context) { + HANDLE hProcess = GetCurrentProcess(); - SymInitialize(GetCurrentProcess(), 0, true); + init_dbghelp(hProcess); STACKFRAME frame = { 0 }; /* setup initial stack frame */ - frame.AddrPC.Offset = context->Eip; frame.AddrPC.Mode = AddrModeFlat; - frame.AddrStack.Offset = context->Esp; frame.AddrStack.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context->Ebp; frame.AddrFrame.Mode = AddrModeFlat; +#if defined(DETHRACE_CPU_X86) + DWORD machine_type = IMAGE_FILE_MACHINE_I386; + frame.AddrFrame.Offset = context->Ebp; + frame.AddrStack.Offset = context->Esp; + frame.AddrPC.Offset = context->Eip; +#elif defined(DETHRACE_CPU_X64) + DWORD machine_type = IMAGE_FILE_MACHINE_AMD64; + frame.AddrFrame.Offset = context->Rbp; + frame.AddrStack.Offset = context->Rsp; + frame.AddrPC.Offset = context->Rip; +#elif defined(DETHRACE_CPU_ARM32) + DWORD machine_type = IMAGE_FILE_MACHINE_ARM; + frame.AddrFrame.Offset = context->Lr; + frame.AddrStack.Offset = context->Sp; + frame.AddrPC.Offset = context->Pc; +#elif defined(DETHRACE_CPU_ARM64) + DWORD machine_type = IMAGE_FILE_MACHINE_ARM64; + frame.AddrFrame.Offset = context->Fp; + frame.AddrStack.Offset = context->Sp; + frame.AddrPC.Offset = context->Pc; +#else + fprintf(stderr, "Unsupported architecture: cannot produce a stacktrace\n"); +#endif - while (StackWalk(IMAGE_FILE_MACHINE_I386, - GetCurrentProcess(), - GetCurrentThread(), - &frame, - context, - 0, - SymFunctionTableAccess, - SymGetModuleBase, - 0)) { - addr2line(windows_program_name, (void*)frame.AddrPC.Offset); - } + while (StackWalk(machine_type, + GetCurrentProcess(), + GetCurrentThread(), + &frame, + context, + 0, + SymFunctionTableAccess, + SymGetModuleBase, + 0)) { - SymCleanup(GetCurrentProcess()); + if (frame.AddrPC.Offset == frame.AddrReturn.Offset) { + fprintf(stderr, "PC == Return Address => Possible endless callstack\n"); + break; + } + + print_address_location(hProcess, frame.AddrPC.Offset); + } } static LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS* ExceptionInfo) { + HANDLE hProcess; switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: fputs("Error: EXCEPTION_ACCESS_VIOLATION\n", stderr); @@ -138,18 +284,40 @@ static LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS* ExceptionInfo) break; } fflush(stderr); + hProcess = GetCurrentProcess(); + init_dbghelp(hProcess); /* If this is a stack overflow then we can't walk the stack, so just show where the error happened */ if (EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode) { print_stacktrace(ExceptionInfo->ContextRecord); } else { - addr2line(windows_program_name, (void*)ExceptionInfo->ContextRecord->Eip); +#if defined(DETHRACE_CPU_X86) + DWORD64 addr = (DWORD64)ExceptionInfo->ContextRecord->Eip; +#elif defined(DETHRACE_CPU_X64) + DWORD64 addr = (DWORD64)ExceptionInfo->ContextRecord->Rip; +#elif defined(DETHRACE_CPU_ARM32) || defined(DETHRACE_CPU_ARM64) + DWORD64 addr = (DWORD64)ExceptionInfo->ContextRecord->Pc; +#endif + print_address_location(hProcess, addr); } + cleanup_dbghelp(hProcess); return EXCEPTION_EXECUTE_HANDLER; } void OS_InstallSignalHandler(char* program_name) { + const char *env_addr2line; + + path_addr2line[0] = '\0'; + env_addr2line = getenv("ADDR2LINE"); + if (env_addr2line != NULL) { + errno_t e = _access_s(env_addr2line, F_OK); + if (e == 0) { + strcpy(path_addr2line, env_addr2line); + } else { + fprintf(stderr, "ADDR2LINE does not exist (%s)\n", path_addr2line); + } + } strcpy(windows_program_name, program_name); SetUnhandledExceptionFilter(windows_exception_handler); }