summaryrefslogtreecommitdiffstats
path: root/src/testlib/qtestcrashhandler_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/testlib/qtestcrashhandler_win.cpp')
-rw-r--r--src/testlib/qtestcrashhandler_win.cpp516
1 files changed, 5 insertions, 511 deletions
diff --git a/src/testlib/qtestcrashhandler_win.cpp b/src/testlib/qtestcrashhandler_win.cpp
index a89103028a2..67e46ab221c 100644
--- a/src/testlib/qtestcrashhandler_win.cpp
+++ b/src/testlib/qtestcrashhandler_win.cpp
@@ -2,249 +2,33 @@
// 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 <QtCore/qstring.h>
+#include <QtTest/qtestcase.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_include_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/prctl.h>
-#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
}
+enum DebuggerProgram { None, Gdb, Lldb, Cdb };
static DebuggerProgram debugger = None;
void prepareStackTrace()
{
@@ -254,71 +38,9 @@ void prepareStackTrace()
if (ok && disableStackDump)
return;
- if (hasSystemCrashReporter())
- return;
-
-#if defined(Q_OS_MACOS)
- // Try to handle https://github.com/llvm/llvm-project/issues/53254,
- // where LLDB will hang and fail to provide a valid stack trace.
-# if defined(Q_PROCESSOR_ARM)
- return;
- #else
- #define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
- std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
- if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
- return;
-# endif
-#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
+ // ### Implement finding a debugger on Windows
}
-#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
void printTestRunTime()
{
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
@@ -334,76 +56,15 @@ void generateStackTrace()
if (debugger == None || alreadyDebugging())
return;
-# if defined(Q_OS_LINUX) && defined(PR_SET_PTRACER)
- // allow ourselves to be debugged
- (void) prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
-# endif
-
-# 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",
- "-ex", "info proc mappings",
- "--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
+ // ### Implement starting a debugger on Windows
}
-#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)
@@ -503,173 +164,6 @@ LONG WINAPI WindowsFaultHandler::windowsFaultHandler(struct _EXCEPTION_POINTERS
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 (sigaction(fatalSignals[i], nullptr, &action))
- continue; // Failed to query present handler
- if (action.sa_flags == 0 && action.sa_handler == SIG_DFL)
- continue; // Already the default
- 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