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 <marc.mutz@qt.io> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
parent
402ce8f266
commit
c8d9b7291a
@ -77,10 +77,13 @@
|
|||||||
#include <qt_windows.h> // for Sleep
|
#include <qt_windows.h> // for Sleep
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
|
#include <QtCore/private/qcore_unix_p.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
# if !defined(Q_OS_INTEGRITY)
|
# if !defined(Q_OS_INTEGRITY)
|
||||||
# include <sys/resource.h>
|
# include <sys/resource.h>
|
||||||
@ -232,7 +235,6 @@ static bool debuggerPresent()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(Q_OS_WASM)
|
|
||||||
static bool hasSystemCrashReporter()
|
static bool hasSystemCrashReporter()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
@ -258,18 +260,19 @@ static void disableCoreDump()
|
|||||||
}
|
}
|
||||||
Q_CONSTRUCTOR_FUNCTION(disableCoreDump);
|
Q_CONSTRUCTOR_FUNCTION(disableCoreDump);
|
||||||
|
|
||||||
static void stackTrace()
|
static std::array<char, 512> stackTraceCommand = {};
|
||||||
|
static void prepareStackTrace()
|
||||||
{
|
{
|
||||||
|
stackTraceCommand[0] = '\0';
|
||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
|
const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
|
||||||
if (ok && disableStackDump)
|
if (ok && disableStackDump)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (debuggerPresent() || hasSystemCrashReporter())
|
if (hasSystemCrashReporter())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
#define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
|
#define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
|
||||||
std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
|
std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
|
||||||
@ -277,14 +280,10 @@ static void stackTrace()
|
|||||||
return; // LLDB will fail to provide a valid stack trace
|
return; // LLDB will fail to provide a valid stack trace
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
// prepare the command to be run (our PID shouldn't change!)
|
||||||
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
|
||||||
fprintf(stderr, "\n=== Received signal at function time: %dms, total time: %dms, dumping stack ===\n",
|
|
||||||
msecsFunctionTime, msecsTotalTime);
|
|
||||||
|
|
||||||
# ifdef Q_OS_LINUX
|
# ifdef Q_OS_LINUX
|
||||||
char cmd[512];
|
qsnprintf(stackTraceCommand.data(), stackTraceCommand.size(),
|
||||||
qsnprintf(cmd, 512, "gdb --pid %d 1>&2 2>/dev/null <<EOF\n"
|
"gdb --pid %d 1>&2 2>/dev/null <<EOF\n"
|
||||||
"set prompt\n"
|
"set prompt\n"
|
||||||
"set height 0\n"
|
"set height 0\n"
|
||||||
"thread apply all where full\n"
|
"thread apply all where full\n"
|
||||||
@ -292,24 +291,45 @@ static void stackTrace()
|
|||||||
"quit\n"
|
"quit\n"
|
||||||
"EOF\n",
|
"EOF\n",
|
||||||
static_cast<int>(getpid()));
|
static_cast<int>(getpid()));
|
||||||
if (system(cmd) == -1)
|
|
||||||
fprintf(stderr, "calling gdb failed\n");
|
|
||||||
fprintf(stderr, "=== End of stack trace ===\n");
|
|
||||||
# elif defined(Q_OS_MACOS)
|
# elif defined(Q_OS_MACOS)
|
||||||
char cmd[512];
|
qsnprintf(stackTraceCommand.data(), stackTraceCommand.size(),
|
||||||
qsnprintf(cmd, 512, "lldb -p %d 1>&2 2>/dev/null <<EOF\n"
|
"lldb -p %d 1>&2 2>/dev/null <<EOF\n"
|
||||||
"bt all\n"
|
"bt all\n"
|
||||||
"quit\n"
|
"quit\n"
|
||||||
"EOF\n",
|
"EOF\n",
|
||||||
static_cast<int>(getpid()));
|
static_cast<int>(getpid()));
|
||||||
if (system(cmd) == -1)
|
|
||||||
fprintf(stderr, "calling lldb failed\n");
|
|
||||||
fprintf(stderr, "=== End of stack trace ===\n");
|
|
||||||
# endif
|
|
||||||
|
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
#endif // !Q_OS_WASM
|
|
||||||
|
[[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
|
||||||
|
}
|
||||||
|
|
||||||
static bool installCoverageTool(const char * appname, const char * testname)
|
static bool installCoverageTool(const char * appname, const char * testname)
|
||||||
{
|
{
|
||||||
@ -1168,9 +1188,7 @@ public:
|
|||||||
case TestFunctionStart:
|
case TestFunctionStart:
|
||||||
case TestFunctionEnd:
|
case TestFunctionEnd:
|
||||||
if (Q_UNLIKELY(!waitFor(locker, e))) {
|
if (Q_UNLIKELY(!waitFor(locker, e))) {
|
||||||
#ifndef Q_OS_WASM
|
generateStackTrace();
|
||||||
stackTrace();
|
|
||||||
#endif
|
|
||||||
qFatal("Test function timed out");
|
qFatal("Test function timed out");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1921,7 +1939,7 @@ private:
|
|||||||
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
|
||||||
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
|
||||||
if (signum != SIGINT) {
|
if (signum != SIGINT) {
|
||||||
stackTrace();
|
generateStackTrace();
|
||||||
if (qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH")) {
|
if (qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH")) {
|
||||||
writeToStderr("Pausing process ", asyncSafeToString(getpid()),
|
writeToStderr("Pausing process ", asyncSafeToString(getpid()),
|
||||||
" for debugging\n");
|
" for debugging\n");
|
||||||
@ -2079,6 +2097,7 @@ int QTest::qRun()
|
|||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
QScopedPointer<FatalSignalHandler> handler;
|
QScopedPointer<FatalSignalHandler> handler;
|
||||||
|
prepareStackTrace();
|
||||||
if (!noCrashHandler)
|
if (!noCrashHandler)
|
||||||
handler.reset(new FatalSignalHandler);
|
handler.reset(new FatalSignalHandler);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user