kit

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

roundtrip_unit.c (13570B)


      1 /* test/debug/roundtrip_unit.c — drive the Debug producer directly and
      2  * assert the resulting section bytes match a known-good encoding for one
      3  * tiny case.
      4  *
      5  * The primary case: one CU with one subprogram named "f" at .text+0, size 4
      6  * (one aarch64 instruction), one line row mapping (.text+0, line 10).
      7  * A second x64 case checks that .debug_line uses byte-granular PC advances
      8  * instead of the aarch64 minimum instruction length.
      9  *
     10  * This is a producer-side encoder check: we deliberately don't go through
     11  * kit_dwarf_open so an encoding bug doesn't get masked by a matching
     12  * decoder bug on the other side. Instead we hexdump the produced
     13  * .debug_line and .debug_info and spot-check structural invariants
     14  * (DWARF 5, address-size 8, version fields, presence of opcodes, length
     15  * fields). The end-to-end producer↔consumer round trip is exercised by
     16  * test/cg path W. */
     17 
     18 #include <kit/arch.h>
     19 #include <kit/core.h>
     20 #include <stdarg.h>
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 
     25 #include "arch/x64/regs.h"
     26 #include "core/core.h"
     27 #include "core/pool.h"
     28 #include "debug/debug.h"
     29 #include "lib/kit_unit.h"
     30 #include "obj/obj.h"
     31 
     32 /* Shared test context replaces the per-file heap/diag/counter globals;
     33  * EXPECT aliases CU_EXPECT so the call sites are unchanged. The original
     34  * context used now=-1, set in main after kit_unit_init. */
     35 static KitUnit g_u;
     36 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     37 
     38 static const Section* sec_by_name(const ObjBuilder* ob, Pool* pool,
     39                                   const char* name) {
     40   u32 i, n = obj_section_count(ob);
     41   for (i = 1; i < n; ++i) {
     42     const Section* s = obj_section_get(ob, i);
     43     Slice sn = pool_slice(pool, s->name);
     44     if (sn.s && strlen(name) == sn.len && memcmp(sn.s, name, sn.len) == 0)
     45       return s;
     46   }
     47   return NULL;
     48 }
     49 
     50 static u32 sec_size(const Section* s) { return s ? buf_pos(&s->bytes) : 0; }
     51 
     52 static void sec_read(const Section* s, u32 ofs, void* dst, size_t n) {
     53   buf_read(&s->bytes, ofs, dst, n);
     54 }
     55 
     56 static u16 le16(const Section* s, u32 ofs) {
     57   u8 b[2];
     58   sec_read(s, ofs, b, 2);
     59   return (u16)(b[0] | ((u16)b[1] << 8));
     60 }
     61 
     62 static u32 le32(const Section* s, u32 ofs) {
     63   u8 b[4];
     64   sec_read(s, ofs, b, 4);
     65   return (u32)(b[0] | ((u32)b[1] << 8) | ((u32)b[2] << 16) | ((u32)b[3] << 24));
     66 }
     67 
     68 static u8 byte_at(const Section* s, u32 ofs) {
     69   u8 b;
     70   sec_read(s, ofs, &b, 1);
     71   return b;
     72 }
     73 
     74 /* Per-arch nop encoding used by the round-trip test. Both 4 bytes; the
     75  * encoders are inlined here so the test stays self-contained. */
     76 #define ARCH_NOP_AA64 0xd503201fu /* HINT #0 */
     77 #define ARCH_NOP_RV64 0x00000013u /* ADDI x0, x0, 0 */
     78 
     79 static int run_one(KitArchKind arch, uint32_t nop_word, const char* tag) {
     80   KitTargetSpec t;
     81   Compiler* c;
     82   ObjBuilder* ob;
     83   Debug* d;
     84   ObjSecId text_sec;
     85   ObjSymId fsym;
     86   Pool* pool;
     87   int local_fail = 0;
     88 
     89   t = kit_unit_target(arch, KIT_OS_LINUX, KIT_OBJ_ELF);
     90 
     91   if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) {
     92     fprintf(stderr, "[%s] compiler_new failed\n", tag);
     93     return 2;
     94   }
     95   ob = obj_new(c);
     96   pool = c->global;
     97 
     98   /* .text section + symbol "f". */
     99   text_sec = obj_section(ob, pool_intern_slice(pool, SLICE_LIT(".text")),
    100                          SEC_TEXT, SF_EXEC | SF_ALLOC, 4);
    101   /* one 4-byte arch nop */
    102   {
    103     u32 nop = nop_word;
    104     obj_write(ob, text_sec, &nop, 4);
    105   }
    106   fsym = obj_symbol(ob, pool_intern_slice(pool, SLICE_LIT("f")), SB_GLOBAL,
    107                     SK_FUNC, text_sec, 0, 4);
    108 
    109   /* Drive Debug. */
    110   d = debug_new(c, ob);
    111   EXPECT(d != NULL, "[%s] debug_new returned NULL", tag);
    112   if (!d) {
    113     kit_compiler_free(c);
    114     return 2;
    115   }
    116   {
    117     /* Set a primary file. */
    118     u32 fid = 0;
    119     (void)source_add_memory(c->sources, SLICE_LIT("p01.c"), &fid);
    120     SrcLoc decl = {fid, 1, 0};
    121     SrcLoc l10 = {fid, 10, 0};
    122     DebugTypeId int_tid = debug_type_base(
    123         d, pool_intern_slice(pool, SLICE_LIT("int")), DEBUG_BE_SIGNED, 4);
    124     DebugTypeId fn_tid = debug_type_func(d, int_tid, NULL, 0, 0);
    125     /* Pre-register the file as DWARF index 0 = primary. */
    126     (void)debug_file(d, fid);
    127 
    128     debug_func_begin(d, fsym, fn_tid, decl);
    129     debug_line(d, text_sec, 0, l10, 1);
    130     debug_func_pc_range(d, text_sec, 0, 4);
    131     debug_func_end(d);
    132   }
    133 
    134   debug_emit(d);
    135 
    136   /* ---- structural assertions ---- */
    137   {
    138     const Section* line = sec_by_name(ob, pool, ".debug_line");
    139     const Section* info = sec_by_name(ob, pool, ".debug_info");
    140     const Section* abbr = sec_by_name(ob, pool, ".debug_abbrev");
    141     const Section* str = sec_by_name(ob, pool, ".debug_str");
    142     const Section* lstr = sec_by_name(ob, pool, ".debug_line_str");
    143     const Section* sof = sec_by_name(ob, pool, ".debug_str_offsets");
    144     const Section* aranges = sec_by_name(ob, pool, ".debug_aranges");
    145     const Section* rng = sec_by_name(ob, pool, ".debug_rnglists");
    146 
    147     EXPECT(line != NULL, "[%s] .debug_line missing", tag);
    148     EXPECT(info != NULL, "[%s] .debug_info missing", tag);
    149     EXPECT(abbr != NULL, "[%s] .debug_abbrev missing", tag);
    150     EXPECT(str != NULL, "[%s] .debug_str missing", tag);
    151     EXPECT(lstr != NULL, "[%s] .debug_line_str missing", tag);
    152     EXPECT(sof != NULL, "[%s] .debug_str_offsets missing", tag);
    153     EXPECT(aranges != NULL, "[%s] .debug_aranges missing", tag);
    154     EXPECT(rng != NULL, "[%s] .debug_rnglists missing", tag);
    155 
    156     if (line) {
    157       /* unit_length at offset 0 must equal section size - 4. */
    158       u32 ul = le32(line, 0);
    159       EXPECT(ul + 4 == sec_size(line),
    160              "[%s] .debug_line unit_length=%u, section size=%u", tag, ul,
    161              sec_size(line));
    162       /* version */
    163       EXPECT(le16(line, 4) == 5, "[%s] .debug_line version != 5", tag);
    164       /* address_size */
    165       EXPECT(byte_at(line, 6) == 8, "[%s] .debug_line address_size != 8", tag);
    166       /* segment selector size */
    167       EXPECT(byte_at(line, 7) == 0, "[%s] .debug_line seg_size != 0", tag);
    168       /* DWARF 5 §6.2.4: header is unit_length(4) + version(2) +
    169        * address_size(1) + seg_size(1) + header_length(4) +
    170        * min_inst_length(1) + ... — byte offset 12 holds
    171        * min_inst_length. Both aa64 and rv64 emit 4-byte fixed-width
    172        * instructions; the producer must encode the value 4 there. */
    173       EXPECT(byte_at(line, 12) == 4,
    174              "[%s] .debug_line min_inst_length != 4 (got %u)", tag,
    175              byte_at(line, 12));
    176       /* max_ops_per_inst at offset 13. */
    177       EXPECT(byte_at(line, 13) == 1, "[%s] .debug_line max_ops_per_inst != 1",
    178              tag);
    179     }
    180     if (info) {
    181       u32 ul = le32(info, 0);
    182       EXPECT(ul + 4 == sec_size(info),
    183              "[%s] .debug_info unit_length=%u, section size=%u", tag, ul,
    184              sec_size(info));
    185       EXPECT(le16(info, 4) == 5, "[%s] .debug_info version != 5", tag);
    186       EXPECT(byte_at(info, 6) == 1,
    187              "[%s] .debug_info unit_type != DW_UT_compile", tag);
    188       EXPECT(byte_at(info, 7) == 8, "[%s] .debug_info address_size != 8", tag);
    189     }
    190     if (str) {
    191       /* Should contain "kit 0.1\0" somewhere. */
    192       u32 sz = sec_size(str);
    193       u8* bytes = (u8*)malloc(sz);
    194       buf_flatten(&str->bytes, bytes);
    195       int found = 0;
    196       u32 i;
    197       for (i = 0; i + 7 <= sz; ++i) {
    198         if (memcmp(bytes + i, "kit 0.1", 7) == 0) {
    199           found = 1;
    200           break;
    201         }
    202       }
    203       EXPECT(found, "[%s] .debug_str missing producer", tag);
    204       free(bytes);
    205     }
    206     if (sof) {
    207       /* unit_length, version 5, padding 0, then N*4 offsets. */
    208       EXPECT(le16(sof, 4) == 5, "[%s] .debug_str_offsets version != 5", tag);
    209     }
    210     if (rng) {
    211       EXPECT(le16(rng, 4) == 5, "[%s] .debug_rnglists version != 5", tag);
    212       EXPECT(byte_at(rng, 6) == 8, "[%s] .debug_rnglists addr_size != 8", tag);
    213     }
    214     if (aranges) {
    215       EXPECT(le16(aranges, 4) == 2, "[%s] .debug_aranges version != 2", tag);
    216     }
    217 
    218     /* Reloc inventory: there should be exactly 4 ABS64 relocs against
    219      * fsym (one each in .debug_info low_pc, .debug_line set_address,
    220      * .debug_aranges first tuple addr, .debug_rnglists start_length). */
    221     {
    222       u32 nrel = obj_reloc_total(ob);
    223       u32 abs64_against_f = 0;
    224       u32 i;
    225       for (i = 0; i < nrel; ++i) {
    226         const Reloc* r = obj_reloc_at(ob, i);
    227         if (r->kind == R_ABS64 && r->sym == fsym) abs64_against_f++;
    228       }
    229       EXPECT(abs64_against_f == 4,
    230              "[%s] expected 4 ABS64 relocs against fsym, got %u", tag,
    231              abs64_against_f);
    232     }
    233   }
    234 
    235   debug_free(d);
    236   obj_free(ob);
    237   kit_compiler_free(c);
    238   return local_fail;
    239 }
    240 
    241 /* Per-arch register-name spot checks: confirm rv64 DWARF numbers match
    242  * the psABI (x1=ra=1, x2=sp=2, x8=s0/fp=8, x10=a0=10, f0=ft0=32,
    243  * f8=fs0=40) and aa64 still resolves x0..x30/sp by their DWARF indices. */
    244 static void check_reg(KitArchKind arch, const char* tag, uint32_t expect_idx,
    245                       const char* expect_name) {
    246   KitSlice nm = kit_arch_register_name(arch, expect_idx);
    247   uint32_t got_idx = 0u;
    248   KitStatus st =
    249       kit_arch_register_index(arch, kit_slice_cstr(expect_name), &got_idx);
    250   EXPECT(nm.s != NULL && kit_slice_eq_cstr(nm, expect_name),
    251          "[%s] register_name(%u) expected %s, got %.*s", tag, expect_idx,
    252          expect_name, KIT_SLICE_ARG(nm));
    253   EXPECT(st == KIT_OK && got_idx == expect_idx,
    254          "[%s] register_index(%s) expected %u, got %u (status %d)", tag,
    255          expect_name, expect_idx, got_idx, (int)st);
    256 }
    257 
    258 static void run_arch_register_checks(void) {
    259   /* aa64 (sanity): x0..x30 + sp = 0..31. */
    260   check_reg(KIT_ARCH_ARM_64, "aa64", 0, "x0");
    261   check_reg(KIT_ARCH_ARM_64, "aa64", 30, "x30");
    262   check_reg(KIT_ARCH_ARM_64, "aa64", 31, "sp");
    263 
    264   /* rv64 psABI / DWARF: integer regs 0..31, FP regs 32..63. */
    265   check_reg(KIT_ARCH_RV64, "rv64", 0, "zero");
    266   check_reg(KIT_ARCH_RV64, "rv64", 1, "ra");
    267   check_reg(KIT_ARCH_RV64, "rv64", 2, "sp");
    268   check_reg(KIT_ARCH_RV64, "rv64", 8, "s0");
    269   check_reg(KIT_ARCH_RV64, "rv64", 10, "a0");
    270   check_reg(KIT_ARCH_RV64, "rv64", 31, "t6");
    271   check_reg(KIT_ARCH_RV64, "rv64", 32, "ft0");
    272   check_reg(KIT_ARCH_RV64, "rv64", 40, "fs0");
    273   check_reg(KIT_ARCH_RV64, "rv64", 63, "ft11");
    274 
    275   /* "fp" alias for s0/x8 on rv64. */
    276   {
    277     uint32_t idx = 0;
    278     KitStatus st =
    279         kit_arch_register_index(KIT_ARCH_RV64, KIT_SLICE_LIT("fp"), &idx);
    280     EXPECT(st == KIT_OK && idx == 8,
    281            "[rv64] register_index(fp) expected 8, got %u (status %d)", idx,
    282            (int)st);
    283   }
    284 
    285   /* x64 hardware-GPR-index -> SysV DWARF number. This is the map the x64
    286    * backend applies before emitting CFI register operands; rcx/rdx/rsi/rdi/
    287    * rsp/rbp diverge between the two namespaces, while rax/rbx/r8..r15 are
    288    * identity. (Guards the table behind x64_dwarf_from_hw_gpr; the rbp=HW5 case
    289    * is exactly the one that misencoded .eh_frame as RDI before the map was
    290    * applied.) */
    291   {
    292     static const uint32_t expect[16] = {0, 2, 1,  3,  7,  6,  4,  5,
    293                                         8, 9, 10, 11, 12, 13, 14, 15};
    294     uint32_t hw;
    295     for (hw = 0; hw < 16u; ++hw) {
    296       uint32_t got = x64_dwarf_from_hw_gpr(hw);
    297       EXPECT(got == expect[hw],
    298              "[x64] dwarf_from_hw_gpr(%u) expected %u got %u", hw, expect[hw],
    299              got);
    300     }
    301     /* rip (16) and any non-GPR index passes through unchanged. */
    302     EXPECT(x64_dwarf_from_hw_gpr(16u) == 16u, "[x64] rip passthrough");
    303   }
    304 }
    305 
    306 static int run_x64_debug_line_check(void) {
    307   KitTargetSpec xt;
    308   Compiler* xc;
    309   ObjBuilder* xob;
    310   Debug* xd;
    311   ObjSecId xtext_sec;
    312   ObjSymId xfsym;
    313   Pool* xpool;
    314 
    315   xt = kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    316 
    317   if (kit_unit_compiler_new(&g_u, xt, &xc) != KIT_OK || !xc) {
    318     fprintf(stderr, "x64 compiler_new failed\n");
    319     return 2;
    320   }
    321   xob = obj_new(xc);
    322   xpool = xc->global;
    323 
    324   xtext_sec = obj_section(xob, pool_intern_slice(xpool, SLICE_LIT(".text")),
    325                           SEC_TEXT, SF_EXEC | SF_ALLOC, 1);
    326   {
    327     u8 code[2] = {0x90, 0xc3}; /* nop; ret */
    328     obj_write(xob, xtext_sec, code, sizeof(code));
    329   }
    330   xfsym = obj_symbol(xob, pool_intern_slice(xpool, SLICE_LIT("xf")), SB_GLOBAL,
    331                      SK_FUNC, xtext_sec, 0, 2);
    332 
    333   xd = debug_new(xc, xob);
    334   EXPECT(xd != NULL, "x64 debug_new returned NULL");
    335   if (xd) {
    336     u32 fid = 0;
    337     (void)source_add_memory(xc->sources, SLICE_LIT("x64.c"), &fid);
    338     SrcLoc decl = {fid, 1, 0};
    339     SrcLoc l10 = {fid, 10, 0};
    340     SrcLoc l11 = {fid, 11, 0};
    341     DebugTypeId int_tid = debug_type_base(
    342         xd, pool_intern_slice(xpool, SLICE_LIT("int")), DEBUG_BE_SIGNED, 4);
    343     DebugTypeId fn_tid = debug_type_func(xd, int_tid, NULL, 0, 0);
    344     (void)debug_file(xd, fid);
    345 
    346     debug_func_begin(xd, xfsym, fn_tid, decl);
    347     debug_line(xd, xtext_sec, 0, l10, 1);
    348     debug_line(xd, xtext_sec, 1, l11, 1);
    349     debug_func_pc_range(xd, xtext_sec, 0, 2);
    350     debug_func_end(xd);
    351     debug_emit(xd);
    352 
    353     {
    354       const Section* xline = sec_by_name(xob, xpool, ".debug_line");
    355       EXPECT(xline != NULL, "x64 .debug_line missing");
    356       if (xline) {
    357         EXPECT(byte_at(xline, 12) == 1, "x64 .debug_line min_inst_length != 1");
    358       }
    359     }
    360 
    361     debug_free(xd);
    362   }
    363   obj_free(xob);
    364   kit_compiler_free(xc);
    365   return 0;
    366 }
    367 
    368 int main(void) {
    369   int rc = 0;
    370 
    371   kit_unit_init(&g_u);
    372   g_u.ctx.now = -1;
    373 
    374   rc |= run_one(KIT_ARCH_ARM_64, ARCH_NOP_AA64, "aa64");
    375   rc |= run_one(KIT_ARCH_RV64, ARCH_NOP_RV64, "rv64");
    376   rc |= run_x64_debug_line_check();
    377   run_arch_register_checks();
    378 
    379   if (g_u.fails || rc) {
    380     fprintf(stderr, "%d FAILED\n", g_u.fails);
    381     return 1;
    382   }
    383   printf("debug roundtrip_unit: OK\n");
    384   return 0;
    385 }