kit

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

displaced.c (4813B)


      1 /* Displaced single-step plumbing.
      2  *
      3  * Reserves a single executable page (W^X dual-mapped via the JitHost
      4  * execmem) the first time STEP_INSN is requested. The per-arch lifter
      5  * copies a fixed-up version of the instruction at insn_pc into that page,
      6  * followed by a trap sentinel; the session arms an internal breakpoint on
      7  * the sentinel and resumes with PC = scratch_runtime. On the trap fault,
      8  * the fault classifier sees the internal bp and the session uses
      9  * dbg_displaced_finalize to restore the user-visible PC. */
     10 
     11 #include <string.h>
     12 
     13 #include "dbg/dbg.h"
     14 
     15 KitStatus dbg_displaced_init(KitJitSession* s) {
     16   const KitExecMem* mem;
     17   KitStatus st;
     18   if (s->displaced.valid) return KIT_OK;
     19   mem = s->execmem;
     20   if (!mem || !mem->reserve || !mem->protect) return KIT_UNSUPPORTED;
     21   memset(&s->displaced.region, 0, sizeof(s->displaced.region));
     22   st = mem->reserve(mem->user, DBG_SCRATCH_PAGE_SIZE,
     23                     KIT_PROT_READ | KIT_PROT_EXEC, &s->displaced.region);
     24   if (st != KIT_OK) return st;
     25   st = mem->protect(mem->user, s->displaced.region.runtime,
     26                     s->displaced.region.size, KIT_PROT_READ | KIT_PROT_EXEC);
     27   if (st != KIT_OK) {
     28     mem->release(mem->user, &s->displaced.region);
     29     memset(&s->displaced.region, 0, sizeof(s->displaced.region));
     30     return st;
     31   }
     32   s->displaced.valid = 1;
     33   return KIT_OK;
     34 }
     35 
     36 void dbg_displaced_fini(KitJitSession* s) {
     37   const KitExecMem* mem = s->execmem;
     38   if (!s->displaced.valid) return;
     39   if (mem && mem->release) mem->release(mem->user, &s->displaced.region);
     40   memset(&s->displaced, 0, sizeof(s->displaced));
     41 }
     42 
     43 KitStatus dbg_displaced_prepare(KitJitSession* s, uint64_t insn_pc,
     44                                 uint64_t* new_pc) {
     45   ArchDbgInsn insn;
     46   u32 brk_off = 0;
     47   uint64_t scratch_runtime;
     48   uint64_t fallthrough_pc = 0;
     49   uint8_t* scratch_write;
     50   u32 bp_id = 0;
     51   KitStatus st;
     52   const KitExecMem* mem;
     53 
     54   if (!s->arch_dbg || !s->arch_dbg->build_displaced_shim) {
     55     return KIT_UNSUPPORTED;
     56   }
     57   st = dbg_displaced_init(s);
     58   if (st != KIT_OK) return st;
     59 
     60   /* A previous step whose shim transferred control (indirect branch or
     61    * a CBZ/TBZ trampoline that took) never ran finalize, leaving a stale
     62    * internal bp at the old return_pc. Drop it before we lay down the
     63    * new shim — bp.c is idempotent, but the refcount would climb. */
     64   if (s->displaced.internal_bp != 0) {
     65     dbg_bp_clear(s, s->displaced.internal_bp);
     66     s->displaced.internal_bp = 0;
     67     s->displaced.return_pc = 0;
     68     s->displaced.fallthrough_pc = 0;
     69     s->displaced.orig_pc = 0;
     70   }
     71 
     72   st = dbg_arch_decode_insn(s, insn_pc, &insn);
     73   if (st != KIT_OK) return st;
     74 
     75   scratch_runtime = (uint64_t)(uintptr_t)s->displaced.region.runtime;
     76   scratch_write = (uint8_t*)s->displaced.region.write;
     77   st = s->arch_dbg->build_displaced_shim(&insn, scratch_write, scratch_runtime,
     78                                          DBG_DISPLACED_SLOT_BYTES, &brk_off,
     79                                          &fallthrough_pc);
     80   if (st != KIT_OK) return st;
     81 
     82   /* Flush the entire slot; trampoline forms may write past the sentinel. */
     83   mem = s->execmem;
     84   if (mem && mem->flush_icache) {
     85     mem->flush_icache(mem->user, s->displaced.region.runtime,
     86                       DBG_DISPLACED_SLOT_BYTES);
     87   }
     88 
     89   /* Arm an internal breakpoint on the shim's trap sentinel so the fault
     90    * classifier identifies it as a displaced-step completion. */
     91   st = dbg_bp_set_internal(s, scratch_runtime + brk_off, &bp_id);
     92   if (st != KIT_OK) return st;
     93   s->displaced.orig_pc = insn_pc;
     94   s->displaced.return_pc = scratch_runtime + brk_off;
     95   s->displaced.fallthrough_pc = fallthrough_pc;
     96   s->displaced.internal_bp = bp_id;
     97   if (new_pc) *new_pc = scratch_runtime;
     98   return KIT_OK;
     99 }
    100 
    101 void dbg_displaced_finalize(KitJitSession* s) {
    102   if (s->displaced.internal_bp != 0) {
    103     dbg_bp_clear(s, s->displaced.internal_bp);
    104     s->displaced.internal_bp = 0;
    105   }
    106   /* Restore PC to the instruction following the original, unless the
    107    * fixed-up branch took (in which case PC will already be elsewhere
    108    * and we leave it alone). */
    109   if (s->stop.regs.pc == s->displaced.return_pc) {
    110     s->stop.regs.pc = s->displaced.fallthrough_pc;
    111   }
    112   s->displaced.orig_pc = 0;
    113   s->displaced.return_pc = 0;
    114   s->displaced.fallthrough_pc = 0;
    115 }
    116 
    117 KitStatus dbg_arch_decode_insn(KitJitSession* s, uint64_t pc,
    118                                ArchDbgInsn* out) {
    119   uint8_t buf[ARCH_DBG_MAX_INSN_BYTES];
    120   KitStatus st;
    121   u32 max_len;
    122   if (!s || !out || !s->arch_dbg || !s->arch_dbg->decode_insn)
    123     return KIT_UNSUPPORTED;
    124   max_len = s->arch_dbg->max_insn_len;
    125   if (max_len == 0 || max_len > ARCH_DBG_MAX_INSN_BYTES) return KIT_UNSUPPORTED;
    126   st = dbg_mem_read(s, pc, buf, max_len);
    127   if (st != KIT_OK) return st;
    128   return s->arch_dbg->decode_insn(buf, max_len, pc, out);
    129 }