QTest: move crash-handling code out into qtestcrashhandler_p.h
In preparation for reusing it in Qt Quick, which currently doesn't print backtraces upon crashes. Task-number: QTQAINFRA-6146 Change-Id: Ib0384f514b348a398f53529ff3bcc7d4ac2daba7 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>bb10
parent
baa44b9ddf
commit
c0014becca
|
|
@ -40,6 +40,7 @@ qt_internal_add_module(Test
|
|||
qtestblacklist.cpp qtestblacklist_p.h
|
||||
qtestcase.cpp qtestcase.h qtestcase_p.h
|
||||
qtestcoreelement_p.h
|
||||
qtestcrashhandler.cpp qtestcrashhandler_p.h
|
||||
qtestdata.cpp qtestdata.h
|
||||
qtestelement.cpp qtestelement_p.h
|
||||
qtestelementattribute.cpp qtestelementattribute_p.h
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#endif // QT_CONFIG(batch_test_support)
|
||||
#include <QtTest/private/cycle_p.h>
|
||||
#include <QtTest/private/qtestblacklist_p.h>
|
||||
#include <QtTest/private/qtestcrashhandler_p.h>
|
||||
#if defined(HAVE_XCTEST)
|
||||
#include <QtTest/private/qxctestlogger_p.h>
|
||||
#endif
|
||||
|
|
@ -129,298 +130,6 @@ using namespace Qt::StringLiterals;
|
|||
using QtMiscUtils::toHexUpper;
|
||||
using QtMiscUtils::fromHex;
|
||||
|
||||
namespace {
|
||||
enum DebuggerProgram { None, Gdb, Lldb };
|
||||
|
||||
#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
|
||||
static struct iovec IoVec(struct iovec vec)
|
||||
{
|
||||
return vec;
|
||||
}
|
||||
static struct iovec IoVec(const char *str)
|
||||
{
|
||||
struct iovec r = {};
|
||||
r.iov_base = const_cast<char *>(str);
|
||||
r.iov_len = strlen(str);
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename... Args> static ssize_t writeToStderr(Args &&... args)
|
||||
{
|
||||
struct iovec vec[] = { IoVec(std::forward<Args>(args))... };
|
||||
return ::writev(STDERR_FILENO, vec, std::size(vec));
|
||||
}
|
||||
|
||||
// async-signal-safe conversion from int to string
|
||||
struct AsyncSafeIntBuffer
|
||||
{
|
||||
// digits10 + 1 for all possible digits
|
||||
// +1 for the sign
|
||||
// +1 for the terminating null
|
||||
static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3;
|
||||
std::array<char, Digits10> array;
|
||||
constexpr AsyncSafeIntBuffer() : array{} {} // initializes array
|
||||
AsyncSafeIntBuffer(Qt::Initialization) {} // leaves array uninitialized
|
||||
};
|
||||
|
||||
static struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized)
|
||||
{
|
||||
char *ptr = result.array.data();
|
||||
if (false) {
|
||||
#ifdef __cpp_lib_to_chars
|
||||
} else if (auto r = std::to_chars(ptr, ptr + result.array.size(), n, 10); r.ec == std::errc{}) {
|
||||
ptr = r.ptr;
|
||||
#endif
|
||||
} else {
|
||||
// handle the sign
|
||||
if (n < 0) {
|
||||
*ptr++ = '-';
|
||||
n = -n;
|
||||
}
|
||||
|
||||
// find the highest power of the base that is less than this number
|
||||
static constexpr int StartingDivider = ([]() {
|
||||
int divider = 1;
|
||||
for (int i = 0; i < std::numeric_limits<int>::digits10; ++i)
|
||||
divider *= 10;
|
||||
return divider;
|
||||
}());
|
||||
int divider = StartingDivider;
|
||||
while (divider && n < divider)
|
||||
divider /= 10;
|
||||
|
||||
// now convert to string
|
||||
while (divider > 1) {
|
||||
int quot = n / divider;
|
||||
n = n % divider;
|
||||
divider /= 10;
|
||||
*ptr++ = quot + '0';
|
||||
}
|
||||
*ptr++ = n + '0';
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
// this isn't necessary, it just helps in the debugger
|
||||
*ptr = '\0';
|
||||
#endif
|
||||
struct iovec r;
|
||||
r.iov_base = result.array.data();
|
||||
r.iov_len = ptr - result.array.data();
|
||||
return r;
|
||||
};
|
||||
#elif defined(Q_OS_WIN)
|
||||
// Windows doesn't need to be async-safe
|
||||
template <typename... Args> static void writeToStderr(Args &&... args)
|
||||
{
|
||||
(std::cerr << ... << args);
|
||||
}
|
||||
|
||||
static std::string asyncSafeToString(int n)
|
||||
{
|
||||
return std::to_string(n);
|
||||
}
|
||||
#endif // defined(Q_OS_UNIX)
|
||||
} // unnamed namespace
|
||||
|
||||
[[maybe_unused]] static void blockUnixSignals();
|
||||
|
||||
static bool alreadyDebugging()
|
||||
{
|
||||
#if defined(Q_OS_LINUX)
|
||||
int fd = open("/proc/self/status", O_RDONLY);
|
||||
if (fd == -1)
|
||||
return false;
|
||||
char buffer[2048];
|
||||
ssize_t size = read(fd, buffer, sizeof(buffer) - 1);
|
||||
if (size == -1) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
buffer[size] = 0;
|
||||
const char tracerPidToken[] = "\nTracerPid:";
|
||||
char *tracerPid = strstr(buffer, tracerPidToken);
|
||||
if (!tracerPid) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
tracerPid += sizeof(tracerPidToken);
|
||||
long int pid = strtol(tracerPid, &tracerPid, 10);
|
||||
close(fd);
|
||||
return pid != 0;
|
||||
#elif defined(Q_OS_WIN)
|
||||
return IsDebuggerPresent();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
// Check if there is an exception handler for the process:
|
||||
mach_msg_type_number_t portCount = 0;
|
||||
exception_mask_t masks[EXC_TYPES_COUNT];
|
||||
mach_port_t ports[EXC_TYPES_COUNT];
|
||||
exception_behavior_t behaviors[EXC_TYPES_COUNT];
|
||||
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
|
||||
exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
|
||||
kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &portCount,
|
||||
ports, behaviors, flavors);
|
||||
if (result == KERN_SUCCESS) {
|
||||
for (mach_msg_type_number_t portIndex = 0; portIndex < portCount; ++portIndex) {
|
||||
if (MACH_PORT_VALID(ports[portIndex])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
// TODO
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool hasSystemCrashReporter()
|
||||
{
|
||||
#if defined(Q_OS_MACOS)
|
||||
return QTestPrivate::macCrashReporterWillShowDialog();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void maybeDisableCoreDump()
|
||||
{
|
||||
#ifdef RLIMIT_CORE
|
||||
bool ok = false;
|
||||
const int disableCoreDump = qEnvironmentVariableIntValue("QTEST_DISABLE_CORE_DUMP", &ok);
|
||||
if (ok && disableCoreDump) {
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
if (setrlimit(RLIMIT_CORE, &limit) != 0)
|
||||
qWarning("Failed to disable core dumps: %d", errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static DebuggerProgram debugger = None;
|
||||
static void prepareStackTrace()
|
||||
{
|
||||
|
||||
bool ok = false;
|
||||
const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
|
||||
if (ok && disableStackDump)
|
||||
return;
|
||||
|
||||
if (hasSystemCrashReporter())
|
||||
return;
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
|
||||
std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
|
||||
if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
|
||||
return; // LLDB will fail to provide a valid stack trace
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// like QStandardPaths::findExecutable(), but simpler
|
||||
auto hasExecutable = [](const char *execname) {
|
||||
std::string candidate;
|
||||
std::string path;
|
||||
if (const char *p = getenv("PATH"); p && *p)
|
||||
path = p;
|
||||
else
|
||||
path = _PATH_DEFPATH;
|
||||
for (const char *p = std::strtok(&path[0], ":'"); p; p = std::strtok(nullptr, ":")) {
|
||||
candidate = p;
|
||||
candidate += '/';
|
||||
candidate += execname;
|
||||
if (QT_ACCESS(candidate.data(), X_OK) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
static constexpr DebuggerProgram debuggerSearchOrder[] = {
|
||||
# if defined(Q_OS_QNX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
Gdb, Lldb
|
||||
# else
|
||||
Lldb, Gdb
|
||||
# endif
|
||||
};
|
||||
for (DebuggerProgram candidate : debuggerSearchOrder) {
|
||||
switch (candidate) {
|
||||
case None:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
case Gdb:
|
||||
if (hasExecutable("gdb")) {
|
||||
debugger = Gdb;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Lldb:
|
||||
if (hasExecutable("lldb")) {
|
||||
debugger = Lldb;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // Q_OS_UNIX
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
|
||||
static void printTestRunTime()
|
||||
{
|
||||
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
||||
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
||||
const char *const name = QTest::currentTestFunction();
|
||||
writeToStderr("\n ", name ? name : "[Non-test]",
|
||||
" function time: ", asyncSafeToString(msecsFunctionTime),
|
||||
"ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
|
||||
}
|
||||
|
||||
static void generateStackTrace()
|
||||
{
|
||||
if (debugger == None || alreadyDebugging())
|
||||
return;
|
||||
|
||||
# if defined(Q_OS_UNIX) && !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_VXWORKS)
|
||||
writeToStderr("\n=== Stack trace ===\n");
|
||||
|
||||
// execlp() requires null-termination, so call the default constructor
|
||||
AsyncSafeIntBuffer pidbuffer;
|
||||
asyncSafeToString(getpid(), std::move(pidbuffer));
|
||||
|
||||
// Note: POSIX.1-2001 still has fork() in the list of async-safe functions,
|
||||
// but in a future edition, it might be removed. It would be safer to wake
|
||||
// up a babysitter thread to launch the debugger.
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// child process
|
||||
(void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
|
||||
|
||||
switch (debugger) {
|
||||
case None:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
case Gdb:
|
||||
execlp("gdb", "gdb", "--nx", "--batch", "-ex", "thread apply all bt",
|
||||
"--pid", pidbuffer.array.data(), nullptr);
|
||||
break;
|
||||
case Lldb:
|
||||
execlp("lldb", "lldb", "--no-lldbinit", "--batch", "-o", "bt all",
|
||||
"--attach-pid", pidbuffer.array.data(), nullptr);
|
||||
break;
|
||||
}
|
||||
_exit(1);
|
||||
} else if (pid < 0) {
|
||||
writeToStderr("Failed to start debugger.\n");
|
||||
} else {
|
||||
int ret;
|
||||
QT_EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
|
||||
}
|
||||
|
||||
writeToStderr("=== End of stack trace ===\n");
|
||||
# endif // Q_OS_UNIX && !Q_OS_WASM && !Q_OS_INTEGRITY && !Q_OS_VXWORKS
|
||||
}
|
||||
#endif // !defined(Q_OS_WASM) || QT_CONFIG(thread)
|
||||
|
||||
static bool installCoverageTool(const char * appname, const char * testname)
|
||||
{
|
||||
#if defined(__COVERAGESCANNER__) && !QT_CONFIG(testlib_selfcover)
|
||||
|
|
@ -1526,7 +1235,7 @@ public:
|
|||
|
||||
void run() override
|
||||
{
|
||||
blockUnixSignals();
|
||||
CrashHandler::blockUnixSignals();
|
||||
auto locker = qt_unique_lock(mutex);
|
||||
expecting.store(TestFunctionStart, std::memory_order_release);
|
||||
waitCondition.notify_all();
|
||||
|
|
@ -1541,8 +1250,8 @@ public:
|
|||
case TestFunctionEnd:
|
||||
if (Q_UNLIKELY(!waitFor(locker, e))) {
|
||||
fflush(stderr);
|
||||
printTestRunTime();
|
||||
generateStackTrace();
|
||||
CrashHandler::printTestRunTime();
|
||||
CrashHandler::generateStackTrace();
|
||||
qFatal("Test function timed out");
|
||||
}
|
||||
}
|
||||
|
|
@ -1986,7 +1695,7 @@ void TestMethods::invokeTests(QObject *testObject) const
|
|||
invokeTestMethodIfValid(m_initTestCaseDataMethod, testObject);
|
||||
|
||||
std::optional<WatchDog> watchDog = std::nullopt;
|
||||
if (!alreadyDebugging()
|
||||
if (!CrashHandler::alreadyDebugging()
|
||||
#if QT_CONFIG(valgrind)
|
||||
&& QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindChildProcess
|
||||
#endif
|
||||
|
|
@ -2043,442 +1752,6 @@ bool reportResult(bool success, qxp::function_ref<const char *()> lhs,
|
|||
|
||||
} // namespace QTest
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
namespace {
|
||||
// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
|
||||
class DebugSymbolResolver
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
|
||||
public:
|
||||
struct Symbol {
|
||||
Symbol() : name(nullptr), address(0) {}
|
||||
|
||||
const char *name; // Must be freed by caller.
|
||||
DWORD64 address;
|
||||
};
|
||||
|
||||
explicit DebugSymbolResolver(HANDLE process);
|
||||
~DebugSymbolResolver() { cleanup(); }
|
||||
|
||||
bool isValid() const { return m_symFromAddr; }
|
||||
|
||||
Symbol resolveSymbol(DWORD64 address) const;
|
||||
|
||||
private:
|
||||
// typedefs from DbgHelp.h/.dll
|
||||
struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
|
||||
ULONG SizeOfStruct;
|
||||
ULONG TypeIndex; // Type Index of symbol
|
||||
ULONG64 Reserved[2];
|
||||
ULONG Index;
|
||||
ULONG Size;
|
||||
ULONG64 ModBase; // Base Address of module comtaining this symbol
|
||||
ULONG Flags;
|
||||
ULONG64 Value; // Value of symbol, ValuePresent should be 1
|
||||
ULONG64 Address; // Address of symbol including base address of module
|
||||
ULONG Register; // register holding value or pointer to value
|
||||
ULONG Scope; // scope of the symbol
|
||||
ULONG Tag; // pdb classification
|
||||
ULONG NameLen; // Actual length of name
|
||||
ULONG MaxNameLen;
|
||||
CHAR Name[1]; // Name of symbol
|
||||
};
|
||||
|
||||
typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
|
||||
typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);
|
||||
|
||||
void cleanup();
|
||||
|
||||
const HANDLE m_process;
|
||||
HMODULE m_dbgHelpLib;
|
||||
SymFromAddrType m_symFromAddr;
|
||||
};
|
||||
|
||||
void DebugSymbolResolver::cleanup()
|
||||
{
|
||||
if (m_dbgHelpLib)
|
||||
FreeLibrary(m_dbgHelpLib);
|
||||
m_dbgHelpLib = 0;
|
||||
m_symFromAddr = nullptr;
|
||||
}
|
||||
|
||||
DebugSymbolResolver::DebugSymbolResolver(HANDLE process)
|
||||
: m_process(process), m_dbgHelpLib(0), m_symFromAddr(nullptr)
|
||||
{
|
||||
bool success = false;
|
||||
m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
|
||||
if (m_dbgHelpLib) {
|
||||
SymInitializeType symInitialize = reinterpret_cast<SymInitializeType>(
|
||||
reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize")));
|
||||
m_symFromAddr = reinterpret_cast<SymFromAddrType>(
|
||||
reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr")));
|
||||
success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE);
|
||||
}
|
||||
if (!success)
|
||||
cleanup();
|
||||
}
|
||||
|
||||
DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const
|
||||
{
|
||||
// reserve additional buffer where SymFromAddr() will store the name
|
||||
struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
|
||||
enum { symbolNameLength = 255 };
|
||||
|
||||
char name[symbolNameLength + 1];
|
||||
};
|
||||
|
||||
Symbol result;
|
||||
if (!isValid())
|
||||
return result;
|
||||
NamedSymbolInfo symbolBuffer;
|
||||
memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
|
||||
symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength;
|
||||
symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO);
|
||||
if (!m_symFromAddr(m_process, address, 0, &symbolBuffer))
|
||||
return result;
|
||||
result.name = qstrdup(symbolBuffer.Name);
|
||||
result.address = symbolBuffer.Address;
|
||||
return result;
|
||||
}
|
||||
|
||||
class WindowsFaultHandler
|
||||
{
|
||||
public:
|
||||
WindowsFaultHandler()
|
||||
{
|
||||
# if !defined(Q_CC_MINGW)
|
||||
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
|
||||
# endif
|
||||
SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
|
||||
SetUnhandledExceptionFilter(windowsFaultHandler);
|
||||
}
|
||||
|
||||
private:
|
||||
static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
|
||||
{
|
||||
enum { maxStackFrames = 100 };
|
||||
char appName[MAX_PATH];
|
||||
if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
|
||||
appName[0] = 0;
|
||||
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
||||
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
||||
const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
|
||||
fprintf(stderr, "A crash occurred in %s.\n", appName);
|
||||
if (const char *name = QTest::currentTestFunction())
|
||||
fprintf(stderr, "While testing %s\n", name);
|
||||
fprintf(stderr, "Function time: %dms Total time: %dms\n\n"
|
||||
"Exception address: 0x%p\n"
|
||||
"Exception code : 0x%lx\n",
|
||||
msecsFunctionTime, msecsTotalTime, exceptionAddress,
|
||||
exInfo->ExceptionRecord->ExceptionCode);
|
||||
|
||||
DebugSymbolResolver resolver(GetCurrentProcess());
|
||||
if (resolver.isValid()) {
|
||||
DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
|
||||
if (exceptionSymbol.name) {
|
||||
fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name);
|
||||
delete [] exceptionSymbol.name;
|
||||
}
|
||||
void *stack[maxStackFrames];
|
||||
fputs("\nStack:\n", stderr);
|
||||
const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
|
||||
for (unsigned f = 0; f < frameCount; ++f) {
|
||||
DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
|
||||
if (symbol.name) {
|
||||
fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
|
||||
delete [] symbol.name;
|
||||
} else {
|
||||
fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fputc('\n', stderr);
|
||||
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
};
|
||||
} // unnamed namespace
|
||||
using FatalSignalHandler = WindowsFaultHandler;
|
||||
|
||||
inline void blockUnixSignals()
|
||||
{
|
||||
// Windows does have C signals, but doesn't use them for the purposes we're
|
||||
// talking about here
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
|
||||
namespace {
|
||||
class FatalSignalHandler
|
||||
{
|
||||
public:
|
||||
# define OUR_SIGNALS(F) \
|
||||
F(HUP) \
|
||||
F(INT) \
|
||||
F(QUIT) \
|
||||
F(ABRT) \
|
||||
F(ILL) \
|
||||
F(BUS) \
|
||||
F(FPE) \
|
||||
F(SEGV) \
|
||||
F(PIPE) \
|
||||
F(TERM) \
|
||||
/**/
|
||||
# define CASE_LABEL(S) case SIG ## S: return QT_STRINGIFY(S);
|
||||
# define ENUMERATE_SIGNALS(S) SIG ## S,
|
||||
static const char *signalName(int signum) noexcept
|
||||
{
|
||||
switch (signum) {
|
||||
OUR_SIGNALS(CASE_LABEL)
|
||||
}
|
||||
|
||||
# if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2)
|
||||
// get the other signal names from glibc 2.32
|
||||
// (accessing the sys_sigabbrev variable causes linker warnings)
|
||||
if (const char *p = sigabbrev_np(signum))
|
||||
return p;
|
||||
# endif
|
||||
return "???";
|
||||
}
|
||||
static constexpr std::array fatalSignals = {
|
||||
OUR_SIGNALS(ENUMERATE_SIGNALS)
|
||||
};
|
||||
# undef CASE_LABEL
|
||||
# undef ENUMERATE_SIGNALS
|
||||
|
||||
static constexpr std::array crashingSignals = {
|
||||
// Crash signals are special, because if we return from the handler
|
||||
// without adjusting the machine state, the same instruction that
|
||||
// originally caused the crash will get re-executed and will thus cause
|
||||
// the same crash again. This is useful if our parent process logs the
|
||||
// exit result or if core dumps are enabled: the core file will point
|
||||
// to the actual instruction that crashed.
|
||||
SIGILL, SIGBUS, SIGFPE, SIGSEGV
|
||||
};
|
||||
using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>;
|
||||
|
||||
FatalSignalHandler()
|
||||
{
|
||||
pauseOnCrash = qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH");
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = SIG_DFL;
|
||||
oldActions().fill(act);
|
||||
|
||||
// Remove the handler after it is invoked.
|
||||
act.sa_flags = SA_RESETHAND | setupAlternateStack();
|
||||
|
||||
# ifdef SA_SIGINFO
|
||||
act.sa_flags |= SA_SIGINFO;
|
||||
act.sa_sigaction = FatalSignalHandler::actionHandler;
|
||||
# else
|
||||
act.sa_handler = FatalSignalHandler::regularHandler;
|
||||
# endif
|
||||
|
||||
// Block all fatal signals in our signal handler so we don't try to close
|
||||
// the testlog twice.
|
||||
sigemptyset(&act.sa_mask);
|
||||
for (int signal : fatalSignals)
|
||||
sigaddset(&act.sa_mask, signal);
|
||||
|
||||
for (size_t i = 0; i < fatalSignals.size(); ++i)
|
||||
sigaction(fatalSignals[i], &act, &oldActions()[i]);
|
||||
}
|
||||
|
||||
~FatalSignalHandler()
|
||||
{
|
||||
// Restore the default signal handlers in place of ours.
|
||||
// If ours has been replaced, leave the replacement alone.
|
||||
auto isOurs = [](const struct sigaction &old) {
|
||||
# ifdef SA_SIGINFO
|
||||
return (old.sa_flags & SA_SIGINFO) && old.sa_sigaction == FatalSignalHandler::actionHandler;
|
||||
# else
|
||||
return old.sa_handler == FatalSignalHandler::regularHandler;
|
||||
# endif
|
||||
};
|
||||
struct sigaction action;
|
||||
|
||||
for (size_t i = 0; i < fatalSignals.size(); ++i) {
|
||||
struct sigaction &act = oldActions()[i];
|
||||
if (act.sa_flags == 0 && act.sa_handler == SIG_DFL)
|
||||
continue; // Already the default
|
||||
if (sigaction(fatalSignals[i], nullptr, &action))
|
||||
continue; // Failed to query present handler
|
||||
if (isOurs(action))
|
||||
sigaction(fatalSignals[i], &act, nullptr);
|
||||
}
|
||||
|
||||
freeAlternateStack();
|
||||
}
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY_MOVE(FatalSignalHandler)
|
||||
|
||||
static OldActionsArray &oldActions()
|
||||
{
|
||||
Q_CONSTINIT static OldActionsArray oldActions {};
|
||||
return oldActions;
|
||||
}
|
||||
|
||||
auto alternateStackSize()
|
||||
{
|
||||
struct R { size_t size, pageSize; };
|
||||
static constexpr size_t MinStackSize = 32 * 1024;
|
||||
size_t pageSize = sysconf(_SC_PAGESIZE);
|
||||
size_t size = SIGSTKSZ;
|
||||
if (size < MinStackSize) {
|
||||
size = MinStackSize;
|
||||
} else {
|
||||
// round up to a page
|
||||
size = (size + pageSize - 1) & -pageSize;
|
||||
}
|
||||
|
||||
return R{ size + pageSize, pageSize };
|
||||
}
|
||||
|
||||
int setupAlternateStack()
|
||||
{
|
||||
// tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
|
||||
// unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
|
||||
# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
|
||||
// Let the signal handlers use an alternate stack
|
||||
// This is necessary if SIGSEGV is to catch a stack overflow
|
||||
auto r = alternateStackSize();
|
||||
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
||||
# ifdef MAP_STACK
|
||||
flags |= MAP_STACK;
|
||||
# endif
|
||||
alternateStackBase = mmap(nullptr, r.size, PROT_READ | PROT_WRITE, flags, -1, 0);
|
||||
if (alternateStackBase == MAP_FAILED)
|
||||
return 0;
|
||||
|
||||
// mark the bottom page inaccessible, to catch a handler stack overflow
|
||||
(void) mprotect(alternateStackBase, r.pageSize, PROT_NONE);
|
||||
|
||||
stack_t stack;
|
||||
stack.ss_flags = 0;
|
||||
stack.ss_size = r.size - r.pageSize;
|
||||
stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
|
||||
sigaltstack(&stack, nullptr);
|
||||
return SA_ONSTACK;
|
||||
# else
|
||||
return 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
void freeAlternateStack()
|
||||
{
|
||||
# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
|
||||
if (alternateStackBase != MAP_FAILED) {
|
||||
stack_t stack = {};
|
||||
stack.ss_flags = SS_DISABLE;
|
||||
sigaltstack(&stack, nullptr);
|
||||
munmap(alternateStackBase, alternateStackSize().size);
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
template <typename T> static
|
||||
std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1>
|
||||
printSentSignalInfo(T *info)
|
||||
{
|
||||
writeToStderr(" sent by PID ", asyncSafeToString(info->si_pid),
|
||||
" UID ", asyncSafeToString(info->si_uid));
|
||||
}
|
||||
static void printSentSignalInfo(...) {}
|
||||
|
||||
template <typename T> static
|
||||
std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info)
|
||||
{
|
||||
using HexString = std::array<char, sizeof(quintptr) * 2>;
|
||||
auto toHexString = [](quintptr u, HexString &&r = {}) {
|
||||
int shift = sizeof(quintptr) * 8 - 4;
|
||||
for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4)
|
||||
r[i] = QtMiscUtils::toHexLower(u >> shift);
|
||||
struct iovec vec;
|
||||
vec.iov_base = r.data();
|
||||
vec.iov_len = r.size();
|
||||
return vec;
|
||||
};
|
||||
writeToStderr(", code ", asyncSafeToString(info->si_code),
|
||||
", for address 0x", toHexString(quintptr(info->si_addr)));
|
||||
}
|
||||
static void printCrashingSignalInfo(...) {}
|
||||
|
||||
static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */)
|
||||
{
|
||||
writeToStderr("Received signal ", asyncSafeToString(signum),
|
||||
" (SIG", signalName(signum), ")");
|
||||
|
||||
bool isCrashingSignal =
|
||||
std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end();
|
||||
if (isCrashingSignal && (!info || info->si_code <= 0))
|
||||
isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
|
||||
if (isCrashingSignal)
|
||||
printCrashingSignalInfo(info);
|
||||
else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
|
||||
printSentSignalInfo(info);
|
||||
|
||||
printTestRunTime();
|
||||
if (signum != SIGINT) {
|
||||
generateStackTrace();
|
||||
if (pauseOnCrash) {
|
||||
writeToStderr("Pausing process ", asyncSafeToString(getpid()),
|
||||
" for debugging\n");
|
||||
raise(SIGSTOP);
|
||||
}
|
||||
}
|
||||
|
||||
// chain back to the previous handler, if any
|
||||
for (size_t i = 0; i < fatalSignals.size(); ++i) {
|
||||
struct sigaction &act = oldActions()[i];
|
||||
if (signum != fatalSignals[i])
|
||||
continue;
|
||||
|
||||
// restore the handler (if SA_RESETHAND hasn't done the job for us)
|
||||
if (SA_RESETHAND == 0 || act.sa_handler != SIG_DFL || act.sa_flags)
|
||||
(void) sigaction(signum, &act, nullptr);
|
||||
|
||||
if (!isCrashingSignal)
|
||||
raise(signum);
|
||||
|
||||
// signal is blocked, so it'll be delivered when we return
|
||||
return;
|
||||
}
|
||||
|
||||
// we shouldn't reach here!
|
||||
std::abort();
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void regularHandler(int signum)
|
||||
{
|
||||
actionHandler(signum, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void *alternateStackBase = MAP_FAILED;
|
||||
static bool pauseOnCrash;
|
||||
};
|
||||
bool FatalSignalHandler::pauseOnCrash = false;
|
||||
} // unnamed namespace
|
||||
|
||||
inline void blockUnixSignals()
|
||||
{
|
||||
// Block most Unix signals so the WatchDog thread won't be called when
|
||||
// external signals are delivered, thus avoiding interfering with the test
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
|
||||
// we allow the crashing signals, in case we have bugs
|
||||
for (int signo : FatalSignalHandler::fatalSignals)
|
||||
sigdelset(&set, signo);
|
||||
|
||||
pthread_sigmask(SIG_BLOCK, &set, nullptr);
|
||||
}
|
||||
#else // Q_OS_WASM or weird systems
|
||||
class FatalSignalHandler {};
|
||||
inline void blockUnixSignals() {}
|
||||
#endif // Q_OS_* choice
|
||||
|
||||
static void initEnvironment()
|
||||
{
|
||||
qputenv("QT_QTESTLIB_RUNNING", "1");
|
||||
|
|
@ -2547,7 +1820,7 @@ int QTest::qExec(QObject *testObject, int argc, char **argv)
|
|||
void QTest::qInit(QObject *testObject, int argc, char **argv)
|
||||
{
|
||||
initEnvironment();
|
||||
maybeDisableCoreDump();
|
||||
CrashHandler::maybeDisableCoreDump();
|
||||
QBenchmarkGlobalData::current = new QBenchmarkGlobalData;
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
|
|
@ -2614,8 +1887,8 @@ int QTest::qRun()
|
|||
} else
|
||||
#endif
|
||||
{
|
||||
std::optional<FatalSignalHandler> handler;
|
||||
prepareStackTrace();
|
||||
std::optional<CrashHandler::FatalSignalHandler> handler;
|
||||
CrashHandler::prepareStackTrace();
|
||||
if (!noCrashHandler)
|
||||
handler.emplace();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,663 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// Copyright (C) 2024 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include <QtTest/qtestcase.h>
|
||||
#include <QtTest/private/qtestcrashhandler_p.h>
|
||||
#include <QtTest/qtestassert.h>
|
||||
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qdir.h>
|
||||
#include <QtCore/qdiriterator.h>
|
||||
#include <QtCore/qfile.h>
|
||||
#include <QtCore/qfileinfo.h>
|
||||
#include <QtCore/qfloat16.h>
|
||||
#include <QtCore/qlibraryinfo.h>
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmetaobject.h>
|
||||
#include <QtCore/qobject.h>
|
||||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qtemporarydir.h>
|
||||
#include <QtCore/qthread.h>
|
||||
#include <QtCore/qvarlengtharray.h>
|
||||
#include <QtCore/private/qlocking_p.h>
|
||||
#include <QtCore/private/qtools_p.h>
|
||||
#include <QtCore/private/qwaitcondition_p.h>
|
||||
|
||||
#include <QtCore/qtestsupport_core.h>
|
||||
|
||||
#include <QtTest/private/qtestlog_p.h>
|
||||
#include <QtTest/private/qtesttable_p.h>
|
||||
#include <QtTest/qtestdata.h>
|
||||
#include <QtTest/private/qtestresult_p.h>
|
||||
#include <QtTest/private/qsignaldumper_p.h>
|
||||
#include <QtTest/private/qbenchmark_p.h>
|
||||
#if QT_CONFIG(batch_test_support)
|
||||
#include <QtTest/private/qtestregistry_p.h>
|
||||
#endif // QT_CONFIG(batch_test_support)
|
||||
#include <QtTest/private/cycle_p.h>
|
||||
#include <QtTest/private/qtestblacklist_p.h>
|
||||
#if defined(HAVE_XCTEST)
|
||||
#include <QtTest/private/qxctestlogger_p.h>
|
||||
#endif
|
||||
#if defined Q_OS_MACOS
|
||||
#include <QtTest/private/qtestutil_macos_p.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
#include <QtTest/private/qappletestlogger_p.h>
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_INTEGRITY) || __GHS_VERSION_NUMBER > 202014
|
||||
# include <charconv>
|
||||
#else
|
||||
// Broken implementation, causes link failures just by #include'ing!
|
||||
# undef __cpp_lib_to_chars // in case <version> was included
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <QtCore/private/qcore_unix_p.h>
|
||||
|
||||
#include <errno.h>
|
||||
#if __has_include(<paths.h>)
|
||||
# include <paths.h>
|
||||
#endif
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
# if !defined(Q_OS_INTEGRITY)
|
||||
# include <sys/resource.h>
|
||||
# endif
|
||||
# ifndef _PATH_DEFPATH
|
||||
# define _PATH_DEFPATH "/usr/bin:/bin"
|
||||
# endif
|
||||
# ifndef SIGSTKSZ
|
||||
# define SIGSTKSZ 0 /* we have code to set the minimum */
|
||||
# endif
|
||||
# ifndef SA_RESETHAND
|
||||
# define SA_RESETHAND 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <CoreFoundation/CFPreferences.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WASM)
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace QTest {
|
||||
namespace CrashHandler {
|
||||
#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
|
||||
struct iovec IoVec(struct iovec vec)
|
||||
{
|
||||
return vec;
|
||||
}
|
||||
struct iovec IoVec(const char *str)
|
||||
{
|
||||
struct iovec r = {};
|
||||
r.iov_base = const_cast<char *>(str);
|
||||
r.iov_len = strlen(str);
|
||||
return r;
|
||||
}
|
||||
|
||||
struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result)
|
||||
{
|
||||
char *ptr = result.array.data();
|
||||
if (false) {
|
||||
#ifdef __cpp_lib_to_chars
|
||||
} else if (auto r = std::to_chars(ptr, ptr + result.array.size(), n, 10); r.ec == std::errc{}) {
|
||||
ptr = r.ptr;
|
||||
#endif
|
||||
} else {
|
||||
// handle the sign
|
||||
if (n < 0) {
|
||||
*ptr++ = '-';
|
||||
n = -n;
|
||||
}
|
||||
|
||||
// find the highest power of the base that is less than this number
|
||||
static constexpr int StartingDivider = ([]() {
|
||||
int divider = 1;
|
||||
for (int i = 0; i < std::numeric_limits<int>::digits10; ++i)
|
||||
divider *= 10;
|
||||
return divider;
|
||||
}());
|
||||
int divider = StartingDivider;
|
||||
while (divider && n < divider)
|
||||
divider /= 10;
|
||||
|
||||
// now convert to string
|
||||
while (divider > 1) {
|
||||
int quot = n / divider;
|
||||
n = n % divider;
|
||||
divider /= 10;
|
||||
*ptr++ = quot + '0';
|
||||
}
|
||||
*ptr++ = n + '0';
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
// this isn't necessary, it just helps in the debugger
|
||||
*ptr = '\0';
|
||||
#endif
|
||||
struct iovec r;
|
||||
r.iov_base = result.array.data();
|
||||
r.iov_len = ptr - result.array.data();
|
||||
return r;
|
||||
};
|
||||
#endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
|
||||
|
||||
bool alreadyDebugging()
|
||||
{
|
||||
#if defined(Q_OS_LINUX)
|
||||
int fd = open("/proc/self/status", O_RDONLY);
|
||||
if (fd == -1)
|
||||
return false;
|
||||
char buffer[2048];
|
||||
ssize_t size = read(fd, buffer, sizeof(buffer) - 1);
|
||||
if (size == -1) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
buffer[size] = 0;
|
||||
const char tracerPidToken[] = "\nTracerPid:";
|
||||
char *tracerPid = strstr(buffer, tracerPidToken);
|
||||
if (!tracerPid) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
tracerPid += sizeof(tracerPidToken);
|
||||
long int pid = strtol(tracerPid, &tracerPid, 10);
|
||||
close(fd);
|
||||
return pid != 0;
|
||||
#elif defined(Q_OS_WIN)
|
||||
return IsDebuggerPresent();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
// Check if there is an exception handler for the process:
|
||||
mach_msg_type_number_t portCount = 0;
|
||||
exception_mask_t masks[EXC_TYPES_COUNT];
|
||||
mach_port_t ports[EXC_TYPES_COUNT];
|
||||
exception_behavior_t behaviors[EXC_TYPES_COUNT];
|
||||
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
|
||||
exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
|
||||
kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &portCount,
|
||||
ports, behaviors, flavors);
|
||||
if (result == KERN_SUCCESS) {
|
||||
for (mach_msg_type_number_t portIndex = 0; portIndex < portCount; ++portIndex) {
|
||||
if (MACH_PORT_VALID(ports[portIndex])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
// TODO
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
enum DebuggerProgram { None, Gdb, Lldb };
|
||||
static bool hasSystemCrashReporter()
|
||||
{
|
||||
#if defined(Q_OS_MACOS)
|
||||
return QTestPrivate::macCrashReporterWillShowDialog();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
} // unnamed namespaced
|
||||
|
||||
void maybeDisableCoreDump()
|
||||
{
|
||||
#ifdef RLIMIT_CORE
|
||||
bool ok = false;
|
||||
const int disableCoreDump = qEnvironmentVariableIntValue("QTEST_DISABLE_CORE_DUMP", &ok);
|
||||
if (ok && disableCoreDump) {
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
if (setrlimit(RLIMIT_CORE, &limit) != 0)
|
||||
qWarning("Failed to disable core dumps: %d", errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static DebuggerProgram debugger = None;
|
||||
void prepareStackTrace()
|
||||
{
|
||||
|
||||
bool ok = false;
|
||||
const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
|
||||
if (ok && disableStackDump)
|
||||
return;
|
||||
|
||||
if (hasSystemCrashReporter())
|
||||
return;
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
|
||||
std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
|
||||
if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
|
||||
return; // LLDB will fail to provide a valid stack trace
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// like QStandardPaths::findExecutable(), but simpler
|
||||
auto hasExecutable = [](const char *execname) {
|
||||
std::string candidate;
|
||||
std::string path;
|
||||
if (const char *p = getenv("PATH"); p && *p)
|
||||
path = p;
|
||||
else
|
||||
path = _PATH_DEFPATH;
|
||||
for (const char *p = std::strtok(&path[0], ":'"); p; p = std::strtok(nullptr, ":")) {
|
||||
candidate = p;
|
||||
candidate += '/';
|
||||
candidate += execname;
|
||||
if (QT_ACCESS(candidate.data(), X_OK) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
static constexpr DebuggerProgram debuggerSearchOrder[] = {
|
||||
# if defined(Q_OS_QNX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
Gdb, Lldb
|
||||
# else
|
||||
Lldb, Gdb
|
||||
# endif
|
||||
};
|
||||
for (DebuggerProgram candidate : debuggerSearchOrder) {
|
||||
switch (candidate) {
|
||||
case None:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
case Gdb:
|
||||
if (hasExecutable("gdb")) {
|
||||
debugger = Gdb;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Lldb:
|
||||
if (hasExecutable("lldb")) {
|
||||
debugger = Lldb;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // Q_OS_UNIX
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
|
||||
void printTestRunTime()
|
||||
{
|
||||
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
||||
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
||||
const char *const name = QTest::currentTestFunction();
|
||||
writeToStderr("\n ", name ? name : "[Non-test]",
|
||||
" function time: ", asyncSafeToString(msecsFunctionTime),
|
||||
"ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
|
||||
}
|
||||
|
||||
void generateStackTrace()
|
||||
{
|
||||
if (debugger == None || alreadyDebugging())
|
||||
return;
|
||||
|
||||
# if defined(Q_OS_UNIX) && !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_VXWORKS)
|
||||
writeToStderr("\n=== Stack trace ===\n");
|
||||
|
||||
// execlp() requires null-termination, so call the default constructor
|
||||
AsyncSafeIntBuffer pidbuffer;
|
||||
asyncSafeToString(getpid(), std::move(pidbuffer));
|
||||
|
||||
// Note: POSIX.1-2001 still has fork() in the list of async-safe functions,
|
||||
// but in a future edition, it might be removed. It would be safer to wake
|
||||
// up a babysitter thread to launch the debugger.
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// child process
|
||||
(void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
|
||||
|
||||
switch (debugger) {
|
||||
case None:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
case Gdb:
|
||||
execlp("gdb", "gdb", "--nx", "--batch", "-ex", "thread apply all bt",
|
||||
"--pid", pidbuffer.array.data(), nullptr);
|
||||
break;
|
||||
case Lldb:
|
||||
execlp("lldb", "lldb", "--no-lldbinit", "--batch", "-o", "bt all",
|
||||
"--attach-pid", pidbuffer.array.data(), nullptr);
|
||||
break;
|
||||
}
|
||||
_exit(1);
|
||||
} else if (pid < 0) {
|
||||
writeToStderr("Failed to start debugger.\n");
|
||||
} else {
|
||||
int ret;
|
||||
QT_EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
|
||||
}
|
||||
|
||||
writeToStderr("=== End of stack trace ===\n");
|
||||
# endif // Q_OS_UNIX && !Q_OS_WASM && !Q_OS_INTEGRITY && !Q_OS_VXWORKS
|
||||
}
|
||||
#endif // !defined(Q_OS_WASM) || QT_CONFIG(thread)
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
void blockUnixSignals()
|
||||
{
|
||||
// Windows does have C signals, but doesn't use them for the purposes we're
|
||||
// talking about here
|
||||
}
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
|
||||
void blockUnixSignals()
|
||||
{
|
||||
// Block most Unix signals so the WatchDog thread won't be called when
|
||||
// external signals are delivered, thus avoiding interfering with the test
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
|
||||
// we allow the crashing signals, in case we have bugs
|
||||
for (int signo : FatalSignalHandler::fatalSignals)
|
||||
sigdelset(&set, signo);
|
||||
|
||||
pthread_sigmask(SIG_BLOCK, &set, nullptr);
|
||||
}
|
||||
#endif // Q_OS_* choice
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
void DebugSymbolResolver::cleanup()
|
||||
{
|
||||
if (m_dbgHelpLib)
|
||||
FreeLibrary(m_dbgHelpLib);
|
||||
m_dbgHelpLib = 0;
|
||||
m_symFromAddr = nullptr;
|
||||
}
|
||||
|
||||
DebugSymbolResolver::DebugSymbolResolver(HANDLE process)
|
||||
: m_process(process), m_dbgHelpLib(0), m_symFromAddr(nullptr)
|
||||
{
|
||||
bool success = false;
|
||||
m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
|
||||
if (m_dbgHelpLib) {
|
||||
SymInitializeType symInitialize = reinterpret_cast<SymInitializeType>(
|
||||
reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize")));
|
||||
m_symFromAddr = reinterpret_cast<SymFromAddrType>(
|
||||
reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr")));
|
||||
success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE);
|
||||
}
|
||||
if (!success)
|
||||
cleanup();
|
||||
}
|
||||
|
||||
DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const
|
||||
{
|
||||
// reserve additional buffer where SymFromAddr() will store the name
|
||||
struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
|
||||
enum { symbolNameLength = 255 };
|
||||
|
||||
char name[symbolNameLength + 1];
|
||||
};
|
||||
|
||||
Symbol result;
|
||||
if (!isValid())
|
||||
return result;
|
||||
NamedSymbolInfo symbolBuffer;
|
||||
memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
|
||||
symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength;
|
||||
symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO);
|
||||
if (!m_symFromAddr(m_process, address, 0, &symbolBuffer))
|
||||
return result;
|
||||
result.name = qstrdup(symbolBuffer.Name);
|
||||
result.address = symbolBuffer.Address;
|
||||
return result;
|
||||
}
|
||||
|
||||
WindowsFaultHandler::WindowsFaultHandler()
|
||||
{
|
||||
# if !defined(Q_CC_MINGW)
|
||||
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
|
||||
# endif
|
||||
SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
|
||||
SetUnhandledExceptionFilter(windowsFaultHandler);
|
||||
}
|
||||
|
||||
LONG WINAPI WindowsFaultHandler::windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
|
||||
{
|
||||
enum { maxStackFrames = 100 };
|
||||
char appName[MAX_PATH];
|
||||
if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
|
||||
appName[0] = 0;
|
||||
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
||||
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
||||
const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
|
||||
fprintf(stderr, "A crash occurred in %s.\n", appName);
|
||||
if (const char *name = QTest::currentTestFunction())
|
||||
fprintf(stderr, "While testing %s\n", name);
|
||||
fprintf(stderr, "Function time: %dms Total time: %dms\n\n"
|
||||
"Exception address: 0x%p\n"
|
||||
"Exception code : 0x%lx\n",
|
||||
msecsFunctionTime, msecsTotalTime, exceptionAddress,
|
||||
exInfo->ExceptionRecord->ExceptionCode);
|
||||
|
||||
DebugSymbolResolver resolver(GetCurrentProcess());
|
||||
if (resolver.isValid()) {
|
||||
DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
|
||||
if (exceptionSymbol.name) {
|
||||
fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name);
|
||||
delete [] exceptionSymbol.name;
|
||||
}
|
||||
void *stack[maxStackFrames];
|
||||
fputs("\nStack:\n", stderr);
|
||||
const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
|
||||
for (unsigned f = 0; f < frameCount; ++f) {
|
||||
DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
|
||||
if (symbol.name) {
|
||||
fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
|
||||
delete [] symbol.name;
|
||||
} else {
|
||||
fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fputc('\n', stderr);
|
||||
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
|
||||
bool FatalSignalHandler::pauseOnCrash = false;
|
||||
|
||||
FatalSignalHandler::FatalSignalHandler()
|
||||
{
|
||||
pauseOnCrash = qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH");
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = SIG_DFL;
|
||||
oldActions().fill(act);
|
||||
|
||||
// Remove the handler after it is invoked.
|
||||
act.sa_flags = SA_RESETHAND | setupAlternateStack();
|
||||
|
||||
# ifdef SA_SIGINFO
|
||||
act.sa_flags |= SA_SIGINFO;
|
||||
act.sa_sigaction = FatalSignalHandler::actionHandler;
|
||||
# else
|
||||
act.sa_handler = FatalSignalHandler::regularHandler;
|
||||
# endif
|
||||
|
||||
// Block all fatal signals in our signal handler so we don't try to close
|
||||
// the testlog twice.
|
||||
sigemptyset(&act.sa_mask);
|
||||
for (int signal : fatalSignals)
|
||||
sigaddset(&act.sa_mask, signal);
|
||||
|
||||
for (size_t i = 0; i < fatalSignals.size(); ++i)
|
||||
sigaction(fatalSignals[i], &act, &oldActions()[i]);
|
||||
}
|
||||
|
||||
FatalSignalHandler::~FatalSignalHandler()
|
||||
{
|
||||
// Restore the default signal handlers in place of ours.
|
||||
// If ours has been replaced, leave the replacement alone.
|
||||
auto isOurs = [](const struct sigaction &old) {
|
||||
# ifdef SA_SIGINFO
|
||||
return (old.sa_flags & SA_SIGINFO) && old.sa_sigaction == FatalSignalHandler::actionHandler;
|
||||
# else
|
||||
return old.sa_handler == FatalSignalHandler::regularHandler;
|
||||
# endif
|
||||
};
|
||||
struct sigaction action;
|
||||
|
||||
for (size_t i = 0; i < fatalSignals.size(); ++i) {
|
||||
struct sigaction &act = oldActions()[i];
|
||||
if (act.sa_flags == 0 && act.sa_handler == SIG_DFL)
|
||||
continue; // Already the default
|
||||
if (sigaction(fatalSignals[i], nullptr, &action))
|
||||
continue; // Failed to query present handler
|
||||
if (isOurs(action))
|
||||
sigaction(fatalSignals[i], &act, nullptr);
|
||||
}
|
||||
|
||||
freeAlternateStack();
|
||||
}
|
||||
|
||||
FatalSignalHandler::OldActionsArray &FatalSignalHandler::oldActions()
|
||||
{
|
||||
Q_CONSTINIT static OldActionsArray oldActions {};
|
||||
return oldActions;
|
||||
}
|
||||
|
||||
auto FatalSignalHandler::alternateStackSize()
|
||||
{
|
||||
struct R { size_t size, pageSize; };
|
||||
static constexpr size_t MinStackSize = 32 * 1024;
|
||||
size_t pageSize = sysconf(_SC_PAGESIZE);
|
||||
size_t size = SIGSTKSZ;
|
||||
if (size < MinStackSize) {
|
||||
size = MinStackSize;
|
||||
} else {
|
||||
// round up to a page
|
||||
size = (size + pageSize - 1) & -pageSize;
|
||||
}
|
||||
|
||||
return R{ size + pageSize, pageSize };
|
||||
}
|
||||
|
||||
int FatalSignalHandler::setupAlternateStack()
|
||||
{
|
||||
// tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
|
||||
// unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
|
||||
# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
|
||||
// Let the signal handlers use an alternate stack
|
||||
// This is necessary if SIGSEGV is to catch a stack overflow
|
||||
auto r = alternateStackSize();
|
||||
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
||||
# ifdef MAP_STACK
|
||||
flags |= MAP_STACK;
|
||||
# endif
|
||||
alternateStackBase = mmap(nullptr, r.size, PROT_READ | PROT_WRITE, flags, -1, 0);
|
||||
if (alternateStackBase == MAP_FAILED)
|
||||
return 0;
|
||||
|
||||
// mark the bottom page inaccessible, to catch a handler stack overflow
|
||||
(void) mprotect(alternateStackBase, r.pageSize, PROT_NONE);
|
||||
|
||||
stack_t stack;
|
||||
stack.ss_flags = 0;
|
||||
stack.ss_size = r.size - r.pageSize;
|
||||
stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
|
||||
sigaltstack(&stack, nullptr);
|
||||
return SA_ONSTACK;
|
||||
# else
|
||||
return 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
void FatalSignalHandler::freeAlternateStack()
|
||||
{
|
||||
# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
|
||||
if (alternateStackBase != MAP_FAILED) {
|
||||
stack_t stack = {};
|
||||
stack.ss_flags = SS_DISABLE;
|
||||
sigaltstack(&stack, nullptr);
|
||||
munmap(alternateStackBase, alternateStackSize().size);
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
void FatalSignalHandler::actionHandler(int signum, siginfo_t *info, void *)
|
||||
{
|
||||
writeToStderr("Received signal ", asyncSafeToString(signum),
|
||||
" (SIG", signalName(signum), ")");
|
||||
|
||||
bool isCrashingSignal =
|
||||
std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end();
|
||||
if (isCrashingSignal && (!info || info->si_code <= 0))
|
||||
isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
|
||||
if (isCrashingSignal)
|
||||
printCrashingSignalInfo(info);
|
||||
else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
|
||||
printSentSignalInfo(info);
|
||||
|
||||
printTestRunTime();
|
||||
if (signum != SIGINT) {
|
||||
generateStackTrace();
|
||||
if (pauseOnCrash) {
|
||||
writeToStderr("Pausing process ", asyncSafeToString(getpid()),
|
||||
" for debugging\n");
|
||||
raise(SIGSTOP);
|
||||
}
|
||||
}
|
||||
|
||||
// chain back to the previous handler, if any
|
||||
for (size_t i = 0; i < fatalSignals.size(); ++i) {
|
||||
struct sigaction &act = oldActions()[i];
|
||||
if (signum != fatalSignals[i])
|
||||
continue;
|
||||
|
||||
// restore the handler (if SA_RESETHAND hasn't done the job for us)
|
||||
if (SA_RESETHAND == 0 || act.sa_handler != SIG_DFL || act.sa_flags)
|
||||
(void) sigaction(signum, &act, nullptr);
|
||||
|
||||
if (!isCrashingSignal)
|
||||
raise(signum);
|
||||
|
||||
// signal is blocked, so it'll be delivered when we return
|
||||
return;
|
||||
}
|
||||
|
||||
// we shouldn't reach here!
|
||||
std::abort();
|
||||
}
|
||||
#endif // defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
|
||||
|
||||
} // namespace CrashHandler
|
||||
} // namespace QTest
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#ifndef QTESTCRASHHANDLER_H
|
||||
#define QTESTCRASHHANDLER_H
|
||||
|
||||
#include <QtTest/qttestglobal.h>
|
||||
|
||||
#include <QtCore/private/qtools_p.h>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <iostream>
|
||||
# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
|
||||
# include <crtdbg.h>
|
||||
# endif
|
||||
#include <qt_windows.h> // for Sleep
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace QTest {
|
||||
namespace CrashHandler {
|
||||
#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
|
||||
struct iovec IoVec(struct iovec vec);
|
||||
struct iovec IoVec(const char *str);
|
||||
|
||||
template <typename... Args> static ssize_t writeToStderr(Args &&... args)
|
||||
{
|
||||
struct iovec vec[] = { IoVec(std::forward<Args>(args))... };
|
||||
return ::writev(STDERR_FILENO, vec, std::size(vec));
|
||||
}
|
||||
|
||||
// async-signal-safe conversion from int to string
|
||||
struct AsyncSafeIntBuffer
|
||||
{
|
||||
// digits10 + 1 for all possible digits
|
||||
// +1 for the sign
|
||||
// +1 for the terminating null
|
||||
static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3;
|
||||
std::array<char, Digits10> array;
|
||||
constexpr AsyncSafeIntBuffer() : array{} {} // initializes array
|
||||
AsyncSafeIntBuffer(Qt::Initialization) {} // leaves array uninitialized
|
||||
};
|
||||
|
||||
struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized);
|
||||
#elif defined(Q_OS_WIN)
|
||||
// Windows doesn't need to be async-safe
|
||||
template <typename... Args> static void writeToStderr(Args &&... args)
|
||||
{
|
||||
(std::cerr << ... << args);
|
||||
}
|
||||
|
||||
inline std::string asyncSafeToString(int n)
|
||||
{
|
||||
return std::to_string(n);
|
||||
}
|
||||
#endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
|
||||
|
||||
bool alreadyDebugging();
|
||||
void blockUnixSignals();
|
||||
|
||||
#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
|
||||
void printTestRunTime();
|
||||
void generateStackTrace();
|
||||
#endif
|
||||
|
||||
void maybeDisableCoreDump();
|
||||
void prepareStackTrace();
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
|
||||
class DebugSymbolResolver
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
|
||||
public:
|
||||
struct Symbol
|
||||
{
|
||||
Symbol() : name(nullptr), address(0) {}
|
||||
|
||||
const char *name; // Must be freed by caller.
|
||||
DWORD64 address;
|
||||
};
|
||||
|
||||
explicit DebugSymbolResolver(HANDLE process);
|
||||
~DebugSymbolResolver() { cleanup(); }
|
||||
|
||||
bool isValid() const { return m_symFromAddr; }
|
||||
|
||||
Symbol resolveSymbol(DWORD64 address) const;
|
||||
|
||||
private:
|
||||
// typedefs from DbgHelp.h/.dll
|
||||
struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
|
||||
ULONG SizeOfStruct;
|
||||
ULONG TypeIndex; // Type Index of symbol
|
||||
ULONG64 Reserved[2];
|
||||
ULONG Index;
|
||||
ULONG Size;
|
||||
ULONG64 ModBase; // Base Address of module comtaining this symbol
|
||||
ULONG Flags;
|
||||
ULONG64 Value; // Value of symbol, ValuePresent should be 1
|
||||
ULONG64 Address; // Address of symbol including base address of module
|
||||
ULONG Register; // register holding value or pointer to value
|
||||
ULONG Scope; // scope of the symbol
|
||||
ULONG Tag; // pdb classification
|
||||
ULONG NameLen; // Actual length of name
|
||||
ULONG MaxNameLen;
|
||||
CHAR Name[1]; // Name of symbol
|
||||
};
|
||||
|
||||
typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
|
||||
typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);
|
||||
|
||||
void cleanup();
|
||||
|
||||
const HANDLE m_process;
|
||||
HMODULE m_dbgHelpLib;
|
||||
SymFromAddrType m_symFromAddr;
|
||||
};
|
||||
|
||||
class WindowsFaultHandler
|
||||
{
|
||||
public:
|
||||
WindowsFaultHandler();
|
||||
|
||||
private:
|
||||
static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo);
|
||||
};
|
||||
using FatalSignalHandler = WindowsFaultHandler;
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
|
||||
class FatalSignalHandler
|
||||
{
|
||||
public:
|
||||
# define OUR_SIGNALS(F) \
|
||||
F(HUP) \
|
||||
F(INT) \
|
||||
F(QUIT) \
|
||||
F(ABRT) \
|
||||
F(ILL) \
|
||||
F(BUS) \
|
||||
F(FPE) \
|
||||
F(SEGV) \
|
||||
F(PIPE) \
|
||||
F(TERM) \
|
||||
/**/
|
||||
# define CASE_LABEL(S) case SIG ## S: return QT_STRINGIFY(S);
|
||||
# define ENUMERATE_SIGNALS(S) SIG ## S,
|
||||
static const char *signalName(int signum) noexcept
|
||||
{
|
||||
switch (signum) {
|
||||
OUR_SIGNALS(CASE_LABEL)
|
||||
}
|
||||
|
||||
# if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2)
|
||||
// get the other signal names from glibc 2.32
|
||||
// (accessing the sys_sigabbrev variable causes linker warnings)
|
||||
if (const char *p = sigabbrev_np(signum))
|
||||
return p;
|
||||
# endif
|
||||
return "???";
|
||||
}
|
||||
static constexpr std::array fatalSignals = {
|
||||
OUR_SIGNALS(ENUMERATE_SIGNALS)
|
||||
};
|
||||
# undef CASE_LABEL
|
||||
# undef ENUMERATE_SIGNALS
|
||||
|
||||
static constexpr std::array crashingSignals = {
|
||||
// Crash signals are special, because if we return from the handler
|
||||
// without adjusting the machine state, the same instruction that
|
||||
// originally caused the crash will get re-executed and will thus cause
|
||||
// the same crash again. This is useful if our parent process logs the
|
||||
// exit result or if core dumps are enabled: the core file will point
|
||||
// to the actual instruction that crashed.
|
||||
SIGILL, SIGBUS, SIGFPE, SIGSEGV
|
||||
};
|
||||
using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>;
|
||||
|
||||
FatalSignalHandler();
|
||||
~FatalSignalHandler();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY_MOVE(FatalSignalHandler)
|
||||
|
||||
static OldActionsArray &oldActions();
|
||||
auto alternateStackSize();
|
||||
int setupAlternateStack();
|
||||
void freeAlternateStack();
|
||||
|
||||
template <typename T> static
|
||||
std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1>
|
||||
printSentSignalInfo(T *info)
|
||||
{
|
||||
writeToStderr(" sent by PID ", asyncSafeToString(info->si_pid),
|
||||
" UID ", asyncSafeToString(info->si_uid));
|
||||
}
|
||||
static void printSentSignalInfo(...) {}
|
||||
|
||||
template <typename T> static
|
||||
std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info)
|
||||
{
|
||||
using HexString = std::array<char, sizeof(quintptr) * 2>;
|
||||
auto toHexString = [](quintptr u, HexString &&r = {}) {
|
||||
int shift = sizeof(quintptr) * 8 - 4;
|
||||
for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4)
|
||||
r[i] = QtMiscUtils::toHexLower(u >> shift);
|
||||
struct iovec vec;
|
||||
vec.iov_base = r.data();
|
||||
vec.iov_len = r.size();
|
||||
return vec;
|
||||
};
|
||||
writeToStderr(", code ", asyncSafeToString(info->si_code),
|
||||
", for address 0x", toHexString(quintptr(info->si_addr)));
|
||||
}
|
||||
static void printCrashingSignalInfo(...) {}
|
||||
static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */);
|
||||
|
||||
[[maybe_unused]] static void regularHandler(int signum)
|
||||
{
|
||||
actionHandler(signum, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void *alternateStackBase = MAP_FAILED;
|
||||
static bool pauseOnCrash;
|
||||
};
|
||||
#else // Q_OS_WASM or weird systems
|
||||
class FatalSignalHandler {};
|
||||
inline void blockUnixSignals() {}
|
||||
#endif // Q_OS_* choice
|
||||
} // namespace CrashHandler
|
||||
} // namespace QTest
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QTESTCRASHHANDLER_H
|
||||
Loading…
Reference in New Issue