| 1 | // Copyright (C) 2024 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | // |
| 5 | // W A R N I N G |
| 6 | // ------------- |
| 7 | // |
| 8 | // This file is not part of the Qt API. It exists purely as an |
| 9 | // implementation detail. This header file may change from version to |
| 10 | // version without notice, or even be removed. |
| 11 | // |
| 12 | // We mean it. |
| 13 | // |
| 14 | |
| 15 | #ifndef QTESTCRASHHANDLER_H |
| 16 | #define QTESTCRASHHANDLER_H |
| 17 | |
| 18 | #include <QtCore/qnamespace.h> |
| 19 | #include <QtTest/qttestglobal.h> |
| 20 | |
| 21 | #include <QtCore/private/qtools_p.h> |
| 22 | |
| 23 | #ifdef Q_OS_UNIX |
| 24 | #include <signal.h> |
| 25 | #include <sys/mman.h> |
| 26 | #include <sys/uio.h> |
| 27 | #include <string.h> |
| 28 | #include <unistd.h> |
| 29 | #endif |
| 30 | |
| 31 | #ifdef Q_OS_WIN |
| 32 | #include <iostream> |
| 33 | # if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR)) |
| 34 | # include <crtdbg.h> |
| 35 | # endif |
| 36 | #include <qt_windows.h> // for Sleep |
| 37 | #endif |
| 38 | |
| 39 | QT_BEGIN_NAMESPACE |
| 40 | namespace QTest { |
| 41 | namespace CrashHandler { |
| 42 | #if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread)) |
| 43 | struct iovec IoVec(struct iovec vec); |
| 44 | struct iovec IoVec(const char *str); |
| 45 | |
| 46 | template <typename... Args> static ssize_t writeToStderr(Args &&... args) |
| 47 | { |
| 48 | struct iovec vec[] = { IoVec(std::forward<Args>(args))... }; |
| 49 | return ::writev(STDERR_FILENO, iovec: vec, count: std::size(vec)); |
| 50 | } |
| 51 | |
| 52 | // async-signal-safe conversion from int to string |
| 53 | struct AsyncSafeIntBuffer |
| 54 | { |
| 55 | // digits10 + 1 for all possible digits |
| 56 | // +1 for the sign |
| 57 | // +1 for the terminating null |
| 58 | static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3; |
| 59 | std::array<char, Digits10> array; |
| 60 | constexpr AsyncSafeIntBuffer() : array{} {} // initializes array |
| 61 | AsyncSafeIntBuffer(Qt::Initialization) {} // leaves array uninitialized |
| 62 | }; |
| 63 | |
| 64 | struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized); |
| 65 | #elif defined(Q_OS_WIN) |
| 66 | // Windows doesn't need to be async-safe |
| 67 | template <typename... Args> static void writeToStderr(Args &&... args) |
| 68 | { |
| 69 | (std::cerr << ... << args); |
| 70 | } |
| 71 | |
| 72 | inline std::string asyncSafeToString(int n) |
| 73 | { |
| 74 | return std::to_string(n); |
| 75 | } |
| 76 | #endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread)) |
| 77 | |
| 78 | bool alreadyDebugging(); |
| 79 | void blockUnixSignals(); |
| 80 | |
| 81 | #if !defined(Q_OS_WASM) || QT_CONFIG(thread) |
| 82 | void printTestRunTime(); |
| 83 | void generateStackTrace(); |
| 84 | #endif |
| 85 | |
| 86 | void maybeDisableCoreDump(); |
| 87 | Q_TESTLIB_EXPORT void prepareStackTrace(); |
| 88 | |
| 89 | #if defined(Q_OS_WIN) |
| 90 | // Helper class for resolving symbol names by dynamically loading "dbghelp.dll". |
| 91 | class DebugSymbolResolver |
| 92 | { |
| 93 | Q_DISABLE_COPY_MOVE(DebugSymbolResolver) |
| 94 | public: |
| 95 | struct Symbol |
| 96 | { |
| 97 | Symbol() : name(nullptr), address(0) {} |
| 98 | |
| 99 | const char *name; // Must be freed by caller. |
| 100 | DWORD64 address; |
| 101 | }; |
| 102 | |
| 103 | explicit DebugSymbolResolver(HANDLE process); |
| 104 | ~DebugSymbolResolver() { cleanup(); } |
| 105 | |
| 106 | bool isValid() const { return m_symFromAddr; } |
| 107 | |
| 108 | Symbol resolveSymbol(DWORD64 address) const; |
| 109 | |
| 110 | private: |
| 111 | // typedefs from DbgHelp.h/.dll |
| 112 | struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO |
| 113 | ULONG SizeOfStruct; |
| 114 | ULONG TypeIndex; // Type Index of symbol |
| 115 | ULONG64 Reserved[2]; |
| 116 | ULONG Index; |
| 117 | ULONG Size; |
| 118 | ULONG64 ModBase; // Base Address of module comtaining this symbol |
| 119 | ULONG Flags; |
| 120 | ULONG64 Value; // Value of symbol, ValuePresent should be 1 |
| 121 | ULONG64 Address; // Address of symbol including base address of module |
| 122 | ULONG Register; // register holding value or pointer to value |
| 123 | ULONG Scope; // scope of the symbol |
| 124 | ULONG Tag; // pdb classification |
| 125 | ULONG NameLen; // Actual length of name |
| 126 | ULONG MaxNameLen; |
| 127 | CHAR Name[1]; // Name of symbol |
| 128 | }; |
| 129 | |
| 130 | typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL); |
| 131 | typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *); |
| 132 | |
| 133 | void cleanup(); |
| 134 | |
| 135 | const HANDLE m_process; |
| 136 | HMODULE m_dbgHelpLib; |
| 137 | SymFromAddrType m_symFromAddr; |
| 138 | }; |
| 139 | |
| 140 | class Q_TESTLIB_EXPORT WindowsFaultHandler |
| 141 | { |
| 142 | public: |
| 143 | WindowsFaultHandler(); |
| 144 | |
| 145 | private: |
| 146 | static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo); |
| 147 | }; |
| 148 | using FatalSignalHandler = WindowsFaultHandler; |
| 149 | #elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM) |
| 150 | class Q_TESTLIB_EXPORT FatalSignalHandler |
| 151 | { |
| 152 | public: |
| 153 | # define OUR_SIGNALS(F) \ |
| 154 | F(HUP) \ |
| 155 | F(INT) \ |
| 156 | F(QUIT) \ |
| 157 | F(ABRT) \ |
| 158 | F(ILL) \ |
| 159 | F(BUS) \ |
| 160 | F(FPE) \ |
| 161 | F(SEGV) \ |
| 162 | F(PIPE) \ |
| 163 | F(TERM) \ |
| 164 | /**/ |
| 165 | # define CASE_LABEL(S) case SIG ## S: return QT_STRINGIFY(S); |
| 166 | # define ENUMERATE_SIGNALS(S) SIG ## S, |
| 167 | static const char *signalName(int signum) noexcept |
| 168 | { |
| 169 | switch (signum) { |
| 170 | OUR_SIGNALS(CASE_LABEL) |
| 171 | } |
| 172 | |
| 173 | # if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2) |
| 174 | // get the other signal names from glibc 2.32 |
| 175 | // (accessing the sys_sigabbrev variable causes linker warnings) |
| 176 | if (const char *p = sigabbrev_np(sig: signum)) |
| 177 | return p; |
| 178 | # endif |
| 179 | return "???" ; |
| 180 | } |
| 181 | static constexpr std::array fatalSignals = { |
| 182 | OUR_SIGNALS(ENUMERATE_SIGNALS) |
| 183 | }; |
| 184 | # undef CASE_LABEL |
| 185 | # undef ENUMERATE_SIGNALS |
| 186 | |
| 187 | static constexpr std::array crashingSignals = { |
| 188 | // Crash signals are special, because if we return from the handler |
| 189 | // without adjusting the machine state, the same instruction that |
| 190 | // originally caused the crash will get re-executed and will thus cause |
| 191 | // the same crash again. This is useful if our parent process logs the |
| 192 | // exit result or if core dumps are enabled: the core file will point |
| 193 | // to the actual instruction that crashed. |
| 194 | SIGILL, SIGBUS, SIGFPE, SIGSEGV |
| 195 | }; |
| 196 | using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>; |
| 197 | |
| 198 | FatalSignalHandler(); |
| 199 | ~FatalSignalHandler(); |
| 200 | |
| 201 | private: |
| 202 | Q_DISABLE_COPY_MOVE(FatalSignalHandler) |
| 203 | |
| 204 | static OldActionsArray &oldActions(); |
| 205 | auto alternateStackSize(); |
| 206 | int setupAlternateStack(); |
| 207 | void freeAlternateStack(); |
| 208 | |
| 209 | template <typename T> static |
| 210 | std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1> |
| 211 | printSentSignalInfo(T *info) |
| 212 | { |
| 213 | writeToStderr(" sent by PID " , asyncSafeToString(info->si_pid), |
| 214 | " UID " , asyncSafeToString(info->si_uid)); |
| 215 | } |
| 216 | static void printSentSignalInfo(...) {} |
| 217 | |
| 218 | template <typename T> static |
| 219 | std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info) |
| 220 | { |
| 221 | using HexString = std::array<char, sizeof(quintptr) * 2>; |
| 222 | auto toHexString = [](quintptr u, HexString &&r = {}) { |
| 223 | int shift = sizeof(quintptr) * 8 - 4; |
| 224 | for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4) |
| 225 | r[i] = QtMiscUtils::toHexLower(value: u >> shift); |
| 226 | struct iovec vec; |
| 227 | vec.iov_base = r.data(); |
| 228 | vec.iov_len = r.size(); |
| 229 | return vec; |
| 230 | }; |
| 231 | writeToStderr(", code " , asyncSafeToString(info->si_code), |
| 232 | ", for address 0x" , toHexString(quintptr(info->si_addr))); |
| 233 | } |
| 234 | static void printCrashingSignalInfo(...) {} |
| 235 | static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */); |
| 236 | |
| 237 | [[maybe_unused]] static void regularHandler(int signum) |
| 238 | { |
| 239 | actionHandler(signum, info: nullptr, nullptr); |
| 240 | } |
| 241 | |
| 242 | void *alternateStackBase = MAP_FAILED; |
| 243 | static bool pauseOnCrash; |
| 244 | }; |
| 245 | #else // Q_OS_WASM or weird systems |
| 246 | class Q_TESTLIB_EXPORT FatalSignalHandler {}; |
| 247 | inline void blockUnixSignals() {} |
| 248 | #endif // Q_OS_* choice |
| 249 | } // namespace CrashHandler |
| 250 | } // namespace QTest |
| 251 | QT_END_NAMESPACE |
| 252 | |
| 253 | #endif // QTESTCRASHHANDLER_H |
| 254 | |