mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
refactor(TestHarness) Test harness stacktrace's (#9642) c0844f01b5
* added stack trace and signal handling for windows * added stack trace unwinding for mac / linux * refactored into seperate file * add file * builds for non windows * addressed some pr comments Co-authored-by: Jonathon Copeland <jcopela4@gmail.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
16c30e956a28611710d1f360448a01c380e608f8
|
||||
c0844f01b5cfea4b9d9e97ba9a8082c1081ac64f
|
||||
|
||||
293
tests/common/stacktrace.cpp
Normal file
293
tests/common/stacktrace.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2025 Rive
|
||||
*/
|
||||
|
||||
#include "common/stacktrace.hpp"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <dbghelp.h>
|
||||
#else
|
||||
#include <execinfo.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#ifdef SYS_SIGNAL_H
|
||||
#include <sys/signal.h>
|
||||
#else
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sstream>
|
||||
|
||||
static SignalFunc signalFunc;
|
||||
static int signalRecurseLevel = 0;
|
||||
|
||||
// this is outside the namespace so we can use it as if it was defined by the
|
||||
// c++ runtime
|
||||
#if defined(SYS_SIGNAL_H) || defined(_WIN32)
|
||||
const char* strsignal(int signo)
|
||||
{
|
||||
switch (signo)
|
||||
{
|
||||
case SIGINT:
|
||||
return "SIGINT";
|
||||
case SIGILL:
|
||||
return "SIGILL";
|
||||
case SIGFPE:
|
||||
return "SIGFPE";
|
||||
case SIGSEGV:
|
||||
return "SIGSEGV";
|
||||
case SIGTERM:
|
||||
return "SIGTERM";
|
||||
case SIGBREAK:
|
||||
return "SIGBREAK";
|
||||
case SIGABRT:
|
||||
return "SIGABRT";
|
||||
}
|
||||
return "Unknown Signal";
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_REDIRECT_OUTPUT
|
||||
|
||||
namespace stacktrace
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
LONG WINAPI top_level_exception_handler(PEXCEPTION_POINTERS pExceptionInfo)
|
||||
{
|
||||
std::stringstream f;
|
||||
|
||||
HANDLE process = GetCurrentProcess();
|
||||
SymInitialize(process, NULL, TRUE);
|
||||
|
||||
// StackWalk64() may modify context record passed to it, so we will
|
||||
// use a copy.
|
||||
CONTEXT context_record = *pExceptionInfo->ContextRecord;
|
||||
// Initialize stack walking.
|
||||
STACKFRAME64 stack_frame;
|
||||
memset(&stack_frame, 0, sizeof(stack_frame));
|
||||
#if defined(_WIN64)
|
||||
int machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
stack_frame.AddrPC.Offset = context_record.Rip;
|
||||
stack_frame.AddrFrame.Offset = context_record.Rbp;
|
||||
stack_frame.AddrStack.Offset = context_record.Rsp;
|
||||
#else
|
||||
int machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
stack_frame.AddrPC.Offset = context_record.Eip;
|
||||
stack_frame.AddrFrame.Offset = context_record.Ebp;
|
||||
stack_frame.AddrStack.Offset = context_record.Esp;
|
||||
#endif
|
||||
stack_frame.AddrPC.Mode = AddrModeFlat;
|
||||
stack_frame.AddrFrame.Mode = AddrModeFlat;
|
||||
stack_frame.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
||||
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
|
||||
|
||||
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
pSymbol->MaxNameLen = MAX_SYM_NAME;
|
||||
|
||||
while (StackWalk64(machine_type,
|
||||
GetCurrentProcess(),
|
||||
GetCurrentThread(),
|
||||
&stack_frame,
|
||||
&context_record,
|
||||
NULL,
|
||||
&SymFunctionTableAccess64,
|
||||
&SymGetModuleBase64,
|
||||
NULL))
|
||||
{
|
||||
|
||||
DWORD64 displacement = 0;
|
||||
|
||||
if (!SymFromAddr(process,
|
||||
(DWORD64)stack_frame.AddrPC.Offset,
|
||||
&displacement,
|
||||
pSymbol))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IMAGEHLP_MODULE64 moduleInfo;
|
||||
ZeroMemory(&moduleInfo, sizeof(IMAGEHLP_MODULE64));
|
||||
moduleInfo.SizeOfStruct = sizeof(moduleInfo);
|
||||
|
||||
if (::SymGetModuleInfo64(process, pSymbol->ModBase, &moduleInfo))
|
||||
f << moduleInfo.ModuleName << ": ";
|
||||
|
||||
f << pSymbol->Name << " + 0x" << std::hex << displacement << std::endl;
|
||||
}
|
||||
|
||||
signalFunc(f.str().c_str());
|
||||
|
||||
// match to SymInitialize
|
||||
SymCleanup(process);
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void build_stack(int signo)
|
||||
{
|
||||
CONTEXT context;
|
||||
ZeroMemory(&context, sizeof(CONTEXT));
|
||||
RtlCaptureContext(&context);
|
||||
EXCEPTION_POINTERS exceptionPointers;
|
||||
ZeroMemory(&exceptionPointers, sizeof(EXCEPTION_POINTERS));
|
||||
exceptionPointers.ContextRecord = &context;
|
||||
top_level_exception_handler(&exceptionPointers);
|
||||
}
|
||||
|
||||
static void handle_signal(int signo) noexcept
|
||||
{
|
||||
// this is because we can accidently spam the signal handler if we can't
|
||||
// communicate with the server
|
||||
if (signo == SIGABRT)
|
||||
{
|
||||
if (++signalRecurseLevel > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Received signal %i (\"%s\")\n", signo, strsignal(signo));
|
||||
|
||||
build_stack(signo);
|
||||
// set the signal handler to the original
|
||||
signal(signo, SIG_DFL);
|
||||
// re raise the signal
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
void replace_signal_handlers(SignalFunc inSignalFunc,
|
||||
ExitFunc atExitFunc) noexcept
|
||||
{
|
||||
assert(inSignalFunc);
|
||||
assert(atExitFunc);
|
||||
|
||||
// signals can only use global vars
|
||||
signalFunc = inSignalFunc;
|
||||
|
||||
SetUnhandledExceptionFilter(top_level_exception_handler);
|
||||
|
||||
for (int i = 1; i <= SIGTERM; ++i)
|
||||
{
|
||||
signal(i, handle_signal);
|
||||
}
|
||||
// for windows SIGABRT is a higher number then SIGTERM, so make sure to
|
||||
// intercept it as well
|
||||
signal(SIGABRT, handle_signal);
|
||||
|
||||
atexit(atExitFunc);
|
||||
}
|
||||
#else
|
||||
#ifdef RIVE_ANDROID
|
||||
// android is a little more complicated in that it requires specific compiler
|
||||
// flags that i don't want to force us into, so we just do it the old way for
|
||||
// now
|
||||
static void handle_signal(int sigNum, siginfo_t* signalInfo, void* userContext)
|
||||
{
|
||||
printf("Received signal %i (\"%s\")\n", sigNum, strsignal(sigNum));
|
||||
signal(sigNum, SIG_DFL);
|
||||
signalFunc(strsignal(sigNum));
|
||||
raise(sigNum);
|
||||
}
|
||||
|
||||
#else // we are unix
|
||||
|
||||
static void handle_signal(int sigNum, siginfo_t* signalInfo, void* userContext)
|
||||
{
|
||||
printf("Received signal %i (\"%s\")\n", sigNum, strsignal(sigNum));
|
||||
// this is because we can accidently spam the signal handler if we can't
|
||||
// communicate with the server
|
||||
if (sigNum == SIGABRT)
|
||||
{
|
||||
if (++signalRecurseLevel > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream f;
|
||||
|
||||
void* stacktrace[1024];
|
||||
char** symbols;
|
||||
char stringBuff[1024];
|
||||
// this is technically unsafe since it uses malloc behind the scene but it's
|
||||
// way simpler then the alternative and this is just for testing anyway so
|
||||
// it's fine
|
||||
int numFrames = backtrace(stacktrace, 1024);
|
||||
symbols = backtrace_symbols(stacktrace, numFrames);
|
||||
for (int i = 1; i < numFrames; i++)
|
||||
{
|
||||
Dl_info dlInfo;
|
||||
if (dladdr(stacktrace[i], &dlInfo))
|
||||
{
|
||||
char* demangled = NULL;
|
||||
int status;
|
||||
demangled = abi::__cxa_demangle(dlInfo.dli_sname, NULL, 0, &status);
|
||||
snprintf(stringBuff,
|
||||
sizeof(stringBuff),
|
||||
"%d %s\n",
|
||||
i,
|
||||
status == 0 ? demangled : dlInfo.dli_sname);
|
||||
free(demangled);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(stringBuff,
|
||||
sizeof(stringBuff),
|
||||
"%d %p\n",
|
||||
i,
|
||||
stacktrace[i]);
|
||||
}
|
||||
|
||||
f << stringBuff << symbols[i] << "\n";
|
||||
}
|
||||
|
||||
free(symbols);
|
||||
|
||||
// send stack to server
|
||||
signalFunc(f.str().c_str());
|
||||
|
||||
exit(sigNum);
|
||||
}
|
||||
#endif
|
||||
void replace_signal_handlers(SignalFunc inSignalFunc,
|
||||
ExitFunc atExitFunc) noexcept
|
||||
{
|
||||
assert(inSignalFunc);
|
||||
assert(atExitFunc);
|
||||
|
||||
signalFunc = inSignalFunc;
|
||||
|
||||
struct sigaction action;
|
||||
struct sigaction oldAction;
|
||||
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
#if defined(__LP64__) && defined(__APPLE__)
|
||||
action.sa_flags |= SA_64REGSET;
|
||||
#endif
|
||||
action.sa_sigaction = &handle_signal;
|
||||
|
||||
for (int i = 1; i <= SIGTERM; ++i)
|
||||
{
|
||||
sigaction(i, &action, &oldAction);
|
||||
}
|
||||
|
||||
atexit(atExitFunc);
|
||||
}
|
||||
|
||||
#endif // defined _WIN32
|
||||
|
||||
#else // NO_REDIRECT_OUTPUT is defined
|
||||
void replace_signal_handlers(SignalFunc signalFunc,
|
||||
ExitFunc atExitFunc) noexcept
|
||||
{}
|
||||
#endif // ifdef NO_REDIRECT_OUTPUT
|
||||
};
|
||||
8
tests/common/stacktrace.hpp
Normal file
8
tests/common/stacktrace.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
typedef void (*SignalFunc)(const char*);
|
||||
typedef void (*ExitFunc)(void);
|
||||
|
||||
namespace stacktrace
|
||||
{
|
||||
void replace_signal_handlers(SignalFunc signalFunc,
|
||||
ExitFunc atExitFunc) noexcept;
|
||||
} // namespace stacktrace
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
#include "common/test_harness.hpp"
|
||||
|
||||
#include "common/stacktrace.hpp"
|
||||
#include "rive/rive_types.hpp"
|
||||
#include "rive/math/math_types.hpp"
|
||||
#include "tcp_client.hpp"
|
||||
@@ -11,16 +11,6 @@
|
||||
#include "zlib.h"
|
||||
#include <vector>
|
||||
|
||||
#ifdef SYS_SIGNAL_H
|
||||
#include <sys/signal.h>
|
||||
const char* strsignal(int)
|
||||
{
|
||||
return "(strsignal) not suported on this platform";
|
||||
}
|
||||
#else
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(NO_REDIRECT_OUTPUT)
|
||||
#include <io.h>
|
||||
static int pipe(int pipefd[2]) { return _pipe(pipefd, 65536, 0); }
|
||||
@@ -41,38 +31,6 @@ constexpr static uint32_t REQUEST_TYPE_PRINT_MESSAGE = 5;
|
||||
constexpr static uint32_t REQUEST_TYPE_DISCONNECT = 6;
|
||||
constexpr static uint32_t REQUEST_TYPE_APPLICATION_CRASH = 7;
|
||||
|
||||
#ifdef _WIN32
|
||||
const char* strsignal(int signo)
|
||||
{
|
||||
switch (signo)
|
||||
{
|
||||
case SIGINT:
|
||||
return "SIGINT";
|
||||
case SIGILL:
|
||||
return "SIGILL";
|
||||
case SIGFPE:
|
||||
return "SIGFPE";
|
||||
case SIGSEGV:
|
||||
return "SIGSEGV";
|
||||
case SIGTERM:
|
||||
return "SIGTERM";
|
||||
case SIGBREAK:
|
||||
return "SIGBREAK";
|
||||
case SIGABRT:
|
||||
return "SIGABRT";
|
||||
}
|
||||
return "Unknown Signal";
|
||||
}
|
||||
#endif
|
||||
#ifndef NO_SIGNAL_FORWARD
|
||||
static void sig_handler(int signo)
|
||||
{
|
||||
printf("Received signal %i (\"%s\")\n", signo, strsignal(signo));
|
||||
signal(signo, SIG_DFL);
|
||||
TestHarness::Instance().onApplicationCrash(strsignal(signo));
|
||||
abort();
|
||||
}
|
||||
|
||||
static void check_early_exit()
|
||||
{
|
||||
if (TestHarness::Instance().initialized())
|
||||
@@ -81,7 +39,11 @@ static void check_early_exit()
|
||||
TestHarness::Instance().onApplicationCrash("Early exit.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void signal_wraper(const char* msg)
|
||||
{
|
||||
TestHarness::Instance().onApplicationCrash(msg);
|
||||
}
|
||||
|
||||
TestHarness& TestHarness::Instance()
|
||||
{
|
||||
@@ -91,17 +53,7 @@ TestHarness& TestHarness::Instance()
|
||||
|
||||
TestHarness::TestHarness()
|
||||
{
|
||||
#ifndef NO_SIGNAL_FORWARD
|
||||
// Forward signals to the test harness.
|
||||
for (int i = 1; i <= SIGTERM; ++i)
|
||||
{
|
||||
signal(i, sig_handler);
|
||||
}
|
||||
|
||||
// Check for if the app exits early (before calling
|
||||
// TestHarness::shutdown()).
|
||||
atexit(check_early_exit);
|
||||
#endif
|
||||
stacktrace::replace_signal_handlers(signal_wraper, check_early_exit);
|
||||
}
|
||||
|
||||
void TestHarness::init(std::unique_ptr<TCPClient> tcpClient,
|
||||
|
||||
@@ -197,7 +197,7 @@ function rive_tools_project(name, project_kind)
|
||||
libdirs({
|
||||
RIVE_RUNTIME_DIR .. '/skia/dependencies/glfw_build/src/Release',
|
||||
})
|
||||
links({ 'glfw3', 'opengl32', 'd3d11', 'd3d12', 'dxguid', 'dxgi', 'd3dcompiler', 'ws2_32' })
|
||||
links({ 'glfw3', 'opengl32', 'd3d11', 'd3d12', 'dxguid', 'dxgi', 'Dbghelp', 'd3dcompiler', 'ws2_32' })
|
||||
end
|
||||
|
||||
filter({ 'kind:ConsoleApp or SharedLib or WindowedApp', 'system:macosx' })
|
||||
|
||||
Reference in New Issue
Block a user