QTest::CrashHandler: prepare to disassemble the crashing instruction

This is the infrastructure code, without actually getting the
instruction pointer from the machine context.

Change-Id: Iadd2c78913b2d0177949fffdeafa12e9fc3daf87
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2025-05-14 18:51:11 -07:00
parent 4fcd854657
commit 33435ce567
3 changed files with 38 additions and 13 deletions

View File

@ -41,7 +41,7 @@ namespace CrashHandler {
#if !defined(Q_OS_WASM) || QT_CONFIG(thread) #if !defined(Q_OS_WASM) || QT_CONFIG(thread)
void printTestRunTime(); void printTestRunTime();
void generateStackTrace(); void generateStackTrace(quintptr ip = 0);
#endif #endif
void maybeDisableCoreDump(); void maybeDisableCoreDump();

View File

@ -358,7 +358,14 @@ void printTestRunTime()
"ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n"); "ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
} }
void generateStackTrace() static quintptr getProgramCounter(void *ucontext)
{
quintptr pc = 0;
Q_UNUSED(ucontext);
return pc;
}
void generateStackTrace(quintptr ip)
{ {
if (debugger == None || alreadyDebugging()) if (debugger == None || alreadyDebugging())
return; return;
@ -383,6 +390,14 @@ void generateStackTrace()
// child process // child process
(void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr (void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
// disassemble the crashing instruction, if known
// (syntax is the same for gdb and lldb)
char disasmInstr[sizeof("x/i 0x") + sizeof(ip) * 2] = {}; // zero-init for terminator
if (ip) {
strcpy(disasmInstr, "x/i ");
asyncSafeToHexString(ip, disasmInstr + strlen(disasmInstr));
}
struct Args { struct Args {
std::array<const char *, 16> argv; std::array<const char *, 16> argv;
int count = 0; int count = 0;
@ -400,15 +415,19 @@ void generateStackTrace()
Q_UNREACHABLE(); Q_UNREACHABLE();
break; break;
case Gdb: case Gdb:
argv << "gdb" << "--nx" << "--batch" argv << "gdb" << "--nx" << "--batch";
<< "-ex" << "thread apply all bt" if (ip)
argv << "-ex" << disasmInstr;
argv << "-ex" << "thread apply all bt"
<< "-ex" << "printf \"\\n\"" << "-ex" << "printf \"\\n\""
<< "-ex" << "info proc mappings" << "-ex" << "info proc mappings"
<< "--pid"; << "--pid";
break; break;
case Lldb: case Lldb:
argv << "lldb" << "--no-lldbinit" << "--batch" argv << "lldb" << "--no-lldbinit" << "--batch";
<< "-o" << "bt all" if (ip)
argv << "-o" << disasmInstr;
argv << "-o" << "bt all"
<< "--attach-pid"; << "--attach-pid";
break; break;
} }
@ -425,6 +444,8 @@ void generateStackTrace()
} }
writeToStderr("=== End of stack trace ===\n"); writeToStderr("=== End of stack trace ===\n");
# else
Q_UNUSED(ip);
# endif // !Q_OS_INTEGRITY && !Q_OS_VXWORKS # endif // !Q_OS_INTEGRITY && !Q_OS_VXWORKS
} }
@ -453,14 +474,16 @@ printSentSignalInfo(T *info)
[[maybe_unused]] static void printSentSignalInfo(...) {} [[maybe_unused]] static void printSentSignalInfo(...) {}
template <typename T> static std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> template <typename T> static std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1>
printCrashingSignalInfo(T *info) printCrashingSignalInfo(T *info, quintptr pc)
{ {
using HexString = std::array<char, sizeof(quintptr) * 2 + 2>; using HexString = std::array<char, sizeof(quintptr) * 2 + 2>;
auto toHexString = [](quintptr u, HexString &&r = {}) { auto toHexString = [](quintptr u, HexString &&r = {}) {
return asyncSafeToHexString(u, r.data()); return asyncSafeToHexString(u, r.data());
}; };
writeToStderr(", code ", asyncSafeToString(info->si_code), writeToStderr(", code ", asyncSafeToString(info->si_code));
", for address ", toHexString(quintptr(info->si_addr))); if (pc)
writeToStderr(", at instruction address ", toHexString(pc));
writeToStderr(", accessing address ", toHexString(quintptr(info->si_addr)));
} }
[[maybe_unused]] static void printCrashingSignalInfo(...) {} [[maybe_unused]] static void printCrashingSignalInfo(...) {}
@ -581,23 +604,24 @@ void FatalSignalHandler::freeAlternateStack()
# endif # endif
} }
void actionHandler(int signum, siginfo_t *info, void *) void actionHandler(int signum, siginfo_t *info, void *ucontext)
{ {
writeToStderr("Received signal ", asyncSafeToString(signum), writeToStderr("Received signal ", asyncSafeToString(signum),
" (SIG", signalName(signum), ")"); " (SIG", signalName(signum), ")");
quintptr pc = 0;
bool isCrashingSignal = bool isCrashingSignal =
std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end(); std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end();
if (isCrashingSignal && (!info || info->si_code <= 0)) if (isCrashingSignal && (!info || info->si_code <= 0))
isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
if (isCrashingSignal) if (isCrashingSignal)
printCrashingSignalInfo(info); printCrashingSignalInfo(info, (pc = getProgramCounter(ucontext)));
else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE)) else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
printSentSignalInfo(info); printSentSignalInfo(info);
printTestRunTime(); printTestRunTime();
if (signum != SIGINT) { if (signum != SIGINT) {
generateStackTrace(); generateStackTrace(pc);
if (pauseOnCrash) { if (pauseOnCrash) {
writeToStderr("Pausing process ", asyncSafeToString(getpid()), writeToStderr("Pausing process ", asyncSafeToString(getpid()),
" for debugging\n"); " for debugging\n");

View File

@ -57,12 +57,13 @@ void printTestRunTime()
name ? name : "[Non-test]", msecsFunctionTime, msecsTotalTime); name ? name : "[Non-test]", msecsFunctionTime, msecsTotalTime);
} }
void generateStackTrace() void generateStackTrace(quintptr ip)
{ {
if (debugger == None || alreadyDebugging()) if (debugger == None || alreadyDebugging())
return; return;
// ### Implement starting a debugger on Windows // ### Implement starting a debugger on Windows
Q_UNUSED(ip);
} }
void blockUnixSignals() void blockUnixSignals()