[common] debug: unwind all threads and print on force quit

This commit is contained in:
Geoffrey McRae
2026-07-29 11:05:31 +10:00
parent 4999e24bb7
commit 6dd91a1269
4 changed files with 177 additions and 27 deletions

View File

@@ -1236,6 +1236,7 @@ void intHandler(int sig)
else else
{ {
DEBUG_INFO("Caught second signal, force quitting..."); DEBUG_INFO("Caught second signal, force quitting...");
printAllThreadBacktraces();
signal(sig, SIG_DFL); signal(sig, SIG_DFL);
raise(sig); raise(sig);
} }

View File

@@ -25,5 +25,6 @@
bool installCrashHandler(const char * exe); bool installCrashHandler(const char * exe);
void cleanupCrashHandler(void); void cleanupCrashHandler(void);
void printAllThreadBacktraces(void);
#endif #endif

View File

@@ -34,6 +34,7 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <ucontext.h> #include <ucontext.h>
@@ -41,9 +42,18 @@
#include <inttypes.h> #include <inttypes.h>
#define CRASH_MAX_FRAMES 64 #define CRASH_MAX_FRAMES 64
enum CrashRequestType
{
CRASH_REQUEST_BACKTRACE,
CRASH_REQUEST_FATAL,
CRASH_REQUEST_ALL_THREADS
};
struct CrashRequest struct CrashRequest
{ {
enum CrashRequestType type;
int signal; int signal;
pid_t threadId;
uintptr_t faultAddress; uintptr_t faultAddress;
unsigned frameCount; unsigned frameCount;
uintptr_t frames[CRASH_MAX_FRAMES]; uintptr_t frames[CRASH_MAX_FRAMES];
@@ -106,11 +116,12 @@ static bool readAll(int fd, void * buffer, size_t size)
return true; return true;
} }
static void reportFrame(Dwfl * dwfl, unsigned index, uintptr_t pc) static void reportFrame(Dwfl * dwfl, unsigned index, uintptr_t pc,
bool isActivation)
{ {
// Except for the faulting instruction, an IP is a return address. Looking // A non-activation IP is a return address. Looking up the preceding byte
// up the preceding byte attributes it to the call instruction. // attributes it to the call instruction.
Dwarf_Addr address = pc - (index != 0 && pc != 0); Dwarf_Addr address = pc - (!isActivation && pc != 0);
Dwfl_Module * module = dwfl_addrmodule(dwfl, address); Dwfl_Module * module = dwfl_addrmodule(dwfl, address);
if (!module) if (!module)
{ {
@@ -151,9 +162,106 @@ static void reportFrame(Dwfl * dwfl, unsigned index, uintptr_t pc)
moduleName, (uint64_t)(address - moduleStart), pc); moduleName, (uint64_t)(address - moduleStart), pc);
} }
static void reportCrash(pid_t parent, const struct CrashRequest * request) struct ThreadFrames
{ {
if (request->signal) Dwfl * dwfl;
unsigned index;
};
static int reportThreadFrame(Dwfl_Frame * frame, void * opaque)
{
struct ThreadFrames * frames = opaque;
Dwarf_Addr pc;
bool isActivation;
if (dwfl_frame_pc(frame, &pc, &isActivation))
reportFrame(frames->dwfl, frames->index++, pc, isActivation);
return DWARF_CB_OK;
}
struct AllThreads
{
Dwfl * dwfl;
pid_t processId;
unsigned threadCount;
unsigned unwindCount;
};
static void getThreadName(pid_t processId, pid_t threadId, char * name,
size_t size)
{
char path[64];
snprintf(path, sizeof(path), "/proc/%d/task/%d/comm", processId, threadId);
int fd = open(path, O_RDONLY);
if (fd < 0)
return;
ssize_t length = read(fd, name, size - 1);
close(fd);
if (length <= 0)
return;
if (name[length - 1] == '\n')
--length;
name[length] = '\0';
}
static int reportThread(Dwfl_Thread * thread, void * opaque)
{
struct AllThreads * all = opaque;
const pid_t threadId = dwfl_thread_tid(thread);
char name[64] = "unknown";
getThreadName(all->processId, threadId, name, sizeof(name));
DEBUG_ERROR("---- thread %d (%s) ----", threadId, name);
struct ThreadFrames frames =
{
.dwfl = all->dwfl
};
if (dwfl_thread_getframes(thread, reportThreadFrame, &frames) < 0)
DEBUG_ERROR("Unable to unwind thread %d: %s", threadId, dwfl_errmsg(-1));
if (frames.index)
++all->unwindCount;
++all->threadCount;
return DWARF_CB_OK;
}
static void reportAllThreads(Dwfl * dwfl, pid_t parent,
const struct CrashRequest * request)
{
DEBUG_ERROR("==== ALL THREAD BACKTRACES (%s) ====", BUILD_VERSION);
if (dwfl_linux_proc_attach(dwfl, parent, false) == 0)
{
struct AllThreads all =
{
.dwfl = dwfl,
.processId = parent
};
int result = dwfl_getthreads(dwfl, reportThread, &all);
if (result == 0 && all.unwindCount)
return;
if (result < 0)
DEBUG_ERROR("Unable to enumerate threads: %s", dwfl_errmsg(-1));
else
DEBUG_ERROR("Unable to unwind any of %u threads", all.threadCount);
}
else
DEBUG_ERROR("Unable to attach to process %d: %s", parent, dwfl_errmsg(-1));
DEBUG_ERROR("Falling back to the requesting thread %d", request->threadId);
for (unsigned i = 0; i < request->frameCount; ++i)
reportFrame(dwfl, i, request->frames[i], i == 0);
}
static void reportRequest(pid_t parent, const struct CrashRequest * request)
{
if (request->type == CRASH_REQUEST_FATAL)
{ {
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION); DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
DEBUG_ERROR("signal %d (%s), address is 0x%" PRIxPTR, request->signal, DEBUG_ERROR("signal %d (%s), address is 0x%" PRIxPTR, request->signal,
@@ -183,8 +291,11 @@ static void reportCrash(pid_t parent, const struct CrashRequest * request)
goto raw; goto raw;
} }
if (request->type == CRASH_REQUEST_ALL_THREADS)
reportAllThreads(dwfl, parent, request);
else
for (unsigned i = 0; i < request->frameCount; ++i) for (unsigned i = 0; i < request->frameCount; ++i)
reportFrame(dwfl, i, request->frames[i]); reportFrame(dwfl, i, request->frames[i], i == 0);
dwfl_end(dwfl); dwfl_end(dwfl);
return; return;
@@ -209,7 +320,7 @@ static void reporterLoop(pid_t parent, int requestFd, int responseFd)
struct CrashRequest request; struct CrashRequest request;
while (readAll(requestFd, &request, sizeof(request))) while (readAll(requestFd, &request, sizeof(request)))
{ {
reportCrash(parent, &request); reportRequest(parent, &request);
const uint8_t complete = 1; const uint8_t complete = 1;
if (!writeAll(responseFd, &complete, sizeof(complete))) if (!writeAll(responseFd, &complete, sizeof(complete)))
@@ -270,6 +381,10 @@ static bool startReporter(void)
crash.requestFd = requestPipe [1]; crash.requestFd = requestPipe [1];
crash.responseFd = responsePipe[0]; crash.responseFd = responsePipe[0];
if (prctl(PR_SET_PTRACER, child) != 0)
DEBUG_WARN("Unable to grant the crash reporter ptrace access; "
"all-thread backtraces may be unavailable");
uint8_t ready; uint8_t ready;
if (!readAll(crash.responseFd, &ready, sizeof(ready))) if (!readAll(crash.responseFd, &ready, sizeof(ready)))
{ {
@@ -303,21 +418,16 @@ void cleanupCrashHandler(void)
} }
} }
void printBacktrace(void) static void collectBacktrace(struct CrashRequest * request, unsigned skip)
{ {
struct CrashRequest request =
{
.signal = 0
};
unw_context_t context; unw_context_t context;
unw_cursor_t cursor; unw_cursor_t cursor;
if (unw_getcontext(&context) < 0 || if (unw_getcontext(&context) < 0 ||
unw_init_local(&cursor, &context) < 0) unw_init_local(&cursor, &context) < 0)
return; return;
// Omit unw_getcontext and printBacktrace itself. while (skip--)
if (unw_step(&cursor) <= 0 || unw_step(&cursor) <= 0) if (unw_step(&cursor) <= 0)
return; return;
do do
@@ -326,22 +436,53 @@ void printBacktrace(void)
if (unw_get_reg(&cursor, UNW_REG_IP, &pc) < 0 || !pc) if (unw_get_reg(&cursor, UNW_REG_IP, &pc) < 0 || !pc)
break; break;
request.frames[request.frameCount++] = (uintptr_t)pc; request->frames[request->frameCount++] = (uintptr_t)pc;
} }
while (request.frameCount < CRASH_MAX_FRAMES && unw_step(&cursor) > 0); while (request->frameCount < CRASH_MAX_FRAMES && unw_step(&cursor) > 0);
}
static void sendRequest(const struct CrashRequest * request)
{
if (crash.requestFd >= 0 && crash.responseFd >= 0 && if (crash.requestFd >= 0 && crash.responseFd >= 0 &&
writeAll(crash.requestFd, &request, sizeof(request))) writeAll(crash.requestFd, request, sizeof(*request)))
{ {
uint8_t complete; uint8_t complete;
readAll(crash.responseFd, &complete, sizeof(complete)); readAll(crash.responseFd, &complete, sizeof(complete));
} }
} }
void printBacktrace(void)
{
struct CrashRequest request =
{
.type = CRASH_REQUEST_BACKTRACE,
.threadId = (pid_t)syscall(SYS_gettid)
};
// Omit collectBacktrace and printBacktrace itself.
collectBacktrace(&request, 2);
sendRequest(&request);
}
void printAllThreadBacktraces(void)
{
struct CrashRequest request =
{
.type = CRASH_REQUEST_ALL_THREADS,
.threadId = (pid_t)syscall(SYS_gettid)
};
// These frames provide a useful fallback when ptrace is unavailable.
collectBacktrace(&request, 2);
sendRequest(&request);
}
static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{ {
struct CrashRequest request; struct CrashRequest request;
request.type = CRASH_REQUEST_FATAL;
request.signal = sig_num; request.signal = sig_num;
request.threadId = (pid_t)syscall(SYS_gettid);
request.faultAddress = (uintptr_t)info->si_addr; request.faultAddress = (uintptr_t)info->si_addr;
request.frameCount = 0; request.frameCount = 0;
@@ -361,12 +502,7 @@ static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
unw_step(&cursor) > 0); unw_step(&cursor) > 0);
} }
if (crash.requestFd >= 0 && crash.responseFd >= 0 && sendRequest(&request);
writeAll(crash.requestFd, &request, sizeof(request)))
{
uint8_t complete;
readAll(crash.responseFd, &complete, sizeof(complete));
}
// SA_RESETHAND has restored the default disposition and SA_NODEFER leaves // SA_RESETHAND has restored the default disposition and SA_NODEFER leaves
// this signal unblocked, so this preserves the original fatal signal and // this signal unblocked, so this preserves the original fatal signal and
@@ -413,4 +549,8 @@ void printBacktrace(void)
{ {
} }
void printAllThreadBacktraces(void)
{
}
#endif #endif

View File

@@ -154,9 +154,17 @@ bool installCrashHandler(const char * exe)
return true; return true;
} }
void printAllThreadBacktraces(void)
{
}
#else #else
bool installCrashHandler(const char * exe) bool installCrashHandler(const char * exe)
{ {
return true; return true;
} }
void printAllThreadBacktraces(void)
{
}
#endif #endif