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 }