diff options
Diffstat (limited to 'src/testlib/qtestcrashhandler.cpp')
| -rw-r--r-- | src/testlib/qtestcrashhandler.cpp | 676 |
1 files changed, 0 insertions, 676 deletions
diff --git a/src/testlib/qtestcrashhandler.cpp b/src/testlib/qtestcrashhandler.cpp deleted file mode 100644 index a89103028a2..00000000000 --- a/src/testlib/qtestcrashhandler.cpp +++ /dev/null @@ -1,676 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// 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 <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 -} - -static DebuggerProgram debugger = None; -void prepareStackTrace() -{ - - bool ok = false; - const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok); - 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 -} - -#if !defined(Q_OS_WASM) || QT_CONFIG(thread) -void printTestRunTime() -{ - const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime()); - const int msecsTotalTime = qRound(QTestLog::msecsTotalTime()); - const char *const name = QTest::currentTestFunction(); - writeToStderr("\n ", name ? name : "[Non-test]", - " function time: ", asyncSafeToString(msecsFunctionTime), - "ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n"); -} - -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 -} -#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) - FreeLibrary(m_dbgHelpLib); - m_dbgHelpLib = 0; - m_symFromAddr = nullptr; -} - -DebugSymbolResolver::DebugSymbolResolver(HANDLE process) - : m_process(process), m_dbgHelpLib(0), m_symFromAddr(nullptr) -{ - bool success = false; - m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll"); - if (m_dbgHelpLib) { - SymInitializeType symInitialize = reinterpret_cast<SymInitializeType>( - reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize"))); - m_symFromAddr = reinterpret_cast<SymFromAddrType>( - reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr"))); - success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE); - } - if (!success) - cleanup(); -} - -DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const -{ - // reserve additional buffer where SymFromAddr() will store the name - struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO { - enum { symbolNameLength = 255 }; - - char name[symbolNameLength + 1]; - }; - - Symbol result; - if (!isValid()) - return result; - NamedSymbolInfo symbolBuffer; - memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo)); - symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength; - symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO); - if (!m_symFromAddr(m_process, address, 0, &symbolBuffer)) - return result; - result.name = qstrdup(symbolBuffer.Name); - result.address = symbolBuffer.Address; - return result; -} - -WindowsFaultHandler::WindowsFaultHandler() -{ -# if !defined(Q_CC_MINGW) - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); -# endif - SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX); - SetUnhandledExceptionFilter(windowsFaultHandler); -} - -LONG WINAPI WindowsFaultHandler::windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo) -{ - enum { maxStackFrames = 100 }; - char appName[MAX_PATH]; - if (!GetModuleFileNameA(NULL, appName, MAX_PATH)) - appName[0] = 0; - const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime()); - const int msecsTotalTime = qRound(QTestLog::msecsTotalTime()); - const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress; - fprintf(stderr, "A crash occurred in %s.\n", appName); - if (const char *name = QTest::currentTestFunction()) - fprintf(stderr, "While testing %s\n", name); - fprintf(stderr, "Function time: %dms Total time: %dms\n\n" - "Exception address: 0x%p\n" - "Exception code : 0x%lx\n", - msecsFunctionTime, msecsTotalTime, exceptionAddress, - exInfo->ExceptionRecord->ExceptionCode); - - DebugSymbolResolver resolver(GetCurrentProcess()); - if (resolver.isValid()) { - DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress)); - if (exceptionSymbol.name) { - fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name); - delete [] exceptionSymbol.name; - } - Q_DECL_UNINITIALIZED void *stack[maxStackFrames]; - fputs("\nStack:\n", stderr); - const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL); - for (unsigned f = 0; f < frameCount; ++f) { - DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f])); - if (symbol.name) { - fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address); - delete [] symbol.name; - } else { - fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1); - } - } - } - - fputc('\n', stderr); - - 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 - -QT_END_NAMESPACE |
