Add a SIGSEGV handler to print a stack trace for central controllers

Only enabled in the central controller builds.  Trying to find more info
about some mystery controller restarts in prod
This commit is contained in:
Grant Limberg
2026-06-26 13:33:07 -07:00
parent 0797626b3e
commit edcc066d25
2 changed files with 65 additions and 0 deletions
+9
View File
@@ -313,6 +313,15 @@ if(ZT1_CENTRAL_CONTROLLER)
-DZT_OPENTELEMETRY_ENABLED=1
-DZT_NO_PEER_METRICS=1
)
# The controller installs a fatal-signal handler (one.cpp) that prints a backtrace
# via backtrace()/backtrace_symbols_fd() on SIGSEGV/SIGABRT/etc. Keep debug info even
# in Release (-O3 -g; no runtime cost) and export the executable's dynamic symbols
# (-rdynamic) so the backtrace resolves function names instead of bare addresses.
# Controller-only; not built on Windows (enforced above).
if(NOT MSVC)
add_compile_options(-g)
add_link_options(-rdynamic)
endif()
endif()
# ---------------------------------------------------------------------------
+56
View File
@@ -1673,6 +1673,10 @@ static int idtool(int argc, char** argv)
/* Unix helper functions and signal handlers */
/****************************************************************************/
#ifdef ZT1_CENTRAL_CONTROLLER
#include <execinfo.h> // backtrace()/backtrace_symbols_fd() for the controller crash handler
#endif
#ifdef __UNIX_LIKE__
static void _sighandlerHup(int sig)
{
@@ -1690,6 +1694,36 @@ static void _sighandlerQuit(int sig)
else
exit(0);
}
#ifdef ZT1_CENTRAL_CONTROLLER
// Controller-only fatal-signal handler. The controller otherwise dies silently on a
// SIGSEGV/SIGABRT/SIGBUS (exit 139/134/135) with nothing in the logs, which makes an
// intermittent crash nearly impossible to locate. Dump a backtrace to stderr (captured
// by `kubectl logs`), then re-raise the default handler so the process still terminates
// with the original signal (preserving the exit code and any core dump).
//
// backtrace_symbols_fd() is async-signal-safe (it does not call malloc, unlike
// backtrace_symbols()). The handler runs on a dedicated alternate stack (SA_ONSTACK) so
// it can still produce output even on a stack-overflow crash.
static char _fatalSigAltStack[65536];
static void _sighandlerFatal(int sig, siginfo_t* info, void*)
{
void* frames[64];
const int n = backtrace(frames, 64);
char hdr[160];
const int hlen = snprintf(
hdr, sizeof(hdr), "\nFATAL: caught signal %d (si_addr=%p), backtrace (%d frames):\n", sig,
(info ? info->si_addr : (void*)0), n);
if (hlen > 0)
(void)!write(STDERR_FILENO, hdr, (size_t)hlen);
backtrace_symbols_fd(frames, n, STDERR_FILENO);
// Re-raise with the default disposition so we exit with the original signal.
signal(sig, SIG_DFL);
raise(sig);
}
#endif // ZT1_CENTRAL_CONTROLLER
#endif
// Drop privileges on Linux, if supported by libc etc. and "zerotier-one" user exists on system
@@ -2112,6 +2146,28 @@ int main(int argc, char** argv)
signal(SIGQUIT, &_sighandlerQuit);
signal(SIGINT, &_sighandlerQuit);
#ifdef ZT1_CENTRAL_CONTROLLER
// Controller-only: install a backtrace-dumping handler for fatal signals so an
// otherwise-silent crash leaves a stack trace in the logs. See _sighandlerFatal.
{
stack_t ss;
ss.ss_sp = _fatalSigAltStack;
ss.ss_size = sizeof(_fatalSigAltStack);
ss.ss_flags = 0;
sigaltstack(&ss, (stack_t*)0);
struct sigaction fsa = {};
fsa.sa_sigaction = &_sighandlerFatal;
fsa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESETHAND;
sigemptyset(&fsa.sa_mask);
sigaction(SIGSEGV, &fsa, (struct sigaction*)0);
sigaction(SIGABRT, &fsa, (struct sigaction*)0);
sigaction(SIGBUS, &fsa, (struct sigaction*)0);
sigaction(SIGFPE, &fsa, (struct sigaction*)0);
sigaction(SIGILL, &fsa, (struct sigaction*)0);
}
#endif
#ifdef ZT_EXTOSDEP
int extosdepFd1 = -1;
int extosdepFd2 = -1;