Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtestcrashhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// Copyright (C) 2024 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include <QtTest/qtestcase.h>
6#include <QtTest/private/qtestcrashhandler_p.h>
7#include <QtTest/qtestassert.h>
8
9#include <QtCore/qbytearray.h>
10#include <QtCore/qcoreapplication.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qdir.h>
13#include <QtCore/qdiriterator.h>
14#include <QtCore/qfile.h>
15#include <QtCore/qfileinfo.h>
16#include <QtCore/qfloat16.h>
17#include <QtCore/qlibraryinfo.h>
18#include <QtCore/qlist.h>
19#include <QtCore/qmetaobject.h>
20#include <QtCore/qobject.h>
21#include <QtCore/qstringlist.h>
22#include <QtCore/qtemporarydir.h>
23#include <QtCore/qthread.h>
24#include <QtCore/qvarlengtharray.h>
25#include <QtCore/private/qlocking_p.h>
26#include <QtCore/private/qtools_p.h>
27#include <QtCore/private/qwaitcondition_p.h>
28
29#include <QtCore/qtestsupport_core.h>
30
31#include <QtTest/private/qtestlog_p.h>
32#include <QtTest/private/qtesttable_p.h>
33#include <QtTest/qtestdata.h>
34#include <QtTest/private/qtestresult_p.h>
35#include <QtTest/private/qsignaldumper_p.h>
36#include <QtTest/private/qbenchmark_p.h>
37#if QT_CONFIG(batch_test_support)
38#include <QtTest/private/qtestregistry_p.h>
39#endif // QT_CONFIG(batch_test_support)
40#include <QtTest/private/cycle_include_p.h>
41#include <QtTest/private/qtestblacklist_p.h>
42#if defined(HAVE_XCTEST)
43#include <QtTest/private/qxctestlogger_p.h>
44#endif
45#if defined Q_OS_MACOS
46#include <QtTest/private/qtestutil_macos_p.h>
47#endif
48
49#if defined(Q_OS_DARWIN)
50#include <QtTest/private/qappletestlogger_p.h>
51#endif
52
53#if !defined(Q_OS_INTEGRITY) || __GHS_VERSION_NUMBER > 202014
54# include <charconv>
55#else
56// Broken implementation, causes link failures just by #include'ing!
57# undef __cpp_lib_to_chars // in case <version> was included
58#endif
59
60#include <stdio.h>
61#include <stdlib.h>
62
63#if defined(Q_OS_LINUX)
64#include <sys/prctl.h>
65#include <sys/types.h>
66#include <fcntl.h>
67#endif
68
69#ifdef Q_OS_UNIX
70#include <QtCore/private/qcore_unix_p.h>
71
72#include <errno.h>
73#if __has_include(<paths.h>)
74# include <paths.h>
75#endif
76#include <signal.h>
77#include <time.h>
78#include <sys/mman.h>
79#include <sys/wait.h>
80#include <unistd.h>
81# if !defined(Q_OS_INTEGRITY)
82# include <sys/resource.h>
83# endif
84# ifndef _PATH_DEFPATH
85# define _PATH_DEFPATH "/usr/bin:/bin"
86# endif
87# ifndef SIGSTKSZ
88# define SIGSTKSZ 0 /* we have code to set the minimum */
89# endif
90# ifndef SA_RESETHAND
91# define SA_RESETHAND 0
92# endif
93#endif
94
95#if defined(Q_OS_MACOS)
96#include <IOKit/pwr_mgt/IOPMLib.h>
97#include <mach/task.h>
98#include <mach/mach_init.h>
99#include <CoreFoundation/CFPreferences.h>
100#endif
101
102#if defined(Q_OS_WASM)
103#include <emscripten.h>
104#endif
105
106QT_BEGIN_NAMESPACE
107
108using namespace Qt::StringLiterals;
109
110namespace QTest {
111namespace CrashHandler {
112#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
113struct iovec IoVec(struct iovec vec)
114{
115 return vec;
116}
117struct iovec IoVec(const char *str)
118{
119 struct iovec r = {};
120 r.iov_base = const_cast<char *>(str);
121 r.iov_len = strlen(str);
122 return r;
123}
124
126{
127 char *ptr = result.array.data();
128 if (false) {
129#ifdef __cpp_lib_to_chars
130 } else if (auto r = std::to_chars(ptr, ptr + result.array.size(), n, 10); r.ec == std::errc{}) {
131 ptr = r.ptr;
132#endif
133 } else {
134 // handle the sign
135 if (n < 0) {
136 *ptr++ = '-';
137 n = -n;
138 }
139
140 // find the highest power of the base that is less than this number
141 static constexpr int StartingDivider = ([]() {
142 int divider = 1;
143 for (int i = 0; i < std::numeric_limits<int>::digits10; ++i)
144 divider *= 10;
145 return divider;
146 }());
148 while (divider && n < divider)
149 divider /= 10;
150
151 // now convert to string
152 while (divider > 1) {
153 int quot = n / divider;
154 n = n % divider;
155 divider /= 10;
156 *ptr++ = quot + '0';
157 }
158 *ptr++ = n + '0';
159 }
160
161#ifndef QT_NO_DEBUG
162 // this isn't necessary, it just helps in the debugger
163 *ptr = '\0';
164#endif
165 struct iovec r;
168 return r;
169};
170#endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
171
173{
174#if defined(Q_OS_LINUX)
175 int fd = open("/proc/self/status", O_RDONLY);
176 if (fd == -1)
177 return false;
178 char buffer[2048];
179 ssize_t size = read(fd, buffer, sizeof(buffer) - 1);
180 if (size == -1) {
181 close(fd);
182 return false;
183 }
184 buffer[size] = 0;
185 const char tracerPidToken[] = "\nTracerPid:";
186 char *tracerPid = strstr(buffer, tracerPidToken);
187 if (!tracerPid) {
188 close(fd);
189 return false;
190 }
191 tracerPid += sizeof(tracerPidToken);
192 long int pid = strtol(tracerPid, &tracerPid, 10);
193 close(fd);
194 return pid != 0;
195#elif defined(Q_OS_WIN)
196 return IsDebuggerPresent();
197#elif defined(Q_OS_MACOS)
198 // Check if there is an exception handler for the process:
199 mach_msg_type_number_t portCount = 0;
200 exception_mask_t masks[EXC_TYPES_COUNT];
201 mach_port_t ports[EXC_TYPES_COUNT];
202 exception_behavior_t behaviors[EXC_TYPES_COUNT];
203 thread_state_flavor_t flavors[EXC_TYPES_COUNT];
204 exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
205 kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &portCount,
206 ports, behaviors, flavors);
207 if (result == KERN_SUCCESS) {
208 for (mach_msg_type_number_t portIndex = 0; portIndex < portCount; ++portIndex) {
209 if (MACH_PORT_VALID(ports[portIndex])) {
210 return true;
211 }
212 }
213 }
214 return false;
215#else
216 // TODO
217 return false;
218#endif
219}
220
221namespace {
222enum DebuggerProgram { None, Gdb, Lldb };
223static bool hasSystemCrashReporter()
224{
225#if defined(Q_OS_MACOS)
226 return QTestPrivate::macCrashReporterWillShowDialog();
227#else
228 return false;
229#endif
230}
231} // unnamed namespaced
232
234{
235#ifdef RLIMIT_CORE
236 bool ok = false;
237 const int disableCoreDump = qEnvironmentVariableIntValue("QTEST_DISABLE_CORE_DUMP", &ok);
238 if (ok && disableCoreDump) {
239 struct rlimit limit;
240 limit.rlim_cur = 0;
241 limit.rlim_max = 0;
242 if (setrlimit(RLIMIT_CORE, &limit) != 0)
243 qWarning("Failed to disable core dumps: %d", errno);
244 }
245#endif
246}
247
248static DebuggerProgram debugger = None;
250{
251
252 bool ok = false;
253 const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
254 if (ok && disableStackDump)
255 return;
256
257 if (hasSystemCrashReporter())
258 return;
259
260#if defined(Q_OS_MACOS)
261 // Try to handle https://github.com/llvm/llvm-project/issues/53254,
262 // where LLDB will hang and fail to provide a valid stack trace.
263# if defined(Q_PROCESSOR_ARM)
264 return;
265 #else
266 #define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
267 std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
268 if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
269 return;
270# endif
271#endif
272
273#ifdef Q_OS_UNIX
274 // like QStandardPaths::findExecutable(), but simpler
275 auto hasExecutable = [](const char *execname) {
276 std::string candidate;
277 std::string path;
278 if (const char *p = getenv("PATH"); p && *p)
279 path = p;
280 else
281 path = _PATH_DEFPATH;
282 for (const char *p = std::strtok(&path[0], ":'"); p; p = std::strtok(nullptr, ":")) {
283 candidate = p;
284 candidate += '/';
285 candidate += execname;
286 if (QT_ACCESS(candidate.data(), X_OK) == 0)
287 return true;
288 }
289 return false;
290 };
291
292 static constexpr DebuggerProgram debuggerSearchOrder[] = {
293# if defined(Q_OS_QNX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
294 Gdb, Lldb
295# else
296 Lldb, Gdb
297# endif
298 };
299 for (DebuggerProgram candidate : debuggerSearchOrder) {
300 switch (candidate) {
301 case None:
302 Q_UNREACHABLE();
303 break;
304 case Gdb:
305 if (hasExecutable("gdb")) {
306 debugger = Gdb;
307 return;
308 }
309 break;
310 case Lldb:
311 if (hasExecutable("lldb")) {
312 debugger = Lldb;
313 return;
314 }
315 break;
316 }
317 }
318#endif // Q_OS_UNIX
319}
320
321#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
323{
326 const char *const name = QTest::currentTestFunction();
327 writeToStderr("\n ", name ? name : "[Non-test]",
328 " function time: ", asyncSafeToString(msecsFunctionTime),
329 "ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
330}
331
333{
334 if (debugger == None || alreadyDebugging())
335 return;
336
337# if defined(Q_OS_LINUX) && defined(PR_SET_PTRACER)
338 // allow ourselves to be debugged
340# endif
341
342# if defined(Q_OS_UNIX) && !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_VXWORKS)
343 writeToStderr("\n=== Stack trace ===\n");
344
345 // execlp() requires null-termination, so call the default constructor
348
349 // Note: POSIX.1-2001 still has fork() in the list of async-safe functions,
350 // but in a future edition, it might be removed. It would be safer to wake
351 // up a babysitter thread to launch the debugger.
352 pid_t pid = fork();
353 if (pid == 0) {
354 // child process
355 (void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
356
357 switch (debugger) {
358 case None:
360 break;
361 case Gdb:
362 execlp("gdb", "gdb", "--nx", "--batch", "-ex", "thread apply all bt",
363 "--pid", pidbuffer.array.data(), nullptr);
364 break;
365 case Lldb:
366 execlp("lldb", "lldb", "--no-lldbinit", "--batch", "-o", "bt all",
367 "--attach-pid", pidbuffer.array.data(), nullptr);
368 break;
369 }
370 _exit(1);
371 } else if (pid < 0) {
372 writeToStderr("Failed to start debugger.\n");
373 } else {
374 int ret;
375 QT_EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
376 }
377
378 writeToStderr("=== End of stack trace ===\n");
379# endif // Q_OS_UNIX && !Q_OS_WASM && !Q_OS_INTEGRITY && !Q_OS_VXWORKS
380}
381#endif // !defined(Q_OS_WASM) || QT_CONFIG(thread)
382
383#if defined(Q_OS_WIN)
384void blockUnixSignals()
385{
386 // Windows does have C signals, but doesn't use them for the purposes we're
387 // talking about here
388}
389#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
390void blockUnixSignals()
391{
392 // Block most Unix signals so the WatchDog thread won't be called when
393 // external signals are delivered, thus avoiding interfering with the test
395 sigfillset(&set);
396
397 // we allow the crashing signals, in case we have bugs
400
401 pthread_sigmask(SIG_BLOCK, &set, nullptr);
402}
403#endif // Q_OS_* choice
404
405#if defined(Q_OS_WIN)
407{
408 if (m_dbgHelpLib)
410 m_dbgHelpLib = 0;
411 m_symFromAddr = nullptr;
412}
413
416{
417 bool success = false;
418 m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
419 if (m_dbgHelpLib) {
421 reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize")));
422 m_symFromAddr = reinterpret_cast<SymFromAddrType>(
423 reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr")));
425 }
426 if (!success)
427 cleanup();
428}
429
431{
432 // reserve additional buffer where SymFromAddr() will store the name
433 struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
434 enum { symbolNameLength = 255 };
435
436 char name[symbolNameLength + 1];
437 };
438
440 if (!isValid())
441 return result;
443 memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
447 return result;
450 return result;
451}
452
454{
455# if !defined(Q_CC_MINGW)
457# endif
460}
461
463{
464 enum { maxStackFrames = 100 };
465 char appName[MAX_PATH];
467 appName[0] = 0;
471 fprintf(stderr, "A crash occurred in %s.\n", appName);
472 if (const char *name = QTest::currentTestFunction())
473 fprintf(stderr, "While testing %s\n", name);
474 fprintf(stderr, "Function time: %dms Total time: %dms\n\n"
475 "Exception address: 0x%p\n"
476 "Exception code : 0x%lx\n",
479
481 if (resolver.isValid()) {
483 if (exceptionSymbol.name) {
484 fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name);
485 delete [] exceptionSymbol.name;
486 }
488 fputs("\nStack:\n", stderr);
490 for (unsigned f = 0; f < frameCount; ++f) {
492 if (symbol.name) {
493 fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
494 delete [] symbol.name;
495 } else {
496 fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1);
497 }
498 }
499 }
500
501 fputc('\n', stderr);
502
504}
505#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
506bool FatalSignalHandler::pauseOnCrash = false;
507
509{
510 pauseOnCrash = qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH");
511 struct sigaction act;
512 memset(&act, 0, sizeof(act));
515
516 // Remove the handler after it is invoked.
518
519# ifdef SA_SIGINFO
522# else
524# endif
525
526 // Block all fatal signals in our signal handler so we don't try to close
527 // the testlog twice.
529 for (int signal : fatalSignals)
531
532 for (size_t i = 0; i < fatalSignals.size(); ++i)
534}
535
537{
538 // Restore the default signal handlers in place of ours.
539 // If ours has been replaced, leave the replacement alone.
540 auto isOurs = [](const struct sigaction &old) {
541# ifdef SA_SIGINFO
543# else
545# endif
546 };
547 struct sigaction action;
548
549 for (size_t i = 0; i < fatalSignals.size(); ++i) {
550 struct sigaction &act = oldActions()[i];
551 if (sigaction(fatalSignals[i], nullptr, &action))
552 continue; // Failed to query present handler
553 if (action.sa_flags == 0 && action.sa_handler == SIG_DFL)
554 continue; // Already the default
555 if (isOurs(action))
556 sigaction(fatalSignals[i], &act, nullptr);
557 }
558
560}
561
563{
565 return oldActions;
566}
567
569{
570 struct R { size_t size, pageSize; };
571 static constexpr size_t MinStackSize = 32 * 1024;
574 if (size < MinStackSize) {
576 } else {
577 // round up to a page
578 size = (size + pageSize - 1) & -pageSize;
579 }
580
581 return R{ size + pageSize, pageSize };
582}
583
585{
586 // tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
587 // unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
588# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
589 // Let the signal handlers use an alternate stack
590 // This is necessary if SIGSEGV is to catch a stack overflow
591 auto r = alternateStackSize();
593# ifdef MAP_STACK
594 flags |= MAP_STACK;
595# endif
596 alternateStackBase = mmap(nullptr, r.size, PROT_READ | PROT_WRITE, flags, -1, 0);
598 return 0;
599
600 // mark the bottom page inaccessible, to catch a handler stack overflow
602
604 stack.ss_flags = 0;
606 stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
607 sigaltstack(&stack, nullptr);
608 return SA_ONSTACK;
609# else
610 return 0;
611# endif
612}
613
615{
616# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
618 stack_t stack = {};
620 sigaltstack(&stack, nullptr);
622 }
623# endif
624}
625
627{
628 writeToStderr("Received signal ", asyncSafeToString(signum),
629 " (SIG", signalName(signum), ")");
630
631 bool isCrashingSignal =
633 if (isCrashingSignal && (!info || info->si_code <= 0))
634 isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
637 else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
639
641 if (signum != SIGINT) {
643 if (pauseOnCrash) {
644 writeToStderr("Pausing process ", asyncSafeToString(getpid()),
645 " for debugging\n");
646 raise(SIGSTOP);
647 }
648 }
649
650 // chain back to the previous handler, if any
651 for (size_t i = 0; i < fatalSignals.size(); ++i) {
652 struct sigaction &act = oldActions()[i];
653 if (signum != fatalSignals[i])
654 continue;
655
656 // restore the handler (if SA_RESETHAND hasn't done the job for us)
657 if (SA_RESETHAND == 0 || act.sa_handler != SIG_DFL || act.sa_flags)
658 (void) sigaction(signum, &act, nullptr);
659
660 if (!isCrashingSignal)
661 raise(signum);
662
663 // signal is blocked, so it'll be delivered when we return
664 return;
665 }
666
667 // we shouldn't reach here!
668 std::abort();
669}
670#endif // defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
671
672} // namespace CrashHandler
673} // namespace QTest
674
675QT_END_NAMESPACE
static DebuggerProgram debugger