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 }