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 | |