summaryrefslogtreecommitdiffstats
path: root/src/testlib/qtestcrashhandler_p.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/testlib/qtestcrashhandler_p.h')
-rw-r--r--src/testlib/qtestcrashhandler_p.h251
1 files changed, 251 insertions, 0 deletions
diff --git a/src/testlib/qtestcrashhandler_p.h b/src/testlib/qtestcrashhandler_p.h
new file mode 100644
index 0000000000..02e19bfaa9
--- /dev/null
+++ b/src/testlib/qtestcrashhandler_p.h
@@ -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();
+ Q_TESTLIB_EXPORT 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 Q_TESTLIB_EXPORT 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 Q_TESTLIB_EXPORT 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 Q_TESTLIB_EXPORT FatalSignalHandler {};
+inline void blockUnixSignals() {}
+#endif // Q_OS_* choice
+} // namespace CrashHandler
+} // namespace QTest
+QT_END_NAMESPACE
+
+#endif // QTESTCRASHHANDLER_H