kit

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

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 }