kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 };