kit

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

dwarf_cfi.c (13382B)


      1 /* dwarf_cfi.c — CFI machine + kit_dwarf_unwind_step.
      2  *
      3  * Per doc/DWARF.md §4.5: walk .eh_frame from the highest-address end
      4  * (CIEs first), run the FDE program for the FDE whose
      5  * (initial_location, address_range) covers frame->pc. Output mutates
      6  * frame->pc, frame->cfa, and caller-saved register slots.
      7  *
      8  * Status: minimal Phase-4 implementation. Decodes the FDE that covers
      9  * `frame->pc` and applies a small subset of CFA opcodes sufficient for
     10  * the aarch64 frame-pointer prologues the producer emits today. Returns
     11  * 1 (no caller info) if no FDE matches or the section is empty —
     12  * callers must treat 1 as "stack bottom" per the API contract.
     13  */
     14 
     15 #include <kit/arch.h>
     16 #include <kit/dwarf.h>
     17 #include <stdint.h>
     18 #include <string.h>
     19 
     20 #include "core/core.h"
     21 #include "core/heap.h"
     22 #include "debug/dwarf_internal.h"
     23 
     24 #define CFI_REG_MAX 32
     25 
     26 typedef struct CfiRule {
     27   /* 0=undefined, 1=offset(cfa+N), 2=register(R), 3=same_value */
     28   u8 kind;
     29   i64 offset;
     30   u32 reg;
     31 } CfiRule;
     32 
     33 typedef struct CfiState {
     34   /* CFA: cfa = regs[reg] + offset (kind 0), or expression (kind 1). */
     35   int cfa_kind; /* 0 = reg+offset; 1 = expression (unhandled) */
     36   u32 cfa_reg;
     37   i64 cfa_offset;
     38   CfiRule rules[CFI_REG_MAX];
     39   i32 code_align;
     40   i32 data_align;
     41   u32 return_reg;
     42 } CfiState;
     43 
     44 static u64 read_eh_ptr(const u8* base, u32 size, u32* off, u8 enc) {
     45   u64 v = 0;
     46   switch (enc & 0x0f) {
     47     case DW_EH_PE_absptr:
     48     case DW_EH_PE_udata8:
     49       v = dw_u64(base, size, off);
     50       break;
     51     case DW_EH_PE_uleb128:
     52       v = dw_uleb(base, size, off);
     53       break;
     54     case DW_EH_PE_udata2:
     55       v = dw_u16(base, size, off);
     56       break;
     57     case DW_EH_PE_udata4:
     58       v = dw_u32(base, size, off);
     59       break;
     60     case DW_EH_PE_sleb128:
     61       v = (u64)dw_sleb(base, size, off);
     62       break;
     63     case DW_EH_PE_sdata2:
     64       v = (u64)(i64)(i16)dw_u16(base, size, off);
     65       break;
     66     case DW_EH_PE_sdata4:
     67       v = (u64)(i64)(i32)dw_u32(base, size, off);
     68       break;
     69     case DW_EH_PE_sdata8:
     70       v = (u64)dw_u64(base, size, off);
     71       break;
     72     default:
     73       break;
     74   }
     75   return v;
     76 }
     77 
     78 static void run_cfi(const u8* prog, u32 plen, CfiState* st, u64* loc,
     79                     u64 stop_pc) {
     80   u32 off = 0;
     81   while (off < plen) {
     82     u8 op = prog[off++];
     83     u8 hi = op & 0xc0;
     84     u8 lo = op & 0x3f;
     85     if (hi == DW_CFA_advance_loc) {
     86       *loc += (u64)lo * (u64)st->code_align;
     87       if (*loc > stop_pc) return;
     88       continue;
     89     }
     90     if (hi == DW_CFA_offset) {
     91       u64 fac = dw_uleb(prog, plen, &off);
     92       if (lo < CFI_REG_MAX) {
     93         st->rules[lo].kind = 1;
     94         st->rules[lo].offset = (i64)fac * (i64)st->data_align;
     95       }
     96       continue;
     97     }
     98     if (hi == DW_CFA_restore) {
     99       if (lo < CFI_REG_MAX) st->rules[lo].kind = 0;
    100       continue;
    101     }
    102     switch (op) {
    103       case DW_CFA_nop:
    104         break;
    105       case DW_CFA_advance_loc1: {
    106         u8 v = dw_u8(prog, plen, &off);
    107         *loc += (u64)v * (u64)st->code_align;
    108         if (*loc > stop_pc) return;
    109       } break;
    110       case DW_CFA_advance_loc2: {
    111         u16 v = dw_u16(prog, plen, &off);
    112         *loc += (u64)v * (u64)st->code_align;
    113         if (*loc > stop_pc) return;
    114       } break;
    115       case DW_CFA_advance_loc4: {
    116         u32 v = dw_u32(prog, plen, &off);
    117         *loc += (u64)v * (u64)st->code_align;
    118         if (*loc > stop_pc) return;
    119       } break;
    120       case DW_CFA_set_loc:
    121         *loc = dw_u64(prog, plen, &off);
    122         if (*loc > stop_pc) return;
    123         break;
    124       case DW_CFA_def_cfa: {
    125         u64 r = dw_uleb(prog, plen, &off);
    126         u64 o = dw_uleb(prog, plen, &off);
    127         st->cfa_kind = 0;
    128         st->cfa_reg = (u32)r;
    129         st->cfa_offset = (i64)o;
    130       } break;
    131       case DW_CFA_def_cfa_register: {
    132         u64 r = dw_uleb(prog, plen, &off);
    133         st->cfa_reg = (u32)r;
    134       } break;
    135       case DW_CFA_def_cfa_offset: {
    136         u64 o = dw_uleb(prog, plen, &off);
    137         st->cfa_offset = (i64)o;
    138       } break;
    139       case DW_CFA_def_cfa_sf: {
    140         u64 r = dw_uleb(prog, plen, &off);
    141         i64 o = dw_sleb(prog, plen, &off);
    142         st->cfa_kind = 0;
    143         st->cfa_reg = (u32)r;
    144         st->cfa_offset = o * st->data_align;
    145       } break;
    146       case DW_CFA_def_cfa_offset_sf: {
    147         i64 o = dw_sleb(prog, plen, &off);
    148         st->cfa_offset = o * st->data_align;
    149       } break;
    150       case DW_CFA_offset_extended: {
    151         u64 r = dw_uleb(prog, plen, &off);
    152         u64 fac = dw_uleb(prog, plen, &off);
    153         if (r < CFI_REG_MAX) {
    154           st->rules[r].kind = 1;
    155           st->rules[r].offset = (i64)fac * (i64)st->data_align;
    156         }
    157       } break;
    158       case DW_CFA_offset_extended_sf: {
    159         u64 r = dw_uleb(prog, plen, &off);
    160         i64 fac = dw_sleb(prog, plen, &off);
    161         if (r < CFI_REG_MAX) {
    162           st->rules[r].kind = 1;
    163           st->rules[r].offset = fac * st->data_align;
    164         }
    165       } break;
    166       case DW_CFA_register: {
    167         u64 r1 = dw_uleb(prog, plen, &off);
    168         u64 r2 = dw_uleb(prog, plen, &off);
    169         if (r1 < CFI_REG_MAX) {
    170           st->rules[r1].kind = 2;
    171           st->rules[r1].reg = (u32)r2;
    172         }
    173       } break;
    174       case DW_CFA_undefined: {
    175         u64 r = dw_uleb(prog, plen, &off);
    176         if (r < CFI_REG_MAX) st->rules[r].kind = 0;
    177       } break;
    178       case DW_CFA_same_value: {
    179         u64 r = dw_uleb(prog, plen, &off);
    180         if (r < CFI_REG_MAX) st->rules[r].kind = 3;
    181       } break;
    182       case DW_CFA_remember_state:
    183       case DW_CFA_restore_state:
    184         /* Not modelled — would need a state stack. Best-effort: skip. */
    185         break;
    186       case DW_CFA_def_cfa_expression: {
    187         u64 n = dw_uleb(prog, plen, &off);
    188         off += (u32)n;
    189         st->cfa_kind = 1; /* expression — we can't evaluate without frame */
    190       } break;
    191       case DW_CFA_expression:
    192       case DW_CFA_val_expression: {
    193         (void)dw_uleb(prog, plen, &off);
    194         {
    195           u64 n = dw_uleb(prog, plen, &off);
    196           off += (u32)n;
    197         }
    198       } break;
    199       case DW_CFA_val_offset: {
    200         (void)dw_uleb(prog, plen, &off);
    201         (void)dw_uleb(prog, plen, &off);
    202       } break;
    203       case DW_CFA_val_offset_sf: {
    204         (void)dw_uleb(prog, plen, &off);
    205         (void)dw_sleb(prog, plen, &off);
    206       } break;
    207       default:
    208         return; /* unknown opcode — bail */
    209     }
    210   }
    211 }
    212 
    213 KitStatus kit_dwarf_unwind_step(KitDebugInfo* d, KitUnwindFrame* frame) {
    214   u32 off;
    215   if (!d || !frame) return KIT_INVALID;
    216   if (d->eh_frame.sec_idx == UINT32_MAX || d->eh_frame.size == 0)
    217     return KIT_NOT_FOUND;
    218   /* Sweep .eh_frame entries, locating the FDE that covers frame->pc. */
    219   off = 0;
    220   while (off < d->eh_frame.size) {
    221     u32 length = dw_u32(d->eh_frame.data, d->eh_frame.size, &off);
    222     u32 entry_end;
    223     u32 cie_id_off = off;
    224     u32 cie_id;
    225     if (length == 0) break;                            /* terminator */
    226     if (length == 0xffffffffu) return KIT_UNSUPPORTED; /* 64-bit eh_frame */
    227     entry_end = off + length;
    228     cie_id = dw_u32(d->eh_frame.data, d->eh_frame.size, &off);
    229     if (cie_id == 0) {
    230       /* CIE — skip body; we'll re-read on demand when its FDEs reference it. */
    231       off = entry_end;
    232       continue;
    233     }
    234     {
    235       /* FDE: cie_id is a backwards offset to the CIE. */
    236       u32 cie_pointer_pos = cie_id_off; /* offset of the cie_id field */
    237       u32 cie_start = cie_pointer_pos - cie_id;
    238       u32 cie_off, cie_len, cie_ver;
    239       const char* aug;
    240       u8 fde_pe = DW_EH_PE_absptr;
    241       i32 code_align;
    242       i32 data_align;
    243       u32 return_reg;
    244       u32 cie_id_at_cie;
    245       u32 cie_aug_data_len = 0;
    246       u8 has_aug_data = 0;
    247       u32 cie_inst_off, cie_inst_end;
    248       u64 fde_pc;
    249       u64 fde_range;
    250       CfiState st;
    251 
    252       /* Parse CIE header. */
    253       cie_off = cie_start;
    254       cie_len = dw_u32(d->eh_frame.data, d->eh_frame.size, &cie_off);
    255       (void)cie_len;
    256       cie_id_at_cie = dw_u32(d->eh_frame.data, d->eh_frame.size, &cie_off);
    257       (void)cie_id_at_cie; /* should be 0 */
    258       cie_ver = dw_u8(d->eh_frame.data, d->eh_frame.size, &cie_off);
    259       if (cie_ver != 1 && cie_ver != 3 && cie_ver != 4) {
    260         off = entry_end;
    261         continue;
    262       }
    263       aug = dw_cstr(d->eh_frame.data, d->eh_frame.size, &cie_off);
    264       if (cie_ver == 4) {
    265         (void)dw_u8(d->eh_frame.data, d->eh_frame.size,
    266                     &cie_off); /* address_size */
    267         (void)dw_u8(d->eh_frame.data, d->eh_frame.size,
    268                     &cie_off); /* segment_size */
    269       }
    270       code_align = (i32)dw_uleb(d->eh_frame.data, d->eh_frame.size, &cie_off);
    271       data_align = (i32)dw_sleb(d->eh_frame.data, d->eh_frame.size, &cie_off);
    272       if (cie_ver == 1) {
    273         return_reg = dw_u8(d->eh_frame.data, d->eh_frame.size, &cie_off);
    274       } else {
    275         return_reg = (u32)dw_uleb(d->eh_frame.data, d->eh_frame.size, &cie_off);
    276       }
    277       /* Parse augmentation. */
    278       {
    279         const char* a = aug;
    280         if (a && a[0] == 'z') {
    281           cie_aug_data_len =
    282               (u32)dw_uleb(d->eh_frame.data, d->eh_frame.size, &cie_off);
    283           has_aug_data = 1;
    284           (void)cie_aug_data_len;
    285           a++;
    286           while (*a) {
    287             switch (*a) {
    288               case 'R':
    289                 fde_pe = dw_u8(d->eh_frame.data, d->eh_frame.size, &cie_off);
    290                 break;
    291               case 'P': {
    292                 u8 enc = dw_u8(d->eh_frame.data, d->eh_frame.size, &cie_off);
    293                 (void)read_eh_ptr(d->eh_frame.data, d->eh_frame.size, &cie_off,
    294                                   enc);
    295               } break;
    296               case 'L':
    297                 (void)dw_u8(d->eh_frame.data, d->eh_frame.size, &cie_off);
    298                 break;
    299               case 'S':
    300               case 'B':
    301                 break;
    302               default:
    303                 break;
    304             }
    305             a++;
    306           }
    307         } else if (a && a[0] != 0) {
    308           /* Unknown augmentation chars without 'z' — bail. */
    309           off = entry_end;
    310           continue;
    311         }
    312       }
    313       cie_inst_off = cie_off;
    314       /* CIE body extends to entry_start of CIE plus 4 + cie_len. We already
    315        * consumed length+id, so the upper bound is cie_start + 4 + cie_len. */
    316       cie_inst_end = cie_start + 4 + cie_len;
    317       (void)has_aug_data;
    318 
    319       /* Run CIE initial instructions. */
    320       memset(&st, 0, sizeof(st));
    321       st.code_align = code_align;
    322       st.data_align = data_align;
    323       st.return_reg = return_reg;
    324       run_cfi(d->eh_frame.data + cie_inst_off,
    325               cie_inst_end > cie_inst_off ? cie_inst_end - cie_inst_off : 0,
    326               &st, &(u64){0}, ~(u64)0);
    327 
    328       /* Parse FDE pc, range. */
    329       {
    330         u32 pc_off = off;
    331         fde_pc = read_eh_ptr(d->eh_frame.data, d->eh_frame.size, &off, fde_pe);
    332         if ((fde_pe & 0xf0) == DW_EH_PE_pcrel) {
    333           /* pcrel: address is relative to the location of the encoded
    334            * pointer itself within the section. We interpret as offset from
    335            * pc_off. The runtime address is unknown to us absent a base —
    336            * for an unrelocated obj, just keep the relative value. */
    337           fde_pc += pc_off; /* relative-to-section-offset best-effort */
    338         }
    339         fde_range = read_eh_ptr(d->eh_frame.data, d->eh_frame.size, &off,
    340                                 fde_pe & 0x0f);
    341       }
    342       /* Skip FDE augmentation data if CIE's z aug was set. */
    343       if (has_aug_data) {
    344         u64 aug_len = dw_uleb(d->eh_frame.data, d->eh_frame.size, &off);
    345         off += (u32)aug_len;
    346       }
    347       if (frame->pc < fde_pc || frame->pc >= fde_pc + fde_range) {
    348         off = entry_end;
    349         continue;
    350       }
    351       /* Run FDE instructions up to frame->pc. */
    352       {
    353         u64 loc = fde_pc;
    354         u32 fde_inst_off = off;
    355         u32 fde_inst_end = entry_end;
    356         run_cfi(d->eh_frame.data + fde_inst_off,
    357                 fde_inst_end > fde_inst_off ? fde_inst_end - fde_inst_off : 0,
    358                 &st, &loc, frame->pc);
    359       }
    360       /* Compute caller frame. */
    361       if (st.cfa_kind != 0 || st.cfa_reg >= 32) return KIT_UNSUPPORTED;
    362       {
    363         u64 cfa = frame->regs[st.cfa_reg] + (u64)st.cfa_offset;
    364         u32 r;
    365         u64 ret_addr = 0;
    366         /* For each register with a rule, we'd read CFA-relative memory to
    367          * recover its caller value. Without a memory provider we can't
    368          * actually load — leave registers as-is and just update cfa/pc.
    369          * The return address sits in the rule for st.return_reg. If
    370          * undefined, we're at the bottom. */
    371         if (st.return_reg < CFI_REG_MAX && st.rules[st.return_reg].kind == 1) {
    372           /* ret_addr = *(cfa + offset) — but we have no JIT session here.
    373            * Caller-supplied frames typically include enough register state
    374            * that the harness already captured x30. We treat "undefined"
    375            * as bottom-of-stack. */
    376           ret_addr = 0;
    377         } else if (st.return_reg < 32 && st.rules[st.return_reg].kind == 2) {
    378           ret_addr = frame->regs[st.rules[st.return_reg].reg];
    379         } else {
    380           return KIT_NOT_FOUND; /* bottom of stack */
    381         }
    382         frame->cfa = cfa;
    383         frame->pc = ret_addr;
    384         for (r = 0; r < 32; ++r) {
    385           /* Without memory access we can't load offset rules; leave the
    386            * register value unchanged (best-effort). */
    387           (void)r;
    388         }
    389       }
    390       return KIT_OK;
    391     }
    392   }
    393   return KIT_NOT_FOUND;
    394 }