cfi_unit.c (11353B)
1 /* test/debug/cfi_unit.c — exercise MCEmitter CFI buffering + the 2 * mc_emit_eh_frame producer, then spot-check the resulting .eh_frame 3 * section bytes. 4 * 5 * Covers aa64, rv64, and x64; the rv64 case validates the locked psABI 6 * defaults (CFA=sp, RA=ra (DWARF 1), saved s0/ra, callee-saved s2..s11 7 * + fs2..fs11) end-to-end, and the x64 case pins the SysV x86-64 DWARF 8 * register numbering (which diverges from the hardware encoding). The 9 * producer is driven directly via MCEmitter and arch_for_compiler so the 10 * test stays independent of the backend lowering pipeline. */ 11 12 #include <kit/arch.h> 13 #include <kit/core.h> 14 #include <stdarg.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include "arch/arch.h" 20 #include "core/core.h" 21 #include "core/pool.h" 22 #include "debug/dwarf_defs.h" 23 #include "lib/kit_unit.h" 24 #include "obj/obj.h" 25 26 /* One shared test context replaces the per-file heap/diag/counter globals. 27 * EXPECT is aliased to CU_EXPECT so the call sites below are unchanged. The 28 * original ctx used now = -1, replicated in main() after kit_unit_init. */ 29 static KitUnit g_u; 30 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) 31 32 static const Section* sec_by_name(const ObjBuilder* ob, Pool* pool, 33 const char* name) { 34 u32 i, n = obj_section_count(ob); 35 for (i = 1; i < n; ++i) { 36 const Section* s = obj_section_get(ob, i); 37 Slice sn = pool_slice(pool, s->name); 38 if (sn.s && strlen(name) == sn.len && memcmp(sn.s, name, sn.len) == 0) 39 return s; 40 } 41 return NULL; 42 } 43 44 static u32 read_u32le(const u8* p) { 45 return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); 46 } 47 48 /* Decode an unsigned LEB128 from buf starting at *off; advance *off. */ 49 static u64 dec_uleb(const u8* buf, u32 size, u32* off) { 50 u64 v = 0; 51 u32 shift = 0; 52 while (*off < size) { 53 u8 byte = buf[(*off)++]; 54 v |= (u64)(byte & 0x7fu) << shift; 55 if ((byte & 0x80u) == 0) break; 56 shift += 7; 57 } 58 return v; 59 } 60 61 static i64 dec_sleb(const u8* buf, u32 size, u32* off) { 62 i64 v = 0; 63 u32 shift = 0; 64 u8 byte = 0; 65 while (*off < size) { 66 byte = buf[(*off)++]; 67 v |= (i64)(byte & 0x7fu) << shift; 68 shift += 7; 69 if ((byte & 0x80u) == 0) break; 70 } 71 if (shift < 64 && (byte & 0x40u)) v |= -((i64)1 << shift); 72 return v; 73 } 74 75 /* ---- driver ---- */ 76 77 typedef struct CfiExpect { 78 KitArchKind arch; 79 const char* tag; 80 /* CIE expectations */ 81 u32 expected_return_reg; 82 i32 expected_code_align; 83 i32 expected_data_align; 84 u32 expected_cfa_init_reg; 85 i32 expected_cfa_init_offset; 86 /* FDE expectations: registers we emit cfi_offset for */ 87 u32 cfa_reg_after_setup; 88 i32 cfa_off_after_setup; 89 } CfiExpect; 90 91 static void check_arch(const CfiExpect* ex) { 92 KitTargetSpec t; 93 Compiler* c; 94 ObjBuilder* ob; 95 ObjSecId text_sec; 96 ObjSymId fsym; 97 Pool* pool; 98 MCEmitter* mc; 99 const Section* eh; 100 const u8* bytes; 101 u8* flat; 102 u32 size; 103 u32 off; 104 105 t = kit_unit_target(ex->arch, KIT_OS_LINUX, KIT_OBJ_ELF); 106 107 if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) { 108 fprintf(stderr, "[%s] compiler_new failed\n", ex->tag); 109 g_u.fails++; 110 return; 111 } 112 ob = obj_new(c); 113 pool = c->global; 114 115 text_sec = obj_section(ob, pool_intern_slice(pool, SLICE_LIT(".text")), 116 SEC_TEXT, SF_EXEC | SF_ALLOC, 4); 117 fsym = obj_symbol(ob, pool_intern_slice(pool, SLICE_LIT("f")), SB_GLOBAL, 118 SK_FUNC, text_sec, 0, 16); 119 120 mc = mc_new(c, ob); 121 EXPECT(mc != NULL, "[%s] mc_new failed", ex->tag); 122 if (!mc) { 123 kit_compiler_free(c); 124 return; 125 } 126 mc->set_section(mc, text_sec); 127 mc_begin_function(mc, fsym, text_sec, 0); 128 mc->cfi_startproc(mc); 129 /* Write the (placeholder) function body bytes AFTER cfi_startproc so 130 * the FDE range captured by cfi_endproc reflects the body size. */ 131 { 132 u8 zeros[16] = {0}; 133 obj_write(ob, text_sec, zeros, sizeof zeros); 134 } 135 /* Anchor the directives at pc_offset=0 so the test can predict offsets 136 * deterministically (we wrote the bytes before opening the FDE, so 137 * cur_pos > func_start). */ 138 mc->cfi_set_next_pc_offset(mc, 0); 139 mc->cfi_def_cfa(mc, ex->cfa_reg_after_setup, ex->cfa_off_after_setup); 140 /* Save the return-address register at CFA-8. */ 141 mc->cfi_set_next_pc_offset(mc, 0); 142 mc->cfi_offset(mc, ex->expected_return_reg, -8); 143 mc->cfi_endproc(mc); 144 mc_end_function(mc); 145 146 mc_emit_eh_frame(mc); 147 148 eh = sec_by_name(ob, pool, ".eh_frame"); 149 EXPECT(eh != NULL, "[%s] .eh_frame missing", ex->tag); 150 if (!eh) goto cleanup; 151 size = buf_pos(&eh->bytes); 152 EXPECT(size >= 24, "[%s] .eh_frame too small (%u)", ex->tag, size); 153 flat = (u8*)malloc(size); 154 buf_flatten(&eh->bytes, flat); 155 bytes = flat; 156 off = 0; 157 158 /* ---- CIE ---- */ 159 { 160 u32 cie_len = read_u32le(bytes + off); 161 u32 cie_id; 162 u8 ver; 163 EXPECT(cie_len + 4 <= size, "[%s] CIE length out of bounds", ex->tag); 164 off += 4; 165 cie_id = read_u32le(bytes + off); 166 off += 4; 167 EXPECT(cie_id == 0, "[%s] CIE id != 0 (got %u)", ex->tag, cie_id); 168 ver = bytes[off++]; 169 EXPECT(ver == 1, "[%s] CIE version != 1 (got %u)", ex->tag, ver); 170 /* augmentation string "zR" */ 171 EXPECT(bytes[off] == 'z' && bytes[off + 1] == 'R' && bytes[off + 2] == 0, 172 "[%s] augmentation != 'zR'", ex->tag); 173 off += 3; 174 { 175 u64 caf = dec_uleb(bytes, size, &off); 176 i64 daf = dec_sleb(bytes, size, &off); 177 u64 rar = dec_uleb(bytes, size, &off); 178 EXPECT((u32)caf == (u32)ex->expected_code_align, 179 "[%s] code_align_factor got %u expected %d", ex->tag, (u32)caf, 180 ex->expected_code_align); 181 EXPECT((i32)daf == ex->expected_data_align, 182 "[%s] data_align_factor got %d expected %d", ex->tag, (i32)daf, 183 ex->expected_data_align); 184 EXPECT((u32)rar == ex->expected_return_reg, 185 "[%s] return_addr_reg got %u expected %u", ex->tag, (u32)rar, 186 ex->expected_return_reg); 187 } 188 { 189 u64 aug_len = dec_uleb(bytes, size, &off); 190 EXPECT(aug_len == 1, "[%s] CIE aug_data_len != 1", ex->tag); 191 EXPECT(bytes[off] == (DW_EH_PE_pcrel | DW_EH_PE_sdata4), 192 "[%s] CIE fde_pe != pcrel|sdata4 (got 0x%x)", ex->tag, bytes[off]); 193 off += 1; 194 } 195 /* Initial instructions: DW_CFA_def_cfa init_reg, init_offset */ 196 EXPECT(bytes[off] == DW_CFA_def_cfa, 197 "[%s] CIE initial op != DW_CFA_def_cfa (got 0x%x)", ex->tag, 198 bytes[off]); 199 off += 1; 200 { 201 u64 r = dec_uleb(bytes, size, &off); 202 u64 o = dec_uleb(bytes, size, &off); 203 EXPECT((u32)r == ex->expected_cfa_init_reg, 204 "[%s] CIE init CFA reg got %u expected %u", ex->tag, (u32)r, 205 ex->expected_cfa_init_reg); 206 EXPECT((i32)o == ex->expected_cfa_init_offset, 207 "[%s] CIE init CFA off got %d expected %d", ex->tag, (i32)o, 208 ex->expected_cfa_init_offset); 209 } 210 /* Skip any DW_CFA_nop padding to the CIE entry boundary. */ 211 off = 4 + cie_len; 212 } 213 214 /* ---- FDE ---- */ 215 { 216 u32 fde_len = read_u32le(bytes + off); 217 u32 cie_ptr; 218 u32 fde_end; 219 EXPECT(fde_len > 0, "[%s] FDE length zero or terminator", ex->tag); 220 off += 4; 221 fde_end = off + fde_len; 222 cie_ptr = read_u32le(bytes + off); 223 off += 4; 224 EXPECT(cie_ptr != 0, "[%s] FDE CIE_pointer = 0 — would mark this as a CIE", 225 ex->tag); 226 /* initial_location (4 bytes — patched by reloc, here zero) */ 227 off += 4; 228 { 229 u32 range = read_u32le(bytes + off); 230 EXPECT(range == 16, "[%s] FDE range got %u expected 16", ex->tag, range); 231 off += 4; 232 } 233 { 234 u64 aug_len = dec_uleb(bytes, size, &off); 235 EXPECT(aug_len == 0, "[%s] FDE aug_data_len != 0", ex->tag); 236 } 237 /* Now decode the FDE program. Our directives were emitted at 238 * pc_offset=0 with the override, so the first byte should be a 239 * DW_CFA_def_cfa (no advance_loc), then DW_CFA_offset of return reg. */ 240 { 241 u8 op = bytes[off++]; 242 EXPECT(op == DW_CFA_def_cfa, 243 "[%s] FDE first op got 0x%x expected def_cfa", ex->tag, op); 244 { 245 u64 r = dec_uleb(bytes, size, &off); 246 u64 o = dec_uleb(bytes, size, &off); 247 EXPECT((u32)r == ex->cfa_reg_after_setup, 248 "[%s] FDE def_cfa reg got %u expected %u", ex->tag, (u32)r, 249 ex->cfa_reg_after_setup); 250 EXPECT((i32)o == ex->cfa_off_after_setup, 251 "[%s] FDE def_cfa off got %d expected %d", ex->tag, (i32)o, 252 ex->cfa_off_after_setup); 253 } 254 } 255 { 256 /* DW_CFA_offset (0x80 | reg) when reg < 0x40 and factor >= 0. */ 257 u8 op = bytes[off++]; 258 u32 reg = op & 0x3fu; 259 EXPECT((op & 0xc0u) == DW_CFA_offset, 260 "[%s] FDE second op high bits != DW_CFA_offset (got 0x%x)", 261 ex->tag, op); 262 EXPECT(reg == ex->expected_return_reg, 263 "[%s] FDE offset reg got %u expected %u", ex->tag, reg, 264 ex->expected_return_reg); 265 { 266 u64 fac = dec_uleb(bytes, size, &off); 267 /* We passed -8 as the imm and the data align factor is -8, so 268 * factored offset should be 1. */ 269 EXPECT(fac == 1u, "[%s] FDE offset factor got %u expected 1", ex->tag, 270 (u32)fac); 271 } 272 } 273 /* Any trailing DW_CFA_nop padding is fine. */ 274 (void)fde_end; 275 } 276 277 free(flat); 278 279 cleanup: 280 /* mc_free is invoked transitively via compiler cleanup. */ 281 obj_free(ob); 282 kit_compiler_free(c); 283 } 284 285 int main(void) { 286 kit_unit_init(&g_u); 287 g_u.ctx.now = -1; /* preserve the original ctx.now */ 288 289 /* aa64: RA=x30 (DWARF 30), code_align=4, data_align=-8, CFA init = sp. */ 290 { 291 CfiExpect ex = { 292 .arch = KIT_ARCH_ARM_64, 293 .tag = "aa64", 294 .expected_return_reg = 30, 295 .expected_code_align = 4, 296 .expected_data_align = -8, 297 .expected_cfa_init_reg = 31, 298 .expected_cfa_init_offset = 0, 299 /* Pretend we set CFA = x29 + 16 after frame setup. */ 300 .cfa_reg_after_setup = 29, 301 .cfa_off_after_setup = 16, 302 }; 303 check_arch(&ex); 304 } 305 /* rv64: RA=x1=ra (DWARF 1), code_align=2 (covers C-ext), data_align=-8, 306 * CFA init = sp (x2). After setup, CFA = s0 (x8) + 16 (typical fp frame). */ 307 { 308 CfiExpect ex = { 309 .arch = KIT_ARCH_RV64, 310 .tag = "rv64", 311 .expected_return_reg = 1, 312 .expected_code_align = 2, 313 .expected_data_align = -8, 314 .expected_cfa_init_reg = 2, 315 .expected_cfa_init_offset = 0, 316 .cfa_reg_after_setup = 8, 317 .cfa_off_after_setup = 16, 318 }; 319 check_arch(&ex); 320 } 321 /* x64: RA=rip (DWARF 16), code_align=1, data_align=-8, CFA init = rsp 322 * (DWARF 7). After setup, CFA = rbp (DWARF 6) + 16. x64 is the only arch 323 * whose DWARF register numbers diverge from the hardware encoding (rbp is 324 * HW 5 = DWARF RDI), so this case pins the SysV x86-64 DWARF numbering. */ 325 { 326 CfiExpect ex = { 327 .arch = KIT_ARCH_X86_64, 328 .tag = "x64", 329 .expected_return_reg = 16, 330 .expected_code_align = 1, 331 .expected_data_align = -8, 332 .expected_cfa_init_reg = 7, 333 .expected_cfa_init_offset = 8, 334 .cfa_reg_after_setup = 6, 335 .cfa_off_after_setup = 16, 336 }; 337 check_arch(&ex); 338 } 339 340 if (g_u.fails) { 341 fprintf(stderr, "%d FAILED\n", g_u.fails); 342 return 1; 343 } 344 printf("debug cfi_unit: OK\n"); 345 return 0; 346 }