posix_dbg.c (12817B)
1 /* POSIX dbg_os scaffolding shared across mac/linux/freebsd: pthreads for 2 * worker thread and event objects, sigaction-based signal handling that 3 * delegates per-(arch,OS) ucontext marshalling to dbg_ucontext_to_frame / 4 * dbg_frame_to_ucontext, and a sigsetjmp-guarded memory copy. 5 * 6 * The W^X transitions (code_write_begin/end) and icache flush are wired 7 * directly from the per-OS file, since they're the genuine divergence 8 * point between Apple's alias-only model and the Linux/FreeBSD memfd 9 * model. 10 */ 11 12 #include <pthread.h> 13 #include <setjmp.h> 14 #include <signal.h> 15 #include <stdint.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include "backtrace.h" 20 #include "env_posix.h" 21 22 /* Single-session process model (one debug target at a time). The signal 23 * handler reads these from async-signal context; both writes happen in 24 * signals_install before any signal can arrive, both clears happen in 25 * signals_uninstall after restoring SIG_DFL. */ 26 static KitDbgSignalOps g_dbg_ops; 27 static int g_dbg_ops_set; 28 static void* g_dbg_session; 29 static pthread_t g_dbg_worker_tid; 30 static int g_dbg_worker_tid_valid; 31 32 static const int g_dbg_signos[6] = {SIGTRAP, SIGSEGV, SIGBUS, 33 SIGILL, SIGFPE, DBG_INTERRUPT_SIGNO}; 34 #define DBG_NSIGS ((int)(sizeof(g_dbg_signos) / sizeof(g_dbg_signos[0]))) 35 static struct sigaction g_dbg_prev_sa[DBG_NSIGS]; 36 static int g_dbg_installed; 37 38 /* TLS landing slot for guarded_copy. The SEGV/BUS handler checks 39 * g_guard_armed first; if set it siglongjmps back into guarded_copy 40 * without touching on_fault. */ 41 static __thread sigjmp_buf g_guard_buf; 42 static __thread int g_guard_armed; 43 44 /* --- thread shim --- */ 45 46 typedef struct DbgThread { 47 pthread_t tid; 48 void (*fn)(void*); 49 void* arg; 50 } DbgThread; 51 52 static void* dbg_thread_trampoline(void* p) { 53 DbgThread* t = (DbgThread*)p; 54 t->fn(t->arg); 55 return NULL; 56 } 57 58 static KitStatus dbg_thread_start(void* user, void (*fn)(void*), void* arg, 59 void** thread_out) { 60 DbgThread* t; 61 (void)user; 62 t = (DbgThread*)malloc(sizeof(*t)); 63 if (!t) return KIT_NOMEM; 64 t->fn = fn; 65 t->arg = arg; 66 if (pthread_create(&t->tid, NULL, dbg_thread_trampoline, t) != 0) { 67 free(t); 68 return KIT_ERR; 69 } 70 g_dbg_worker_tid = t->tid; 71 g_dbg_worker_tid_valid = 1; 72 *thread_out = t; 73 return KIT_OK; 74 } 75 76 static void dbg_thread_join(void* user, void* thread) { 77 DbgThread* t = (DbgThread*)thread; 78 (void)user; 79 if (!t) return; 80 pthread_join(t->tid, NULL); 81 g_dbg_worker_tid_valid = 0; 82 free(t); 83 } 84 85 static KitStatus dbg_thread_interrupt(void* user, void* thread) { 86 DbgThread* t = (DbgThread*)thread; 87 (void)user; 88 if (!t) return KIT_INVALID; 89 return pthread_kill(t->tid, DBG_INTERRUPT_SIGNO) == 0 ? KIT_OK : KIT_ERR; 90 } 91 92 int posix_dbg_caller_is_worker(void) { 93 return g_dbg_worker_tid_valid && 94 pthread_equal(pthread_self(), g_dbg_worker_tid); 95 } 96 97 /* --- event shim --- */ 98 99 typedef struct DbgEvent { 100 pthread_mutex_t mu; 101 pthread_cond_t cv; 102 int signaled; 103 } DbgEvent; 104 105 static KitStatus dbg_event_new(void* user, void** event_out) { 106 DbgEvent* e; 107 (void)user; 108 e = (DbgEvent*)malloc(sizeof(*e)); 109 if (!e) return KIT_NOMEM; 110 if (pthread_mutex_init(&e->mu, NULL) != 0) { 111 free(e); 112 return KIT_ERR; 113 } 114 if (pthread_cond_init(&e->cv, NULL) != 0) { 115 pthread_mutex_destroy(&e->mu); 116 free(e); 117 return KIT_ERR; 118 } 119 e->signaled = 0; 120 *event_out = e; 121 return KIT_OK; 122 } 123 124 static void dbg_event_free(void* user, void* ev) { 125 DbgEvent* e = (DbgEvent*)ev; 126 (void)user; 127 if (!e) return; 128 pthread_cond_destroy(&e->cv); 129 pthread_mutex_destroy(&e->mu); 130 free(e); 131 } 132 133 static KitStatus dbg_event_wait(void* user, void* ev) { 134 DbgEvent* e = (DbgEvent*)ev; 135 (void)user; 136 pthread_mutex_lock(&e->mu); 137 while (!e->signaled) pthread_cond_wait(&e->cv, &e->mu); 138 e->signaled = 0; 139 pthread_mutex_unlock(&e->mu); 140 return KIT_OK; 141 } 142 143 /* pthread_cond_signal is not formally async-signal-safe per POSIX, but 144 * it is in practice on glibc and Apple libc when callers manage the 145 * signal mask carefully. LLDB and rr rely on the same pattern. */ 146 static KitStatus dbg_event_signal(void* user, void* ev) { 147 DbgEvent* e = (DbgEvent*)ev; 148 (void)user; 149 pthread_mutex_lock(&e->mu); 150 e->signaled = 1; 151 pthread_cond_broadcast(&e->cv); 152 pthread_mutex_unlock(&e->mu); 153 return KIT_OK; 154 } 155 156 static KitStatus dbg_event_reset(void* user, void* ev) { 157 DbgEvent* e = (DbgEvent*)ev; 158 (void)user; 159 pthread_mutex_lock(&e->mu); 160 e->signaled = 0; 161 pthread_mutex_unlock(&e->mu); 162 return KIT_OK; 163 } 164 165 /* --- signal install + ucontext dispatch --- */ 166 167 static void dbg_signal_handler(int signo, siginfo_t* si, void* ucv) { 168 ucontext_t* uc = (ucontext_t*)ucv; 169 KitUnwindFrame frame; 170 KitStatus rc; 171 (void)si; 172 173 /* SIGSEGV/SIGBUS during an armed guarded_copy: bail out to the 174 * sigsetjmp landing slot before the session ever sees the fault. */ 175 if ((signo == SIGSEGV || signo == SIGBUS) && g_guard_armed) { 176 g_guard_armed = 0; 177 siglongjmp(g_guard_buf, 1); 178 } 179 180 /* Only the registered worker thread participates in stop-the-world. 181 * Faults on other threads (e.g. the REPL) fall through to the default. */ 182 if (!posix_dbg_caller_is_worker() || !g_dbg_ops_set || !g_dbg_ops.on_fault) { 183 int i; 184 for (i = 0; i < DBG_NSIGS; ++i) { 185 if (g_dbg_signos[i] == signo) { 186 sigaction(signo, &g_dbg_prev_sa[i], NULL); 187 break; 188 } 189 } 190 raise(signo); 191 return; 192 } 193 194 dbg_ucontext_to_frame(uc, &frame); 195 rc = g_dbg_ops.on_fault(g_dbg_session, signo, &frame); 196 if (rc != KIT_OK) { 197 int i; 198 for (i = 0; i < DBG_NSIGS; ++i) { 199 if (g_dbg_signos[i] == signo) { 200 sigaction(signo, &g_dbg_prev_sa[i], NULL); 201 break; 202 } 203 } 204 raise(signo); 205 return; 206 } 207 dbg_frame_to_ucontext(&frame, uc); 208 } 209 210 static KitStatus dbg_signals_install(void* user, const KitDbgSignalOps* ops, 211 void* session) { 212 struct sigaction sa; 213 int i; 214 (void)user; 215 if (g_dbg_installed) return KIT_ERR; 216 g_dbg_ops = *ops; 217 g_dbg_ops_set = 1; 218 g_dbg_session = session; 219 220 memset(&sa, 0, sizeof(sa)); 221 sa.sa_sigaction = dbg_signal_handler; 222 sa.sa_flags = SA_SIGINFO | SA_RESTART; 223 sigemptyset(&sa.sa_mask); 224 for (i = 0; i < DBG_NSIGS; ++i) sigaddset(&sa.sa_mask, g_dbg_signos[i]); 225 226 for (i = 0; i < DBG_NSIGS; ++i) { 227 if (sigaction(g_dbg_signos[i], &sa, &g_dbg_prev_sa[i]) != 0) { 228 int j; 229 for (j = 0; j < i; ++j) 230 sigaction(g_dbg_signos[j], &g_dbg_prev_sa[j], NULL); 231 memset(&g_dbg_ops, 0, sizeof(g_dbg_ops)); 232 g_dbg_ops_set = 0; 233 g_dbg_session = NULL; 234 return KIT_ERR; 235 } 236 } 237 g_dbg_installed = 1; 238 return KIT_OK; 239 } 240 241 static void dbg_signals_uninstall(void* user) { 242 int i; 243 (void)user; 244 if (!g_dbg_installed) return; 245 for (i = 0; i < DBG_NSIGS; ++i) 246 sigaction(g_dbg_signos[i], &g_dbg_prev_sa[i], NULL); 247 g_dbg_installed = 0; 248 memset(&g_dbg_ops, 0, sizeof(g_dbg_ops)); 249 g_dbg_ops_set = 0; 250 g_dbg_session = NULL; 251 } 252 253 /* --- guarded copy --- */ 254 255 static KitStatus dbg_guarded_copy(void* user, void* dst, const void* src, 256 size_t n) { 257 (void)user; 258 if (sigsetjmp(g_guard_buf, 1) != 0) { 259 g_guard_armed = 0; 260 return KIT_ERR; /* SIGSEGV/SIGBUS during the copy */ 261 } 262 g_guard_armed = 1; 263 memcpy(dst, src, n); 264 g_guard_armed = 0; 265 return KIT_OK; 266 } 267 268 static __thread sigjmp_buf g_dbg_abort_buf; 269 270 static int dbg_call_with_catch(void* user, void (*fn)(void*), void* arg) { 271 (void)user; 272 if (sigsetjmp(g_dbg_abort_buf, 1) == 0) { 273 fn(arg); 274 return 0; 275 } 276 return 1; 277 } 278 279 static void dbg_thread_abort(void* user) { 280 (void)user; 281 siglongjmp(g_dbg_abort_buf, 1); 282 } 283 284 /* --- kit run crash guard -------------------------------------------------- 285 * Independent of the session machinery above (no worker thread, no KitDbgOs): 286 * `kit run` calls the JITed entry directly in-process, sharing its stack with 287 * the program. So we catch a fatal fault, marshal the faulting frame via the 288 * same per-(arch,OS) ucontext path the session uses, and walk the frame-pointer 289 * chain *here in the handler* — where those frames are still intact. After the 290 * siglongjmp the guard's own call frames reuse that stack, so the walk must 291 * finish first; only the captured return-address list survives, to be 292 * symbolized later in normal context (DWARF/malloc/printf never run async). */ 293 294 #define RUN_BT_MAX 256 295 static const int g_run_signos[6] = {SIGSEGV, SIGBUS, SIGILL, 296 SIGFPE, SIGABRT, SIGTRAP}; 297 #define RUN_NSIGS ((int)(sizeof(g_run_signos) / sizeof(g_run_signos[0]))) 298 static struct sigaction g_run_prev_sa[RUN_NSIGS]; 299 static int g_run_installed; 300 301 static sigjmp_buf g_run_crash_buf; /* fault -> back into the guard */ 302 static volatile sig_atomic_t g_run_crash_armed; 303 static KitArchKind g_run_crash_arch; 304 static int g_run_crash_signo; 305 static uint64_t g_run_crash_pcs[RUN_BT_MAX]; 306 static int g_run_crash_npcs; 307 308 static void run_crash_restore(void) { 309 int i; 310 if (!g_run_installed) return; 311 for (i = 0; i < RUN_NSIGS; ++i) 312 sigaction(g_run_signos[i], &g_run_prev_sa[i], NULL); 313 g_run_installed = 0; 314 } 315 316 /* Raw in-handler memory read. The FP-walk guards (alignment, strictly 317 * increasing fp, bounded depth) keep this from running away on a corrupt 318 * chain, mirroring rt's __kit_backtrace. */ 319 static KitStatus run_raw_read(void* user, uint64_t addr, void* dst, size_t n) { 320 (void)user; 321 memcpy(dst, (const void*)(uintptr_t)addr, n); 322 return KIT_OK; 323 } 324 325 static void run_crash_handler(int signo, siginfo_t* si, void* ucv) { 326 ucontext_t* uc = (ucontext_t*)ucv; 327 (void)si; 328 if (g_run_crash_armed) { 329 KitUnwindFrame fr; 330 int fpreg, n = 0; 331 g_run_crash_armed = 0; 332 g_run_crash_signo = signo; 333 dbg_ucontext_to_frame(uc, &fr); 334 g_run_crash_pcs[n++] = fr.pc; /* frame #0 = the faulting PC */ 335 fpreg = driver_bt_fp_dwarf_reg(g_run_crash_arch); 336 if (fpreg >= 0) { 337 uint64_t fp = fr.regs[fpreg], ra = 0, next_fp = 0; 338 while (n < RUN_BT_MAX && driver_bt_fp_step(g_run_crash_arch, run_raw_read, 339 NULL, fp, &ra, &next_fp)) { 340 g_run_crash_pcs[n++] = ra; 341 fp = next_fp; 342 } 343 } 344 g_run_crash_npcs = n; 345 siglongjmp(g_run_crash_buf, 1); 346 } 347 /* Not ours (or a fault after disarm): restore the default disposition for 348 * this signal and re-raise so the process dies as it would have. */ 349 { 350 int i; 351 for (i = 0; i < RUN_NSIGS; ++i) 352 if (g_run_signos[i] == signo) { 353 sigaction(signo, &g_run_prev_sa[i], NULL); 354 break; 355 } 356 } 357 raise(signo); 358 } 359 360 int driver_run_with_crash_guard(DriverEnv* env, KitArchKind arch, 361 DriverRunEntryFn entry, int argc, char** argv, 362 int* ret_out, DriverRunCrashFn on_crash, 363 void* user) { 364 struct sigaction sa; 365 int i; 366 (void)env; 367 368 g_run_crash_arch = arch; 369 370 memset(&sa, 0, sizeof sa); 371 sa.sa_sigaction = run_crash_handler; 372 sa.sa_flags = SA_SIGINFO; 373 sigemptyset(&sa.sa_mask); 374 for (i = 0; i < RUN_NSIGS; ++i) sigaddset(&sa.sa_mask, g_run_signos[i]); 375 376 g_run_installed = 1; 377 for (i = 0; i < RUN_NSIGS; ++i) { 378 if (sigaction(g_run_signos[i], &sa, &g_run_prev_sa[i]) != 0) { 379 int j; 380 for (j = 0; j < i; ++j) 381 sigaction(g_run_signos[j], &g_run_prev_sa[j], NULL); 382 g_run_installed = 0; 383 *ret_out = entry(argc, argv); /* could not guard — run unguarded */ 384 return 0; 385 } 386 } 387 388 if (sigsetjmp(g_run_crash_buf, 1) != 0) { 389 /* Reached via a fault. Restore default dispositions first so symbolization 390 * (which touches libkit, not the corrupt stack) faults cleanly if it ever 391 * goes wrong, then hand the captured chain to the caller. */ 392 run_crash_restore(); 393 if (on_crash) 394 on_crash(user, g_run_crash_signo, g_run_crash_pcs, g_run_crash_npcs); 395 return 1; 396 } 397 398 g_run_crash_armed = 1; 399 *ret_out = entry(argc, argv); 400 g_run_crash_armed = 0; 401 run_crash_restore(); 402 return 0; 403 } 404 405 /* --- vtable --- */ 406 407 KitDbgOs g_dbg_os_posix = { 408 .thread_start = dbg_thread_start, 409 .thread_join = dbg_thread_join, 410 .thread_interrupt = dbg_thread_interrupt, 411 .event_new = dbg_event_new, 412 .event_free = dbg_event_free, 413 .event_wait = dbg_event_wait, 414 .event_signal = dbg_event_signal, 415 .event_reset = dbg_event_reset, 416 .signals_install = dbg_signals_install, 417 .signals_uninstall = dbg_signals_uninstall, 418 .interrupt_signo = DBG_INTERRUPT_SIGNO, 419 .trap_signo = SIGTRAP, 420 .code_write_begin = os_dbg_code_write_begin, 421 .code_write_end = os_dbg_code_write_end, 422 .flush_icache = os_dbg_flush_icache, 423 .guarded_copy = dbg_guarded_copy, 424 .call_with_catch = dbg_call_with_catch, 425 .thread_abort = dbg_thread_abort, 426 .user = NULL, 427 };