kit

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

dbg.c (11917B)


      1 /* x86_64 debug support for software breakpoints and displaced stepping.
      2  *
      3  * The decoder here is intentionally small: it covers the encodings kit's
      4  * x64 backend emits plus common branch forms. It measures one instruction,
      5  * finds any RIP-relative disp32 operand, and identifies rel8/rel32 control
      6  * transfers that need their displacement re-based for the scratch slot. */
      7 
      8 #include <string.h>
      9 
     10 #include "arch/arch.h"
     11 #include "core/bytes.h"
     12 
     13 #define X64_INT3_BYTE 0xCCu
     14 #define X64_JMP_REL32_BYTE 0xE9u
     15 #define X64_CALL_REL32_BYTE 0xE8u
     16 #define X64_JCC_SHORT_BASE 0x70u
     17 #define X64_JCC_NEAR_BASE0 0x0Fu
     18 #define X64_JCC_NEAR_BASE1 0x80u
     19 
     20 typedef enum X64DbgPcRelKind {
     21   X64_DBG_PCREL_NONE,
     22   X64_DBG_PCREL_RIP_DISP32,
     23   X64_DBG_PCREL_REL8,
     24   X64_DBG_PCREL_REL32,
     25 } X64DbgPcRelKind;
     26 
     27 typedef struct X64DbgDecode {
     28   u32 len;
     29   u32 opc_off;
     30   u32 disp_off;
     31   u8 disp_size;
     32   u8 pc_rel_kind;
     33   u8 is_call;
     34   u8 pad;
     35   i64 disp;
     36 } X64DbgDecode;
     37 
     38 static int fits_i32(i64 v) {
     39   return v >= (i64)INT32_MIN && v <= (i64)INT32_MAX;
     40 }
     41 static int fits_i8(i64 v) { return v >= -128 && v <= 127; }
     42 
     43 static int is_legacy_prefix(u8 b) {
     44   switch (b) {
     45     case 0x26:
     46     case 0x2e:
     47     case 0x36:
     48     case 0x3e:
     49     case 0x64:
     50     case 0x65:
     51     case 0x66:
     52     case 0x67:
     53     case 0xf0:
     54     case 0xf2:
     55     case 0xf3:
     56       return 1;
     57     default:
     58       return 0;
     59   }
     60 }
     61 
     62 static u32 x64_dbg_prefix_len(const u8* bytes, u32 len) {
     63   u32 off = 0;
     64   while (off < len && is_legacy_prefix(bytes[off])) ++off;
     65   if (off < len && bytes[off] >= 0x40u && bytes[off] <= 0x4fu) ++off;
     66   return off;
     67 }
     68 
     69 static int read_i8(const u8* bytes, u32 len, u32 off, i64* out) {
     70   if (off >= len) return 0;
     71   *out = (i64)(i8)bytes[off];
     72   return 1;
     73 }
     74 
     75 static int read_i32(const u8* bytes, u32 len, u32 off, i64* out) {
     76   if (off + 4u > len) return 0;
     77   *out = (i64)(i32)rd_u32_le(bytes + off);
     78   return 1;
     79 }
     80 
     81 static int x64_dbg_modrm_len(const u8* bytes, u32 len, u32 modrm_off,
     82                              u32* total_out, u32* rip_disp_off) {
     83   u8 mr;
     84   u32 mod;
     85   u32 rm;
     86   u32 off;
     87   if (modrm_off >= len) return 0;
     88   mr = bytes[modrm_off];
     89   mod = (mr >> 6) & 3u;
     90   rm = mr & 7u;
     91   off = modrm_off + 1u;
     92   *rip_disp_off = 0;
     93 
     94   if (mod != 3u && rm == 4u) {
     95     u8 sib;
     96     if (off >= len) return 0;
     97     sib = bytes[off++];
     98     if (mod == 0u && (sib & 7u) == 5u) {
     99       if (off + 4u > len) return 0;
    100       off += 4u;
    101     }
    102   } else if (mod == 0u && rm == 5u) {
    103     if (off + 4u > len) return 0;
    104     *rip_disp_off = off;
    105     off += 4u;
    106   } else if (mod == 1u) {
    107     if (off + 1u > len) return 0;
    108     off += 1u;
    109   } else if (mod == 2u) {
    110     if (off + 4u > len) return 0;
    111     off += 4u;
    112   }
    113 
    114   *total_out = off;
    115   return 1;
    116 }
    117 
    118 static int onebyte_has_modrm(u8 op) {
    119   switch (op) {
    120     case 0x01:
    121     case 0x09:
    122     case 0x21:
    123     case 0x29:
    124     case 0x31:
    125     case 0x39:
    126     case 0x85:
    127     case 0x87:
    128     case 0x88:
    129     case 0x89:
    130     case 0x8b:
    131     case 0x8d:
    132     case 0x63:
    133     case 0x81:
    134     case 0x83:
    135     case 0xc1:
    136     case 0xd3:
    137     case 0xf7:
    138     case 0xff:
    139     case 0x69:
    140     case 0x6b:
    141       return 1;
    142     default:
    143       return 0;
    144   }
    145 }
    146 
    147 static u32 onebyte_imm_len(u8 op, const u8* bytes, u32 len, u32 modrm_off) {
    148   (void)bytes;
    149   (void)len;
    150   (void)modrm_off;
    151   switch (op) {
    152     case 0x83:
    153     case 0xc1:
    154     case 0x6b:
    155       return 1;
    156     case 0x81:
    157     case 0x69:
    158       return 4;
    159     default:
    160       return 0;
    161   }
    162 }
    163 
    164 static int twobyte_has_modrm(u8 op) {
    165   if (op >= 0x40u && op <= 0x4fu) return 1; /* CMOVcc */
    166   if (op >= 0x90u && op <= 0x9fu) return 1; /* SETcc */
    167   switch (op) {
    168     case 0x10:
    169     case 0x11:
    170     case 0x1f:
    171     case 0x2a:
    172     case 0x2c:
    173     case 0x2e:
    174     case 0x58:
    175     case 0x59:
    176     case 0x5a:
    177     case 0x5c:
    178     case 0x5e:
    179     case 0xaf:
    180     case 0xb1:
    181     case 0xb6:
    182     case 0xb7:
    183     case 0xb8:
    184     case 0xbc:
    185     case 0xbd:
    186     case 0xbe:
    187     case 0xbf:
    188     case 0xc1:
    189       return 1;
    190     default:
    191       return 0;
    192   }
    193 }
    194 
    195 static KitStatus x64_dbg_measure(const u8* bytes, u32 len, u64 pc,
    196                                  X64DbgDecode* out) {
    197   u32 off;
    198   u8 op;
    199   if (!bytes || !out) return KIT_INVALID;
    200   memset(out, 0, sizeof(*out));
    201   off = x64_dbg_prefix_len(bytes, len);
    202   if (off >= len) return KIT_UNSUPPORTED;
    203   out->opc_off = off;
    204   op = bytes[off];
    205 
    206   if (op == X64_CALL_REL32_BYTE || op == X64_JMP_REL32_BYTE) {
    207     if (!read_i32(bytes, len, off + 1u, &out->disp)) return KIT_UNSUPPORTED;
    208     out->len = off + 5u;
    209     out->disp_off = off + 1u;
    210     out->disp_size = 4u;
    211     out->pc_rel_kind = X64_DBG_PCREL_REL32;
    212     out->is_call = (op == X64_CALL_REL32_BYTE);
    213     return KIT_OK;
    214   }
    215   if (op == 0xebu || (op >= 0x70u && op <= 0x7fu) ||
    216       (op >= 0xe0u && op <= 0xe3u)) {
    217     if (!read_i8(bytes, len, off + 1u, &out->disp)) return KIT_UNSUPPORTED;
    218     out->len = off + 2u;
    219     out->disp_off = off + 1u;
    220     out->disp_size = 1u;
    221     out->pc_rel_kind = X64_DBG_PCREL_REL8;
    222     return KIT_OK;
    223   }
    224   if (op >= 0x50u && op <= 0x5fu) {
    225     out->len = off + 1u;
    226     return KIT_OK;
    227   }
    228   if (op >= 0xb8u && op <= 0xbfu) {
    229     u32 imm = 4u;
    230     for (u32 i = 0; i < off; ++i) {
    231       if (bytes[i] >= 0x48u && bytes[i] <= 0x4fu) imm = 8u;
    232     }
    233     if (off + 1u + imm > len) return KIT_UNSUPPORTED;
    234     out->len = off + 1u + imm;
    235     return KIT_OK;
    236   }
    237   switch (op) {
    238     case 0x90:
    239     case 0xc3:
    240     case 0xc9:
    241     case 0x99:
    242       out->len = off + 1u;
    243       return KIT_OK;
    244     default:
    245       break;
    246   }
    247 
    248   if (op == 0x0fu) {
    249     u8 op2;
    250     if (off + 1u >= len) return KIT_UNSUPPORTED;
    251     op2 = bytes[off + 1u];
    252     if (op2 >= 0x80u && op2 <= 0x8fu) {
    253       if (!read_i32(bytes, len, off + 2u, &out->disp)) return KIT_UNSUPPORTED;
    254       out->len = off + 6u;
    255       out->disp_off = off + 2u;
    256       out->disp_size = 4u;
    257       out->pc_rel_kind = X64_DBG_PCREL_REL32;
    258       return KIT_OK;
    259     }
    260     if (op2 == 0x0bu || (op2 >= 0xc8u && op2 <= 0xcfu)) {
    261       out->len = off + 2u;
    262       return KIT_OK;
    263     }
    264     if (op2 == 0xaeu && off + 2u < len && bytes[off + 2u] == 0xf0u) {
    265       out->len = off + 3u;
    266       return KIT_OK;
    267     }
    268     if (twobyte_has_modrm(op2)) {
    269       u32 end = 0;
    270       u32 rip = 0;
    271       if (!x64_dbg_modrm_len(bytes, len, off + 2u, &end, &rip))
    272         return KIT_UNSUPPORTED;
    273       out->len = end;
    274       if (rip) {
    275         if (!read_i32(bytes, len, rip, &out->disp)) return KIT_UNSUPPORTED;
    276         out->disp_off = rip;
    277         out->disp_size = 4u;
    278         out->pc_rel_kind = X64_DBG_PCREL_RIP_DISP32;
    279       }
    280       return KIT_OK;
    281     }
    282     return KIT_UNSUPPORTED;
    283   }
    284 
    285   if (onebyte_has_modrm(op)) {
    286     u32 end = 0;
    287     u32 rip = 0;
    288     u32 imm;
    289     if (!x64_dbg_modrm_len(bytes, len, off + 1u, &end, &rip))
    290       return KIT_UNSUPPORTED;
    291     imm = onebyte_imm_len(op, bytes, len, off + 1u);
    292     if (end + imm > len) return KIT_UNSUPPORTED;
    293     out->len = end + imm;
    294     if (rip) {
    295       if (!read_i32(bytes, len, rip, &out->disp)) return KIT_UNSUPPORTED;
    296       out->disp_off = rip;
    297       out->disp_size = 4u;
    298       out->pc_rel_kind = X64_DBG_PCREL_RIP_DISP32;
    299     }
    300     if (op == 0xffu && off + 1u < len) {
    301       u8 sub = (bytes[off + 1u] >> 3) & 7u;
    302       out->is_call = (sub == 2u);
    303     }
    304     (void)pc;
    305     return KIT_OK;
    306   }
    307 
    308   return KIT_UNSUPPORTED;
    309 }
    310 
    311 static KitStatus x64_dbg_breakpoint_patch(u8* out, u32 cap, u32* len_out) {
    312   if (!out || !len_out) return KIT_INVALID;
    313   if (cap < 1u) return KIT_INVALID;
    314   out[0] = X64_INT3_BYTE;
    315   *len_out = 1u;
    316   return KIT_OK;
    317 }
    318 
    319 static u64 x64_dbg_breakpoint_addr_from_fault_pc(u64 fault_pc) {
    320   return fault_pc ? fault_pc - 1u : 0u;
    321 }
    322 
    323 static KitStatus x64_dbg_decode_insn(const u8* bytes, u32 len, u64 pc,
    324                                      ArchDbgInsn* out) {
    325   X64DbgDecode d;
    326   KitStatus st = x64_dbg_measure(bytes, len, pc, &d);
    327   if (st != KIT_OK) return st;
    328   if (d.len == 0 || d.len > ARCH_DBG_MAX_INSN_BYTES) return KIT_UNSUPPORTED;
    329   memset(out, 0, sizeof(*out));
    330   out->pc = pc;
    331   out->len = d.len;
    332   memcpy(out->bytes, bytes, d.len);
    333   return KIT_OK;
    334 }
    335 
    336 static KitStatus x64_dbg_build_displaced_shim(
    337     const ArchDbgInsn* insn, void* scratch_write, u64 scratch_runtime,
    338     u32 scratch_cap, u32* sentinel_off, u64* fallthrough_pc) {
    339   X64DbgDecode d;
    340   u8* w = (u8*)scratch_write;
    341   KitStatus st;
    342   u8 op;
    343   if (!insn || !scratch_write || !sentinel_off || !fallthrough_pc)
    344     return KIT_INVALID;
    345   st = x64_dbg_measure(insn->bytes, insn->len, insn->pc, &d);
    346   if (st != KIT_OK) return st;
    347   if (d.len != insn->len) return KIT_UNSUPPORTED;
    348   if (insn->len + 1u > scratch_cap) return KIT_UNSUPPORTED;
    349 
    350   op = insn->bytes[d.opc_off];
    351   if ((op == 0xe9u || op == 0xebu) && (d.pc_rel_kind == X64_DBG_PCREL_REL32 ||
    352                                        d.pc_rel_kind == X64_DBG_PCREL_REL8)) {
    353     *fallthrough_pc = (u64)((i64)(insn->pc + insn->len) + d.disp);
    354     w[0] = X64_INT3_BYTE;
    355     *sentinel_off = 0;
    356     return KIT_OK;
    357   }
    358 
    359   memcpy(w, insn->bytes, insn->len);
    360   *fallthrough_pc = insn->pc + insn->len;
    361 
    362   if (d.pc_rel_kind == X64_DBG_PCREL_RIP_DISP32) {
    363     i64 target = (i64)(insn->pc + insn->len) + d.disp;
    364     i64 nd = target - (i64)(scratch_runtime + insn->len);
    365     if (!fits_i32(nd)) return KIT_UNSUPPORTED;
    366     wr_u32_le(w + d.disp_off, (u32)(i32)nd);
    367   } else if (d.pc_rel_kind == X64_DBG_PCREL_REL32) {
    368     i64 target = (i64)(insn->pc + insn->len) + d.disp;
    369     i64 nd = target - (i64)(scratch_runtime + insn->len);
    370     if (!fits_i32(nd)) return KIT_UNSUPPORTED;
    371     wr_u32_le(w + d.disp_off, (u32)(i32)nd);
    372   } else if (d.pc_rel_kind == X64_DBG_PCREL_REL8) {
    373     i64 target = (i64)(insn->pc + insn->len) + d.disp;
    374     i64 nd = target - (i64)(scratch_runtime + insn->len);
    375     if (!fits_i8(nd) && op >= 0x70u && op <= 0x7fu) {
    376       if (scratch_cap < 7u) return KIT_UNSUPPORTED;
    377       nd = target - (i64)(scratch_runtime + 6u);
    378       if (!fits_i32(nd)) return KIT_UNSUPPORTED;
    379       w[0] = 0x0fu;
    380       w[1] = (u8)(0x80u | (op & 0x0fu));
    381       wr_u32_le(w + 2u, (u32)(i32)nd);
    382       w[6] = X64_INT3_BYTE;
    383       *sentinel_off = 6u;
    384       return KIT_OK;
    385     }
    386     if (!fits_i8(nd)) return KIT_UNSUPPORTED;
    387     w[d.disp_off] = (u8)(i8)nd;
    388   }
    389 
    390   w[insn->len] = X64_INT3_BYTE;
    391   *sentinel_off = insn->len;
    392   return KIT_OK;
    393 }
    394 
    395 static int x64_dbg_is_call(const ArchDbgInsn* insn) {
    396   X64DbgDecode d;
    397   if (!insn) return 0;
    398   if (x64_dbg_measure(insn->bytes, insn->len, insn->pc, &d) != KIT_OK) return 0;
    399   return d.is_call != 0;
    400 }
    401 
    402 static KitStatus x64_dbg_direct_call_target(const ArchDbgInsn* insn,
    403                                             u64* target_out) {
    404   X64DbgDecode d;
    405   if (!insn || !target_out) return KIT_INVALID;
    406   if (x64_dbg_measure(insn->bytes, insn->len, insn->pc, &d) != KIT_OK)
    407     return KIT_UNSUPPORTED;
    408   if (!d.is_call || d.pc_rel_kind != X64_DBG_PCREL_REL32) return KIT_NOT_FOUND;
    409   *target_out = (u64)((i64)(insn->pc + insn->len) + d.disp);
    410   return KIT_OK;
    411 }
    412 
    413 static KitStatus x64_dbg_direct_jump_target(const ArchDbgInsn* insn,
    414                                             u64* target_out) {
    415   X64DbgDecode d;
    416   if (!insn || !target_out) return KIT_INVALID;
    417   if (x64_dbg_measure(insn->bytes, insn->len, insn->pc, &d) != KIT_OK)
    418     return KIT_UNSUPPORTED;
    419   if (d.is_call || (d.pc_rel_kind != X64_DBG_PCREL_REL32 &&
    420                     d.pc_rel_kind != X64_DBG_PCREL_REL8))
    421     return KIT_NOT_FOUND;
    422   if (insn->bytes[d.opc_off] != X64_JMP_REL32_BYTE &&
    423       insn->bytes[d.opc_off] != 0xebu)
    424     return KIT_NOT_FOUND;
    425   *target_out = (u64)((i64)(insn->pc + insn->len) + d.disp);
    426   return KIT_OK;
    427 }
    428 
    429 const ArchDbgOps x64_dbg_ops = {
    430     .min_insn_len = 1u,
    431     .max_insn_len = ARCH_DBG_MAX_INSN_BYTES,
    432     .breakpoint_patch = x64_dbg_breakpoint_patch,
    433     .breakpoint_addr_from_fault_pc = x64_dbg_breakpoint_addr_from_fault_pc,
    434     .decode_insn = x64_dbg_decode_insn,
    435     .build_displaced_shim = x64_dbg_build_displaced_shim,
    436     .is_call = x64_dbg_is_call,
    437     .direct_call_target = x64_dbg_direct_call_target,
    438     .direct_jump_target = x64_dbg_direct_jump_target,
    439 };