From c8d9b7291a9d48d8b0e4043b3cf9664d4b261e77 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 2 May 2022 18:06:55 -0700 Subject: qtestcase: make the stack trace generation more signal-safe This should work so long as there's no async-unsafe pthread_atfork()- registered callback. Pick-to: 6.3 Change-Id: I5ff8e16fcdcb4ffd9ab6fffd16eb72e4e313bc19 Reviewed-by: Marc Mutz Reviewed-by: Edward Welbourne --- src/testlib/qtestcase.cpp | 69 ++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 25 deletions(-) (limited to 'src/testlib') diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp index 42c6f7aeb5..43facebe58 100644 --- a/src/testlib/qtestcase.cpp +++ b/src/testlib/qtestcase.cpp @@ -77,10 +77,13 @@ #include // for Sleep #endif #ifdef Q_OS_UNIX +#include + #include #include #include #include +#include #include # if !defined(Q_OS_INTEGRITY) # include @@ -232,7 +235,6 @@ static bool debuggerPresent() #endif } -#if !defined(Q_OS_WASM) static bool hasSystemCrashReporter() { #if defined(Q_OS_MACOS) @@ -258,18 +260,19 @@ static void disableCoreDump() } Q_CONSTRUCTOR_FUNCTION(disableCoreDump); -static void stackTrace() +static std::array stackTraceCommand = {}; +static void prepareStackTrace() { + stackTraceCommand[0] = '\0'; + bool ok = false; const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok); if (ok && disableStackDump) return; - if (debuggerPresent() || hasSystemCrashReporter()) + if (hasSystemCrashReporter()) return; -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - #if defined(Q_OS_MACOS) #define CSR_ALLOW_UNRESTRICTED_FS (1 << 1) std::optional sipConfiguration = qt_mac_sipConfiguration(); @@ -277,14 +280,10 @@ static void stackTrace() return; // LLDB will fail to provide a valid stack trace #endif - const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime()); - const int msecsTotalTime = qRound(QTestLog::msecsTotalTime()); - fprintf(stderr, "\n=== Received signal at function time: %dms, total time: %dms, dumping stack ===\n", - msecsFunctionTime, msecsTotalTime); - + // prepare the command to be run (our PID shouldn't change!) # ifdef Q_OS_LINUX - char cmd[512]; - qsnprintf(cmd, 512, "gdb --pid %d 1>&2 2>/dev/null <&2 2>/dev/null <(getpid())); - if (system(cmd) == -1) - fprintf(stderr, "calling gdb failed\n"); - fprintf(stderr, "=== End of stack trace ===\n"); # elif defined(Q_OS_MACOS) - char cmd[512]; - qsnprintf(cmd, 512, "lldb -p %d 1>&2 2>/dev/null <&2 2>/dev/null <(getpid())); - if (system(cmd) == -1) - fprintf(stderr, "calling lldb failed\n"); - fprintf(stderr, "=== End of stack trace ===\n"); # endif +} +[[maybe_unused]] static void generateStackTrace() +{ + if (stackTraceCommand[0] == '\0' || debuggerPresent()) + return; + +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime()); + const int msecsTotalTime = qRound(QTestLog::msecsTotalTime()); + writeToStderr("\n=== Received signal at function time: ", asyncSafeToString(msecsFunctionTime), + "ms, total time: ", asyncSafeToString(msecsTotalTime), + "ms, dumping stack ===\n"); + + // 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 + execl("/bin/sh", "/bin/sh", "-c", stackTraceCommand.data(), nullptr); + _exit(1); + } else if (pid < 0) { + writeToStderr("Failed to start debugger.\n"); + } else { + int ret; + EINTR_LOOP(ret, waitpid(pid, nullptr, 0)); + } + writeToStderr("=== End of stack trace ===\n"); #endif } -#endif // !Q_OS_WASM static bool installCoverageTool(const char * appname, const char * testname) { @@ -1168,9 +1188,7 @@ public: case TestFunctionStart: case TestFunctionEnd: if (Q_UNLIKELY(!waitFor(locker, e))) { -#ifndef Q_OS_WASM - stackTrace(); -#endif + generateStackTrace(); qFatal("Test function timed out"); } } @@ -1921,7 +1939,7 @@ private: const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime()); const int msecsTotalTime = qRound(QTestLog::msecsTotalTime()); if (signum != SIGINT) { - stackTrace(); + generateStackTrace(); if (qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH")) { writeToStderr("Pausing process ", asyncSafeToString(getpid()), " for debugging\n"); @@ -2079,6 +2097,7 @@ int QTest::qRun() #endif { QScopedPointer handler; + prepareStackTrace(); if (!noCrashHandler) handler.reset(new FatalSignalHandler); -- cgit v1.2.3