kit

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

dwarf_test.c (40053B)


      1 /* test/dwarf/dwarf_test.c — round-trip tests for the DWARF consumer.
      2  *
      3  * Builds an in-memory ELF object containing hand-crafted .debug_*
      4  * sections, then re-opens it with kit_obj_open + kit_dwarf_open
      5  * and exercises the public consumer surface:
      6  *
      7  *   - kit_dwarf_open finds the mandatory five sections
      8  *   - kit_dwarf_addr_to_line / line_to_addr round-trip
      9  *   - kit_dwarf_subprogram_at returns a non-empty range with a name
     10  *   - kit_dwarf_var_at + kit_dwarf_loc_read fast-path
     11  *   - kit_dwarf_type_info / field_iter / enum_iter
     12  *
     13  * Only depends on libkit.a (the public surface) plus libc.
     14  */
     15 
     16 #include <kit/core.h>
     17 #include <kit/dwarf.h>
     18 #include <kit/object.h>
     19 #include <stdarg.h>
     20 #include <stdint.h>
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 
     25 #include "lib/kit_unit.h"
     26 
     27 /* This test reaches into the internal obj/ surface to construct a
     28  * DWARF-bearing ELF without going through the parser/codegen path.
     29  * That's deliberate: we want to test the *consumer* in isolation against
     30  * known-good hand-crafted DWARF byte streams. */
     31 #include "core/core.h"
     32 #include "core/pool.h"
     33 #include "obj/obj.h"
     34 
     35 /* Shared test context replaces the per-file heap/diag/counter globals;
     36  * EXPECT aliases CU_EXPECT so the call sites are unchanged. */
     37 static KitUnit g_u;
     38 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     39 
     40 /* ---- byte builders -------------------------------------------------- */
     41 
     42 typedef struct ByteBuf {
     43   uint8_t* data;
     44   size_t len;
     45   size_t cap;
     46 } ByteBuf;
     47 
     48 static void bb_init(ByteBuf* b) {
     49   b->data = NULL;
     50   b->len = 0;
     51   b->cap = 0;
     52 }
     53 static void bb_put(ByteBuf* b, const void* src, size_t n) {
     54   if (b->len + n > b->cap) {
     55     size_t nc = b->cap ? b->cap * 2 : 64;
     56     while (nc < b->len + n) nc *= 2;
     57     b->data = (uint8_t*)realloc(b->data, nc);
     58     b->cap = nc;
     59   }
     60   memcpy(b->data + b->len, src, n);
     61   b->len += n;
     62 }
     63 static void bb_u8(ByteBuf* b, uint8_t v) { bb_put(b, &v, 1); }
     64 static void bb_u16(ByteBuf* b, uint16_t v) {
     65   uint8_t buf[2] = {(uint8_t)v, (uint8_t)(v >> 8)};
     66   bb_put(b, buf, 2);
     67 }
     68 static void bb_u32(ByteBuf* b, uint32_t v) {
     69   uint8_t buf[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16),
     70                     (uint8_t)(v >> 24)};
     71   bb_put(b, buf, 4);
     72 }
     73 static void bb_u64(ByteBuf* b, uint64_t v) {
     74   uint8_t buf[8] = {(uint8_t)v,         (uint8_t)(v >> 8),  (uint8_t)(v >> 16),
     75                     (uint8_t)(v >> 24), (uint8_t)(v >> 32), (uint8_t)(v >> 40),
     76                     (uint8_t)(v >> 48), (uint8_t)(v >> 56)};
     77   bb_put(b, buf, 8);
     78 }
     79 static void bb_uleb(ByteBuf* b, uint64_t v) {
     80   for (;;) {
     81     uint8_t byte = v & 0x7f;
     82     v >>= 7;
     83     if (v) byte |= 0x80;
     84     bb_u8(b, byte);
     85     if (!v) break;
     86   }
     87 }
     88 static void bb_sleb(ByteBuf* b, int64_t v) {
     89   int more = 1;
     90   while (more) {
     91     uint8_t byte = v & 0x7f;
     92     v >>= 7;
     93     if ((v == 0 && !(byte & 0x40)) || (v == -1 && (byte & 0x40)))
     94       more = 0;
     95     else
     96       byte |= 0x80;
     97     bb_u8(b, byte);
     98   }
     99 }
    100 static void bb_str(ByteBuf* b, const char* s) { bb_put(b, s, strlen(s) + 1); }
    101 
    102 /* ---- DWARF constants (subset) --------------------------------------- */
    103 #define DW_TAG_compile_unit 0x11
    104 #define DW_TAG_subprogram 0x2e
    105 #define DW_TAG_base_type 0x24
    106 #define DW_TAG_pointer_type 0x0f
    107 #define DW_TAG_typedef 0x16
    108 #define DW_TAG_array_type 0x01
    109 #define DW_TAG_subrange_type 0x21
    110 #define DW_TAG_structure_type 0x13
    111 #define DW_TAG_member 0x0d
    112 #define DW_TAG_enumeration_type 0x04
    113 #define DW_TAG_enumerator 0x28
    114 #define DW_TAG_variable 0x34
    115 #define DW_TAG_formal_parameter 0x05
    116 
    117 #define DW_AT_name 0x03
    118 #define DW_AT_stmt_list 0x10
    119 #define DW_AT_low_pc 0x11
    120 #define DW_AT_high_pc 0x12
    121 #define DW_AT_language 0x13
    122 #define DW_AT_comp_dir 0x1b
    123 #define DW_AT_const_value 0x1c
    124 #define DW_AT_byte_size 0x0b
    125 #define DW_AT_encoding 0x3e
    126 #define DW_AT_type 0x49
    127 #define DW_AT_data_member_location 0x38
    128 #define DW_AT_count 0x37
    129 #define DW_AT_location 0x02
    130 #define DW_AT_frame_base 0x40
    131 #define DW_AT_decl_file 0x3a
    132 #define DW_AT_decl_line 0x3b
    133 #define DW_AT_str_offsets_base 0x72
    134 
    135 #define DW_FORM_addr 0x01
    136 #define DW_FORM_data1 0x0b
    137 #define DW_FORM_data2 0x05
    138 #define DW_FORM_data4 0x06
    139 #define DW_FORM_data8 0x07
    140 #define DW_FORM_strx1 0x26
    141 #define DW_FORM_strp 0x0e
    142 #define DW_FORM_line_strp 0x1f
    143 #define DW_FORM_sec_offset 0x17
    144 #define DW_FORM_udata 0x0f
    145 #define DW_FORM_flag_present 0x19
    146 #define DW_FORM_ref4 0x13
    147 #define DW_FORM_exprloc 0x18
    148 #define DW_FORM_string 0x08
    149 
    150 #define DW_LNCT_path 0x01
    151 #define DW_LNCT_directory_index 0x02
    152 
    153 #define DW_ATE_signed 0x05
    154 #define DW_ATE_unsigned 0x07
    155 
    156 #define DW_OP_reg0 0x50
    157 #define DW_OP_fbreg 0x91
    158 #define DW_OP_call_frame_cfa 0x9c
    159 
    160 #define DW_LNS_copy 0x01
    161 #define DW_LNS_advance_pc 0x02
    162 #define DW_LNS_advance_line 0x03
    163 #define DW_LNE_end_sequence 0x01
    164 #define DW_LNE_set_address 0x02
    165 
    166 #define DW_LANG_C11 0x1d
    167 
    168 #define DW_CHILDREN_no 0
    169 #define DW_CHILDREN_yes 1
    170 
    171 /* ---- build the .debug_* sections ------------------------------------ */
    172 
    173 /* Plan:
    174  *   .debug_line_str: paths.
    175  *   .debug_str: cu name, subprog name, type names, var names.
    176  *   .debug_abbrev: 5 abbrevs:
    177  *     1: compile_unit (children) — name(strp), comp_dir(strp), language(udata),
    178  *         stmt_list(sec_offset), low_pc(addr), high_pc(data8)
    179  *     2: subprogram (children) — name(strp), low_pc(addr), high_pc(data8),
    180  *         frame_base(exprloc), decl_file(udata), decl_line(udata)
    181  *     3: base_type (no children) — name(strp), byte_size(data1),
    182  * encoding(data1) 4: variable (no children) — name(strp), type(ref4),
    183  * location(exprloc) 5: formal_parameter (no children) — same shape as variable
    184  *
    185  *   .debug_info: one CU; one subprogram with two locals + one param +
    186  *      one base type child.
    187  *   .debug_line: header for one file plus a small program emitting two rows:
    188  *      (file=0, line=10, addr=0x100), (file=0, line=11, addr=0x104).
    189  */
    190 
    191 typedef struct DieOffsets {
    192   uint32_t int_off;
    193   uint32_t ptr_off;
    194   uint32_t typedef_off;
    195   uint32_t array_off;
    196   uint32_t struct_off;
    197 } DieOffsets;
    198 
    199 static void build_debug_sections(ByteBuf* abbrev, ByteBuf* info, ByteBuf* line,
    200                                  ByteBuf* str, ByteBuf* line_str,
    201                                  uint64_t func_low, uint64_t func_size,
    202                                  DieOffsets* off_out) {
    203   /* str pool: collect offsets first by appending. */
    204   size_t s_cu_name = str->len;
    205   bb_str(str, "test.c");
    206   size_t s_cu_dir = str->len;
    207   bb_str(str, "/proj");
    208   size_t s_func = str->len;
    209   bb_str(str, "test_main");
    210   size_t s_int = str->len;
    211   bb_str(str, "int");
    212   size_t s_x = str->len;
    213   bb_str(str, "x");
    214   size_t s_y = str->len;
    215   bb_str(str, "y");
    216   size_t s_arg = str->len;
    217   bb_str(str, "arg");
    218   size_t s_my_int = str->len;
    219   bb_str(str, "my_int");
    220   size_t s_pt = str->len;
    221   bb_str(str, "Point");
    222   size_t s_x_field = str->len;
    223   bb_str(str, "x_field");
    224   size_t s_y_field = str->len;
    225   bb_str(str, "y_field");
    226 
    227   /* line_str: dir, file. */
    228   size_t ls_dir = line_str->len;
    229   bb_str(line_str, "/proj");
    230   size_t ls_file = line_str->len;
    231   bb_str(line_str, "test.c");
    232 
    233   /* abbrev table */
    234   /* Abbrev 1: compile_unit, has children. */
    235   bb_uleb(abbrev, 1);
    236   bb_uleb(abbrev, DW_TAG_compile_unit);
    237   bb_u8(abbrev, DW_CHILDREN_yes);
    238   bb_uleb(abbrev, DW_AT_name);
    239   bb_uleb(abbrev, DW_FORM_strp);
    240   bb_uleb(abbrev, DW_AT_comp_dir);
    241   bb_uleb(abbrev, DW_FORM_strp);
    242   bb_uleb(abbrev, DW_AT_language);
    243   bb_uleb(abbrev, DW_FORM_udata);
    244   bb_uleb(abbrev, DW_AT_stmt_list);
    245   bb_uleb(abbrev, DW_FORM_sec_offset);
    246   bb_uleb(abbrev, DW_AT_low_pc);
    247   bb_uleb(abbrev, DW_FORM_addr);
    248   bb_uleb(abbrev, DW_AT_high_pc);
    249   bb_uleb(abbrev, DW_FORM_data8);
    250   bb_uleb(abbrev, 0);
    251   bb_uleb(abbrev, 0);
    252   /* Abbrev 2: subprogram, has children. */
    253   bb_uleb(abbrev, 2);
    254   bb_uleb(abbrev, DW_TAG_subprogram);
    255   bb_u8(abbrev, DW_CHILDREN_yes);
    256   bb_uleb(abbrev, DW_AT_name);
    257   bb_uleb(abbrev, DW_FORM_strp);
    258   bb_uleb(abbrev, DW_AT_low_pc);
    259   bb_uleb(abbrev, DW_FORM_addr);
    260   bb_uleb(abbrev, DW_AT_high_pc);
    261   bb_uleb(abbrev, DW_FORM_data8);
    262   bb_uleb(abbrev, DW_AT_frame_base);
    263   bb_uleb(abbrev, DW_FORM_exprloc);
    264   bb_uleb(abbrev, DW_AT_decl_file);
    265   bb_uleb(abbrev, DW_FORM_udata);
    266   bb_uleb(abbrev, DW_AT_decl_line);
    267   bb_uleb(abbrev, DW_FORM_udata);
    268   bb_uleb(abbrev, 0);
    269   bb_uleb(abbrev, 0);
    270   /* Abbrev 3: base_type, no children. */
    271   bb_uleb(abbrev, 3);
    272   bb_uleb(abbrev, DW_TAG_base_type);
    273   bb_u8(abbrev, DW_CHILDREN_no);
    274   bb_uleb(abbrev, DW_AT_name);
    275   bb_uleb(abbrev, DW_FORM_strp);
    276   bb_uleb(abbrev, DW_AT_byte_size);
    277   bb_uleb(abbrev, DW_FORM_data1);
    278   bb_uleb(abbrev, DW_AT_encoding);
    279   bb_uleb(abbrev, DW_FORM_data1);
    280   bb_uleb(abbrev, 0);
    281   bb_uleb(abbrev, 0);
    282   /* Abbrev 4: variable, no children. */
    283   bb_uleb(abbrev, 4);
    284   bb_uleb(abbrev, DW_TAG_variable);
    285   bb_u8(abbrev, DW_CHILDREN_no);
    286   bb_uleb(abbrev, DW_AT_name);
    287   bb_uleb(abbrev, DW_FORM_strp);
    288   bb_uleb(abbrev, DW_AT_type);
    289   bb_uleb(abbrev, DW_FORM_ref4);
    290   bb_uleb(abbrev, DW_AT_location);
    291   bb_uleb(abbrev, DW_FORM_exprloc);
    292   bb_uleb(abbrev, 0);
    293   bb_uleb(abbrev, 0);
    294   /* Abbrev 5: formal_parameter, no children. */
    295   bb_uleb(abbrev, 5);
    296   bb_uleb(abbrev, DW_TAG_formal_parameter);
    297   bb_u8(abbrev, DW_CHILDREN_no);
    298   bb_uleb(abbrev, DW_AT_name);
    299   bb_uleb(abbrev, DW_FORM_strp);
    300   bb_uleb(abbrev, DW_AT_type);
    301   bb_uleb(abbrev, DW_FORM_ref4);
    302   bb_uleb(abbrev, DW_AT_location);
    303   bb_uleb(abbrev, DW_FORM_exprloc);
    304   bb_uleb(abbrev, 0);
    305   bb_uleb(abbrev, 0);
    306   /* Abbrev 6: pointer_type, no children — byte_size, type. */
    307   bb_uleb(abbrev, 6);
    308   bb_uleb(abbrev, DW_TAG_pointer_type);
    309   bb_u8(abbrev, DW_CHILDREN_no);
    310   bb_uleb(abbrev, DW_AT_byte_size);
    311   bb_uleb(abbrev, DW_FORM_data1);
    312   bb_uleb(abbrev, DW_AT_type);
    313   bb_uleb(abbrev, DW_FORM_ref4);
    314   bb_uleb(abbrev, 0);
    315   bb_uleb(abbrev, 0);
    316   /* Abbrev 7: typedef, no children — name, type. */
    317   bb_uleb(abbrev, 7);
    318   bb_uleb(abbrev, DW_TAG_typedef);
    319   bb_u8(abbrev, DW_CHILDREN_no);
    320   bb_uleb(abbrev, DW_AT_name);
    321   bb_uleb(abbrev, DW_FORM_strp);
    322   bb_uleb(abbrev, DW_AT_type);
    323   bb_uleb(abbrev, DW_FORM_ref4);
    324   bb_uleb(abbrev, 0);
    325   bb_uleb(abbrev, 0);
    326   /* Abbrev 8: array_type, has children — type. */
    327   bb_uleb(abbrev, 8);
    328   bb_uleb(abbrev, DW_TAG_array_type);
    329   bb_u8(abbrev, DW_CHILDREN_yes);
    330   bb_uleb(abbrev, DW_AT_type);
    331   bb_uleb(abbrev, DW_FORM_ref4);
    332   bb_uleb(abbrev, 0);
    333   bb_uleb(abbrev, 0);
    334   /* Abbrev 9: subrange_type, no children — count. */
    335   bb_uleb(abbrev, 9);
    336   bb_uleb(abbrev, DW_TAG_subrange_type);
    337   bb_u8(abbrev, DW_CHILDREN_no);
    338   bb_uleb(abbrev, DW_AT_count);
    339   bb_uleb(abbrev, DW_FORM_data1);
    340   bb_uleb(abbrev, 0);
    341   bb_uleb(abbrev, 0);
    342   /* Abbrev 10: structure_type, has children — name, byte_size. */
    343   bb_uleb(abbrev, 10);
    344   bb_uleb(abbrev, DW_TAG_structure_type);
    345   bb_u8(abbrev, DW_CHILDREN_yes);
    346   bb_uleb(abbrev, DW_AT_name);
    347   bb_uleb(abbrev, DW_FORM_strp);
    348   bb_uleb(abbrev, DW_AT_byte_size);
    349   bb_uleb(abbrev, DW_FORM_data1);
    350   bb_uleb(abbrev, 0);
    351   bb_uleb(abbrev, 0);
    352   /* Abbrev 11: member, no children — name, type, data_member_location(udata).
    353    */
    354   bb_uleb(abbrev, 11);
    355   bb_uleb(abbrev, DW_TAG_member);
    356   bb_u8(abbrev, DW_CHILDREN_no);
    357   bb_uleb(abbrev, DW_AT_name);
    358   bb_uleb(abbrev, DW_FORM_strp);
    359   bb_uleb(abbrev, DW_AT_type);
    360   bb_uleb(abbrev, DW_FORM_ref4);
    361   bb_uleb(abbrev, DW_AT_data_member_location);
    362   bb_uleb(abbrev, DW_FORM_udata);
    363   bb_uleb(abbrev, 0);
    364   bb_uleb(abbrev, 0);
    365   /* End-of-table */
    366   bb_uleb(abbrev, 0);
    367 
    368   /* .debug_info CU header (32-bit DWARF, version 5) */
    369   /* unit_length placeholder */
    370   size_t cu_len_pos = info->len;
    371   bb_u32(info, 0); /* unit_length */
    372   size_t cu_body_start = info->len;
    373   bb_u16(info, 5);   /* version */
    374   bb_u8(info, 0x01); /* unit_type = DW_UT_compile */
    375   bb_u8(info, 8);    /* address_size */
    376   bb_u32(info, 0);   /* debug_abbrev_offset */
    377   /* CU root DIE — abbrev 1 */
    378   size_t cu_die_off = info->len;
    379   bb_uleb(info, 1); /* abbrev code */
    380   bb_u32(info, (uint32_t)s_cu_name);
    381   bb_u32(info, (uint32_t)s_cu_dir);
    382   bb_uleb(info, DW_LANG_C11);
    383   bb_u32(info, 0);         /* stmt_list -> .debug_line offset 0 */
    384   bb_u64(info, func_low);  /* low_pc */
    385   bb_u64(info, func_size); /* high_pc (offset) */
    386 
    387   /* Children: int (base_type), then sibling type DIEs, then subprogram. */
    388   size_t int_die_off = info->len;
    389   bb_uleb(info, 3); /* base_type abbrev */
    390   bb_u32(info, (uint32_t)s_int);
    391   bb_u8(info, 4); /* byte_size */
    392   bb_u8(info, DW_ATE_signed);
    393 
    394   /* pointer_type → int (8-byte pointer). */
    395   size_t ptr_die_off = info->len;
    396   bb_uleb(info, 6);
    397   bb_u8(info, 8); /* byte_size */
    398   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    399 
    400   /* typedef my_int → int. */
    401   size_t td_die_off = info->len;
    402   bb_uleb(info, 7);
    403   bb_u32(info, (uint32_t)s_my_int);
    404   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    405 
    406   /* array_type → int [4]. */
    407   size_t arr_die_off = info->len;
    408   bb_uleb(info, 8);
    409   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    410   /* subrange child: count=4 */
    411   bb_uleb(info, 9);
    412   bb_u8(info, 4);
    413   /* end-of-children for array */
    414   bb_uleb(info, 0);
    415 
    416   /* struct Point { int x_field; int y_field; }, byte_size=8. */
    417   size_t st_die_off = info->len;
    418   bb_uleb(info, 10);
    419   bb_u32(info, (uint32_t)s_pt);
    420   bb_u8(info, 8);
    421   /* member x_field */
    422   bb_uleb(info, 11);
    423   bb_u32(info, (uint32_t)s_x_field);
    424   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    425   bb_uleb(info, 0);
    426   /* member y_field */
    427   bb_uleb(info, 11);
    428   bb_u32(info, (uint32_t)s_y_field);
    429   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    430   bb_uleb(info, 4);
    431   /* end-of-children for struct */
    432   bb_uleb(info, 0);
    433 
    434   if (off_out) {
    435     off_out->int_off = (uint32_t)int_die_off;
    436     off_out->ptr_off = (uint32_t)ptr_die_off;
    437     off_out->typedef_off = (uint32_t)td_die_off;
    438     off_out->array_off = (uint32_t)arr_die_off;
    439     off_out->struct_off = (uint32_t)st_die_off;
    440   }
    441 
    442   /* subprogram */
    443   size_t sub_die_off = info->len;
    444   bb_uleb(info, 2); /* subprogram abbrev */
    445   bb_u32(info, (uint32_t)s_func);
    446   bb_u64(info, func_low);
    447   bb_u64(info, func_size);
    448   bb_uleb(info, 1); /* frame_base exprloc len */
    449   bb_u8(info, DW_OP_call_frame_cfa);
    450   bb_uleb(info, 1); /* decl_file = 1 (the cu primary) */
    451   bb_uleb(info, 9); /* decl_line */
    452 
    453   /* Children: x (variable, fbreg -16), y (variable, fbreg -8),
    454    *           arg (formal_parameter, reg0). */
    455   bb_uleb(info, 4); /* var abbrev */
    456   bb_u32(info, (uint32_t)s_x);
    457   /* type ref: CU-relative offset of int_die_off. */
    458   bb_u32(info, (uint32_t)(int_die_off - cu_body_start + 4));
    459   /* Wait — ref4 is CU-relative, offset starting from CU header start. */
    460   /* CU header starts at cu_len_pos. The CU offset reference base is
    461    * cu_len_pos (since DWARF 5 ref* are relative to the start of the CU
    462    * header). */
    463   /* Re-patch: the previous bb_u32 wrote a wrong value. Patch in place. */
    464   {
    465     uint32_t want = (uint32_t)(int_die_off - cu_len_pos);
    466     info->data[info->len - 4] = (uint8_t)want;
    467     info->data[info->len - 3] = (uint8_t)(want >> 8);
    468     info->data[info->len - 2] = (uint8_t)(want >> 16);
    469     info->data[info->len - 1] = (uint8_t)(want >> 24);
    470   }
    471   /* location: DW_OP_fbreg -16 */
    472   {
    473     ByteBuf e;
    474     bb_init(&e);
    475     bb_u8(&e, DW_OP_fbreg);
    476     bb_sleb(&e, -16);
    477     bb_uleb(info, e.len);
    478     bb_put(info, e.data, e.len);
    479     free(e.data);
    480   }
    481 
    482   /* y */
    483   bb_uleb(info, 4);
    484   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    485   bb_u32(info, (uint32_t)s_y);
    486   /* The two writes above are out of order — fix: name first, then type. */
    487   /* Actually our abbrev was: name(strp), type(ref4), location(exprloc).
    488    * So we should write: name strp, then type ref4. Let's revert. */
    489   {
    490     /* Undo: we wrote 8 bytes for u32(type) then u32(s_y), but in the
    491      * wrong order. Rewind by 8 bytes and redo. */
    492     info->len -= 8;
    493     bb_u32(info, (uint32_t)s_y);
    494     bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    495   }
    496   {
    497     ByteBuf e;
    498     bb_init(&e);
    499     bb_u8(&e, DW_OP_fbreg);
    500     bb_sleb(&e, -8);
    501     bb_uleb(info, e.len);
    502     bb_put(info, e.data, e.len);
    503     free(e.data);
    504   }
    505 
    506   /* arg formal_parameter */
    507   bb_uleb(info, 5);
    508   bb_u32(info, (uint32_t)s_arg);
    509   bb_u32(info, (uint32_t)(int_die_off - cu_len_pos));
    510   {
    511     ByteBuf e;
    512     bb_init(&e);
    513     bb_u8(&e, DW_OP_reg0);
    514     bb_uleb(info, e.len);
    515     bb_put(info, e.data, e.len);
    516     free(e.data);
    517   }
    518 
    519   /* Locals for the extra type DIEs — give each a distinct frame offset.
    520    * Names are reused: we re-use the "x"/"y" string slots to keep the
    521    * existing test cases stable, but bind via the local fbreg position. */
    522   /* p (pointer to int) at fbreg -24. We re-purpose s_my_int's name. */
    523   bb_uleb(info, 4);
    524   bb_u32(info, (uint32_t)s_my_int); /* name "my_int" — used as local var name */
    525   bb_u32(info, (uint32_t)(ptr_die_off - cu_len_pos));
    526   {
    527     ByteBuf e;
    528     bb_init(&e);
    529     bb_u8(&e, DW_OP_fbreg);
    530     bb_sleb(&e, -24);
    531     bb_uleb(info, e.len);
    532     bb_put(info, e.data, e.len);
    533     free(e.data);
    534   }
    535   /* td (typedef alias) at fbreg -32 — name uses s_pt ("Point"). */
    536   bb_uleb(info, 4);
    537   bb_u32(info, (uint32_t)s_pt);
    538   bb_u32(info, (uint32_t)(td_die_off - cu_len_pos));
    539   {
    540     ByteBuf e;
    541     bb_init(&e);
    542     bb_u8(&e, DW_OP_fbreg);
    543     bb_sleb(&e, -32);
    544     bb_uleb(info, e.len);
    545     bb_put(info, e.data, e.len);
    546     free(e.data);
    547   }
    548   /* arr (array of int) at fbreg -64 — name uses s_x_field ("x_field"). */
    549   bb_uleb(info, 4);
    550   bb_u32(info, (uint32_t)s_x_field);
    551   bb_u32(info, (uint32_t)(arr_die_off - cu_len_pos));
    552   {
    553     ByteBuf e;
    554     bb_init(&e);
    555     bb_u8(&e, DW_OP_fbreg);
    556     bb_sleb(&e, -64);
    557     bb_uleb(info, e.len);
    558     bb_put(info, e.data, e.len);
    559     free(e.data);
    560   }
    561   /* st (struct Point) at fbreg -72 — name uses s_y_field ("y_field"). */
    562   bb_uleb(info, 4);
    563   bb_u32(info, (uint32_t)s_y_field);
    564   bb_u32(info, (uint32_t)(st_die_off - cu_len_pos));
    565   {
    566     ByteBuf e;
    567     bb_init(&e);
    568     bb_u8(&e, DW_OP_fbreg);
    569     bb_sleb(&e, -72);
    570     bb_uleb(info, e.len);
    571     bb_put(info, e.data, e.len);
    572     free(e.data);
    573   }
    574 
    575   /* end-of-children for subprogram */
    576   bb_uleb(info, 0);
    577   /* end-of-children for compile_unit */
    578   bb_uleb(info, 0);
    579 
    580   /* Patch CU unit_length */
    581   {
    582     uint32_t total = (uint32_t)(info->len - cu_body_start);
    583     info->data[cu_len_pos + 0] = (uint8_t)total;
    584     info->data[cu_len_pos + 1] = (uint8_t)(total >> 8);
    585     info->data[cu_len_pos + 2] = (uint8_t)(total >> 16);
    586     info->data[cu_len_pos + 3] = (uint8_t)(total >> 24);
    587   }
    588   (void)cu_die_off;
    589   (void)sub_die_off;
    590 
    591   /* .debug_line header (DWARF 5) */
    592   size_t line_len_pos = line->len;
    593   bb_u32(line, 0); /* unit_length */
    594   size_t line_body_start = line->len;
    595   bb_u16(line, 5); /* version */
    596   bb_u8(line, 8);  /* address_size */
    597   bb_u8(line, 0);  /* segment_selector_size */
    598   size_t hdr_len_pos = line->len;
    599   bb_u32(line, 0); /* header_length */
    600   size_t header_len_start = line->len;
    601   bb_u8(line, 4);                   /* min_inst_len */
    602   bb_u8(line, 1);                   /* max_ops_per_inst */
    603   bb_u8(line, 1);                   /* default_is_stmt */
    604   bb_u8(line, (uint8_t)(int8_t)-5); /* line_base */
    605   bb_u8(line, 14);                  /* line_range */
    606   bb_u8(line, 13);                  /* opcode_base */
    607   /* standard_opcode_lengths: 12 entries (opcode_base - 1) */
    608   uint8_t op_lens[] = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1};
    609   bb_put(line, op_lens, sizeof(op_lens));
    610   /* directory_entry_format: 1 pair (DW_LNCT_path, DW_FORM_line_strp) */
    611   bb_u8(line, 1);
    612   bb_uleb(line, DW_LNCT_path);
    613   bb_uleb(line, DW_FORM_line_strp);
    614   /* directories_count = 1 */
    615   bb_uleb(line, 1);
    616   bb_u32(line, (uint32_t)ls_dir);
    617   /* file_name_entry_format: 2 pairs (path, dir_index) */
    618   bb_u8(line, 2);
    619   bb_uleb(line, DW_LNCT_path);
    620   bb_uleb(line, DW_FORM_line_strp);
    621   bb_uleb(line, DW_LNCT_directory_index);
    622   bb_uleb(line, DW_FORM_udata);
    623   /* file_names_count = 1 */
    624   bb_uleb(line, 1);
    625   bb_u32(line, (uint32_t)ls_file);
    626   bb_uleb(line, 0); /* dir_index = 0 (the only dir) */
    627   /* Patch header_length = bytes from after header_length field to start
    628    * of program. Program starts now. */
    629   {
    630     uint32_t hl = (uint32_t)(line->len - header_len_start);
    631     line->data[hdr_len_pos + 0] = (uint8_t)hl;
    632     line->data[hdr_len_pos + 1] = (uint8_t)(hl >> 8);
    633     line->data[hdr_len_pos + 2] = (uint8_t)(hl >> 16);
    634     line->data[hdr_len_pos + 3] = (uint8_t)(hl >> 24);
    635   }
    636   /* Program */
    637   /* DW_LNE_set_address func_low */
    638   bb_u8(line, 0);
    639   bb_uleb(line, 9); /* length: opcode + 8 addr bytes */
    640   bb_u8(line, DW_LNE_set_address);
    641   bb_u64(line, func_low);
    642   /* DW_LNS_set_file 0 — DW5 file 0 is the CU primary (we only have one
    643    * file in the table, indexed at 0). */
    644   bb_u8(line, 4 /* DW_LNS_set_file */);
    645   bb_uleb(line, 0);
    646   /* DW_LNS_advance_line +9 (default line is 1 → 10) */
    647   bb_u8(line, DW_LNS_advance_line);
    648   bb_sleb(line, 9);
    649   /* DW_LNS_copy → row at (file=0/1?, line=10, addr=func_low). DW5 file 0
    650    * is the CU primary; default file = 1 in standard, but DW5 line program
    651    * starts with file=1. We'll match either since file_norm[1] equals
    652    * file_norm[0] in our setup if we have one file. With nfiles_count=1,
    653    * file 1 maps to the 0th entry. */
    654   bb_u8(line, DW_LNS_copy);
    655   /* DW_LNS_advance_pc 1 (* min_inst_len 4 = 4 bytes) */
    656   bb_u8(line, DW_LNS_advance_pc);
    657   bb_uleb(line, 1);
    658   /* advance_line +1 → line 11 */
    659   bb_u8(line, DW_LNS_advance_line);
    660   bb_sleb(line, 1);
    661   bb_u8(line, DW_LNS_copy);
    662   /* end_sequence */
    663   bb_u8(line, 0);
    664   bb_uleb(line, 1);
    665   bb_u8(line, DW_LNE_end_sequence);
    666   /* Patch unit_length */
    667   {
    668     uint32_t total = (uint32_t)(line->len - line_body_start);
    669     line->data[line_len_pos + 0] = (uint8_t)total;
    670     line->data[line_len_pos + 1] = (uint8_t)(total >> 8);
    671     line->data[line_len_pos + 2] = (uint8_t)(total >> 16);
    672     line->data[line_len_pos + 3] = (uint8_t)(total >> 24);
    673   }
    674 }
    675 
    676 /* ---- structural enumeration (objdump --dwarf) ----------------------- */
    677 
    678 static void run_dump_tests(KitDebugInfo* di) {
    679   /* CU iterator: the fixture has exactly one DWARF5 CU at offset 0. */
    680   uint32_t root_off = 0;
    681   int have_root = 0;
    682   {
    683     KitDwarfCuIter* it = NULL;
    684     KitDwarfCu cu;
    685     int ncu = 0;
    686     EXPECT(kit_dwarf_cu_iter_new(di, &it) == KIT_OK, "cu_iter_new failed");
    687     while (kit_dwarf_cu_iter_next(it, &cu) == KIT_ITER_ITEM) {
    688       if (ncu == 0) {
    689         EXPECT(cu.offset == 0, "cu offset expected 0, got 0x%x", cu.offset);
    690         EXPECT(cu.version == 5, "cu version expected 5, got %u",
    691                (unsigned)cu.version);
    692         EXPECT(cu.address_size == 8, "cu addr_size expected 8, got %u",
    693                (unsigned)cu.address_size);
    694       }
    695       ncu++;
    696     }
    697     EXPECT(ncu == 1, "expected 1 CU, got %d", ncu);
    698     kit_dwarf_cu_iter_free(it);
    699   }
    700 
    701   /* DIE iterator: first DIE is the CU root (depth 0, compile_unit). We
    702    * should also encounter a subprogram and a base_type along the way. */
    703   {
    704     KitDwarfDieIter* it = NULL;
    705     KitDwarfDie die;
    706     int ndie = 0, saw_subprog = 0, saw_base = 0;
    707     EXPECT(kit_dwarf_die_iter_new(di, &it) == KIT_OK, "die_iter_new failed");
    708     while (kit_dwarf_die_iter_next(it, &die) == KIT_ITER_ITEM) {
    709       if (ndie == 0) {
    710         EXPECT(die.depth == 0, "root die depth expected 0, got %u", die.depth);
    711         EXPECT(die.tag == DW_TAG_compile_unit,
    712                "root die tag expected compile_unit (0x11), got 0x%x", die.tag);
    713         root_off = die.offset;
    714         have_root = 1;
    715       }
    716       if (die.tag == DW_TAG_subprogram) saw_subprog = 1;
    717       if (die.tag == DW_TAG_base_type) saw_base = 1;
    718       ndie++;
    719     }
    720     EXPECT(ndie >= 4, "expected several DIEs, got %d", ndie);
    721     EXPECT(saw_subprog, "expected a DW_TAG_subprogram DIE");
    722     EXPECT(saw_base, "expected a DW_TAG_base_type DIE");
    723     kit_dwarf_die_iter_free(it);
    724   }
    725 
    726   /* Attribute iterator on the CU root: the fixture's compile_unit carries
    727    * DW_AT_low_pc == 0x1000 and a string-valued DW_AT_name. */
    728   if (have_root) {
    729     KitDwarfAttrIter* it = NULL;
    730     KitDwarfAttr a;
    731     int saw_low_pc = 0, saw_string = 0;
    732     EXPECT(kit_dwarf_attr_iter_new(di, root_off, &it) == KIT_OK,
    733            "attr_iter_new failed");
    734     while (kit_dwarf_attr_iter_next(it, &a) == KIT_ITER_ITEM) {
    735       if (a.attr == DW_AT_low_pc) {
    736         saw_low_pc = 1;
    737         EXPECT(a.u == 0x1000, "root low_pc expected 0x1000, got 0x%llx",
    738                (unsigned long long)a.u);
    739       }
    740       if (a.form_class == KIT_DWARF_FC_STRING && a.str.len) saw_string = 1;
    741     }
    742     EXPECT(saw_low_pc, "root DIE missing DW_AT_low_pc");
    743     EXPECT(saw_string, "root DIE missing a string-valued attribute");
    744     kit_dwarf_attr_iter_free(it);
    745   }
    746 
    747   /* Abbrev iterator + per-abbrev attribute specs. */
    748   {
    749     KitDwarfAbbrevIter* it = NULL;
    750     KitDwarfAbbrev ab;
    751     int nabbrev = 0, saw_cu_abbrev = 0;
    752     uint32_t cu_code_tbl = 0;
    753     uint64_t cu_code = 0;
    754     EXPECT(kit_dwarf_abbrev_iter_new(di, &it) == KIT_OK,
    755            "abbrev_iter_new failed");
    756     while (kit_dwarf_abbrev_iter_next(it, &ab) == KIT_ITER_ITEM) {
    757       if (ab.tag == DW_TAG_compile_unit) {
    758         saw_cu_abbrev = 1;
    759         cu_code_tbl = ab.table_offset;
    760         cu_code = ab.code;
    761         EXPECT(ab.has_children, "compile_unit abbrev should have children");
    762       }
    763       nabbrev++;
    764     }
    765     EXPECT(nabbrev >= 5, "expected several abbrevs, got %d", nabbrev);
    766     EXPECT(saw_cu_abbrev, "expected a compile_unit abbrev");
    767     kit_dwarf_abbrev_iter_free(it);
    768 
    769     if (saw_cu_abbrev) {
    770       KitDwarfAbbrevAttrIter* ait = NULL;
    771       KitDwarfAbbrevAttr aa;
    772       int saw_name_spec = 0;
    773       EXPECT(kit_dwarf_abbrev_attr_iter_new(di, cu_code_tbl, cu_code, &ait) ==
    774                  KIT_OK,
    775              "abbrev_attr_iter_new failed");
    776       while (kit_dwarf_abbrev_attr_iter_next(ait, &aa) == KIT_ITER_ITEM) {
    777         if (aa.attr == DW_AT_name) saw_name_spec = 1;
    778       }
    779       EXPECT(saw_name_spec, "compile_unit abbrev missing DW_AT_name spec");
    780       kit_dwarf_abbrev_attr_iter_free(ait);
    781     }
    782   }
    783 
    784   /* Line iterator: a row at address 0x1000 (line 10), and its file_index
    785    * resolves to a path containing test.c. */
    786   {
    787     KitDwarfLineIter* it = NULL;
    788     KitDwarfLineRow row;
    789     int saw_1000 = 0;
    790     uint32_t file_at_1000 = 0;
    791     EXPECT(kit_dwarf_line_iter_new(di, 0, &it) == KIT_OK,
    792            "line_iter_new failed");
    793     while (kit_dwarf_line_iter_next(it, &row) == KIT_ITER_ITEM) {
    794       if (row.address == 0x1000 && !row.end_sequence) {
    795         saw_1000 = 1;
    796         file_at_1000 = row.file_index;
    797         EXPECT(row.line == 10, "row at 0x1000 expected line 10, got %u",
    798                row.line);
    799       }
    800     }
    801     EXPECT(saw_1000, "expected a line row at address 0x1000");
    802     kit_dwarf_line_iter_free(it);
    803 
    804     if (saw_1000) {
    805       KitSlice path = KIT_SLICE_NULL;
    806       EXPECT(kit_dwarf_line_file(di, 0, file_at_1000, &path) == KIT_OK,
    807              "line_file lookup failed");
    808       EXPECT(path.s && strstr(path.s, "test.c") != NULL,
    809              "line file should contain test.c, got %.*s", KIT_SLICE_ARG(path));
    810     }
    811   }
    812 
    813   /* .debug_str iterator: the fixture interns these known strings. */
    814   {
    815     KitDwarfStrIter* it = NULL;
    816     KitDwarfStr s;
    817     int saw_int = 0, saw_point = 0, saw_testc = 0;
    818     EXPECT(kit_dwarf_str_iter_new(di, &it) == KIT_OK, "str_iter_new failed");
    819     while (kit_dwarf_str_iter_next(it, &s) == KIT_ITER_ITEM) {
    820       if (kit_slice_eq_cstr(s.str, "int")) saw_int = 1;
    821       if (kit_slice_eq_cstr(s.str, "Point")) saw_point = 1;
    822       if (kit_slice_eq_cstr(s.str, "test.c")) saw_testc = 1;
    823     }
    824     EXPECT(saw_int && saw_point && saw_testc,
    825            "expected int/Point/test.c in .debug_str (%d/%d/%d)", saw_int,
    826            saw_point, saw_testc);
    827     kit_dwarf_str_iter_free(it);
    828   }
    829 }
    830 
    831 /* ---- main ----------------------------------------------------------- */
    832 
    833 static void run_tests(KitDebugInfo* di) {
    834   /* 1. addr_to_line at func_low. */
    835   KitSlice file = KIT_SLICE_NULL;
    836   uint32_t line = 0, col = 0;
    837   if (kit_dwarf_addr_to_line(di, 0x1000, &file, &line, &col) == 0) {
    838     EXPECT(line == 10, "expected line 10 at 0x1000, got %u (file=%.*s)", line,
    839            KIT_SLICE_ARG(file));
    840     EXPECT(file.s && strstr(file.s, "test.c") != NULL,
    841            "file should contain test.c, got %.*s", KIT_SLICE_ARG(file));
    842   } else {
    843     g_u.fails++;
    844     fprintf(stderr, "FAIL: addr_to_line(0x1000) returned no entry\n");
    845   }
    846   /* 2. line_to_addr round trip. */
    847   uint64_t pc = 0;
    848   if (kit_dwarf_line_to_addr(di, KIT_SLICE_LIT("/proj/test.c"), 10, &pc) == 0) {
    849     EXPECT(pc == 0x1000, "expected pc 0x1000 for /proj/test.c:10, got 0x%llx",
    850            (unsigned long long)pc);
    851   } else {
    852     fprintf(stderr,
    853             "NOTE: line_to_addr looked up by absolute path failed; "
    854             "trying relative\n");
    855     if (kit_dwarf_line_to_addr(di, KIT_SLICE_LIT("test.c"), 10, &pc) == 0) {
    856       EXPECT(pc == 0x1000, "expected pc 0x1000 for test.c:10, got 0x%llx",
    857              (unsigned long long)pc);
    858     } else {
    859       g_u.fails++;
    860       fprintf(stderr, "FAIL: line_to_addr could not find any test.c:10\n");
    861     }
    862   }
    863   /* 3. subprogram_at. */
    864   KitDwarfSubprogram sp;
    865   EXPECT(kit_dwarf_subprogram_at(di, 0x1000, &sp) == 0,
    866          "subprogram_at(0x1000) should succeed");
    867   if (sp.name.s) {
    868     EXPECT(kit_slice_eq_cstr(sp.name, "test_main"),
    869            "subprogram name '%.*s' != test_main", KIT_SLICE_ARG(sp.name));
    870   }
    871   EXPECT(sp.high_pc > sp.low_pc, "subprogram pc range empty");
    872 
    873   /* 4. var_at "x" should be FRAME_OFS. */
    874   KitDwarfVarLoc loc;
    875   EXPECT(kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("x"), &loc) == 0,
    876          "var_at(0x1000, x) failed");
    877   if (g_u.fails == 0) {
    878     EXPECT(loc.kind == KIT_DLOC_FRAME_OFS, "expected x.kind=FRAME_OFS, got %d",
    879            (int)loc.kind);
    880     if (loc.kind == KIT_DLOC_FRAME_OFS) {
    881       EXPECT(loc.v.frame_ofs == -16, "expected frame_ofs=-16, got %d",
    882              loc.v.frame_ofs);
    883     }
    884     EXPECT(loc.byte_size == 4, "expected byte_size=4, got %u", loc.byte_size);
    885   }
    886 
    887   /* 5. var_at "arg" (param) should be REG. */
    888   EXPECT(kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("arg"), &loc) == 0,
    889          "var_at(0x1000, arg) failed");
    890   EXPECT(loc.kind == KIT_DLOC_REG, "expected arg.kind=REG, got %d",
    891          (int)loc.kind);
    892   if (loc.kind == KIT_DLOC_REG) {
    893     EXPECT(loc.v.reg == 0, "expected reg=0, got %u", loc.v.reg);
    894   }
    895 
    896   /* 6. type_info on int. */
    897   if (loc.type) {
    898     KitDwarfTypeInfo ti = kit_dwarf_type_info(loc.type);
    899     EXPECT(ti.kind == KIT_DT_SINT, "expected SINT, got kind=%d", (int)ti.kind);
    900     EXPECT(ti.byte_size == 4, "expected byte_size=4, got %u", ti.byte_size);
    901     EXPECT(kit_slice_eq_cstr(ti.name, "int"), "expected name=int, got %.*s",
    902            KIT_SLICE_ARG(ti.name));
    903   }
    904 
    905   /* 7. param_iter — should yield arg. */
    906   KitDwarfParamIter* pi = NULL;
    907   EXPECT(kit_dwarf_param_iter_new(di, 0x1000, &pi) == KIT_OK && pi != NULL,
    908          "param_iter_new failed");
    909   if (pi) {
    910     KitDwarfVar v;
    911     int n = 0;
    912     for (;;) {
    913       KitIterResult r = kit_dwarf_param_iter_next(pi, &v);
    914       if (r != KIT_ITER_ITEM) break;
    915       n++;
    916       EXPECT(kit_slice_eq_cstr(v.name, "arg"), "param name %.*s != arg",
    917              KIT_SLICE_ARG(v.name));
    918     }
    919     EXPECT(n == 1, "expected 1 param, got %d", n);
    920     kit_dwarf_param_iter_free(pi);
    921   }
    922 
    923   /* 8. addr_to_line at second row (0x1004) → line 11. */
    924   {
    925     KitSlice f2 = KIT_SLICE_NULL;
    926     uint32_t l2 = 0, c2 = 0;
    927     if (kit_dwarf_addr_to_line(di, 0x1004, &f2, &l2, &c2) == 0) {
    928       EXPECT(l2 == 11, "expected line 11 at 0x1004, got %u", l2);
    929     } else {
    930       g_u.fails++;
    931       fprintf(stderr, "FAIL: addr_to_line(0x1004) failed\n");
    932     }
    933   }
    934 
    935   /* 9. vars_at_new — yields x, y as locals plus arg as ARG. */
    936   {
    937     uint32_t mask = (1u << KIT_DVR_LOCAL) | (1u << KIT_DVR_ARG);
    938     KitDwarfVarIter* vi = NULL;
    939     int n_local = 0, n_arg = 0, saw_x = 0, saw_y = 0, saw_arg = 0;
    940     EXPECT(kit_dwarf_vars_at_new(di, 0x1000, mask, &vi) == KIT_OK && vi != NULL,
    941            "vars_at_new failed");
    942     if (vi) {
    943       KitDwarfVar v;
    944       for (;;) {
    945         KitIterResult r = kit_dwarf_vars_at_next(vi, &v);
    946         if (r != KIT_ITER_ITEM) break;
    947         if (v.role == KIT_DVR_LOCAL) {
    948           n_local++;
    949           if (kit_slice_eq_cstr(v.name, "x")) saw_x = 1;
    950           if (kit_slice_eq_cstr(v.name, "y")) saw_y = 1;
    951         } else if (v.role == KIT_DVR_ARG) {
    952           n_arg++;
    953           if (kit_slice_eq_cstr(v.name, "arg")) saw_arg = 1;
    954         }
    955       }
    956       /* The fixture has 6 locals total (x, y, my_int, Point, x_field,
    957        * y_field). We only assert that x and y are among them. */
    958       EXPECT(n_local >= 2 && saw_x && saw_y,
    959              "expected >=2 locals incl x,y, got %d", n_local);
    960       EXPECT(n_arg == 1 && saw_arg, "expected 1 arg (arg), got %d", n_arg);
    961       kit_dwarf_vars_at_free(vi);
    962     }
    963   }
    964 
    965   /* 10. loc_read REG fast path: pull arg via a fake unwind frame. */
    966   {
    967     KitDwarfVarLoc varg;
    968     if (kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("arg"), &varg) == KIT_OK) {
    969       KitUnwindFrame fr;
    970       uint32_t v32 = 0;
    971       size_t got = 0;
    972       memset(&fr, 0, sizeof fr);
    973       fr.regs[0] = 0xdeadbeefULL;
    974       fr.cfa = 0x7000;
    975       fr.pc = 0x1000;
    976       EXPECT(kit_dwarf_loc_read(di, &varg, &fr, NULL, NULL, &v32, sizeof v32,
    977                                 &got) == KIT_OK,
    978              "loc_read REG failed");
    979       EXPECT(got >= sizeof v32 && v32 == 0xdeadbeefU,
    980              "REG read got %u bytes, val 0x%x", (unsigned)got, v32);
    981     }
    982   }
    983 
    984   /* 11. type_info: pointer (var "my_int" carries pointer_type → int). */
    985   {
    986     KitDwarfVarLoc lp;
    987     if (kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("my_int"), &lp) == 0) {
    988       KitDwarfTypeInfo ti = kit_dwarf_type_info(lp.type);
    989       EXPECT(ti.kind == KIT_DT_PTR, "expected PTR, got kind=%d", (int)ti.kind);
    990       EXPECT(ti.byte_size == 8, "expected ptr byte_size=8, got %u",
    991              ti.byte_size);
    992       if (ti.inner) {
    993         KitDwarfTypeInfo it = kit_dwarf_type_info(ti.inner);
    994         EXPECT(it.kind == KIT_DT_SINT, "ptr inner kind != SINT (%d)",
    995                (int)it.kind);
    996       }
    997     } else {
    998       g_u.fails++;
    999       fprintf(stderr, "FAIL: var_at(my_int) returned nothing\n");
   1000     }
   1001   }
   1002 
   1003   /* 12. type_info: typedef (var "Point" carries typedef → int). */
   1004   {
   1005     KitDwarfVarLoc lp;
   1006     if (kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("Point"), &lp) == 0) {
   1007       KitDwarfTypeInfo ti = kit_dwarf_type_info(lp.type);
   1008       EXPECT(ti.kind == KIT_DT_TYPEDEF, "expected TYPEDEF, got kind=%d",
   1009              (int)ti.kind);
   1010       EXPECT(kit_slice_eq_cstr(ti.name, "my_int"),
   1011              "typedef name=%.*s != my_int", KIT_SLICE_ARG(ti.name));
   1012       EXPECT(ti.inner != NULL, "typedef inner missing");
   1013     }
   1014   }
   1015 
   1016   /* 13. type_info: array of int [4]. */
   1017   {
   1018     KitDwarfVarLoc lp;
   1019     if (kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("x_field"), &lp) == 0) {
   1020       KitDwarfTypeInfo ti = kit_dwarf_type_info(lp.type);
   1021       EXPECT(ti.kind == KIT_DT_ARRAY, "expected ARRAY, got kind=%d",
   1022              (int)ti.kind);
   1023       EXPECT(ti.element_count == 4, "expected ec=4, got %u", ti.element_count);
   1024     }
   1025   }
   1026 
   1027   /* 14. type_info: struct Point with two int fields. */
   1028   {
   1029     KitDwarfVarLoc lp;
   1030     if (kit_dwarf_var_at(di, 0x1000, KIT_SLICE_LIT("y_field"), &lp) == 0) {
   1031       KitDwarfTypeInfo ti = kit_dwarf_type_info(lp.type);
   1032       EXPECT(ti.kind == KIT_DT_STRUCT, "expected STRUCT, got kind=%d",
   1033              (int)ti.kind);
   1034       EXPECT(ti.byte_size == 8, "struct byte_size=%u", ti.byte_size);
   1035       EXPECT(kit_slice_eq_cstr(ti.name, "Point"), "struct name=%.*s",
   1036              KIT_SLICE_ARG(ti.name));
   1037       KitDwarfFieldIter* fi = NULL;
   1038       EXPECT(kit_dwarf_field_iter_new(di, lp.type, &fi) == KIT_OK && fi != NULL,
   1039              "field_iter_new failed");
   1040       if (fi) {
   1041         KitDwarfField f;
   1042         int count = 0;
   1043         int saw_x = 0, saw_y = 0;
   1044         for (;;) {
   1045           KitIterResult r = kit_dwarf_field_iter_next(fi, &f);
   1046           if (r != KIT_ITER_ITEM) break;
   1047           count++;
   1048           if (kit_slice_eq_cstr(f.name, "x_field") && f.byte_offset == 0)
   1049             saw_x = 1;
   1050           if (kit_slice_eq_cstr(f.name, "y_field") && f.byte_offset == 4)
   1051             saw_y = 1;
   1052         }
   1053         EXPECT(count == 2, "expected 2 fields, got %d", count);
   1054         EXPECT(saw_x && saw_y, "missing x_field or y_field");
   1055         kit_dwarf_field_iter_free(fi);
   1056       }
   1057     }
   1058   }
   1059 }
   1060 
   1061 int main(void) {
   1062   KitTargetSpec target;
   1063   kit_unit_init(&g_u);
   1064   g_u.ctx.now = -1;
   1065   target = kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
   1066 
   1067   KitCompiler* cc = NULL;
   1068   if (kit_unit_compiler_new(&g_u, target, &cc) != KIT_OK || !cc) {
   1069     fprintf(stderr, "compiler_new failed\n");
   1070     return 1;
   1071   }
   1072 
   1073   /* Build .debug_* byte buffers. */
   1074   ByteBuf abbrev, info, line, str, line_str;
   1075   bb_init(&abbrev);
   1076   bb_init(&info);
   1077   bb_init(&line);
   1078   bb_init(&str);
   1079   bb_init(&line_str);
   1080   /* Reserve initial 0 in str/line_str so offset 0 is a valid empty
   1081    * string. */
   1082   bb_u8(&str, 0);
   1083   bb_u8(&line_str, 0);
   1084   DieOffsets die_offs = {0};
   1085   build_debug_sections(&abbrev, &info, &line, &str, &line_str, 0x1000, 8,
   1086                        &die_offs);
   1087   (void)die_offs;
   1088 
   1089   /* Build an ObjBuilder via internal API. */
   1090   ObjBuilder* ob = obj_new(cc);
   1091   Sym text_name = pool_intern_slice(cc->global, SLICE_LIT(".text"));
   1092   Sym func_name = pool_intern_slice(cc->global, SLICE_LIT("test_main"));
   1093   ObjSecId text_sec =
   1094       obj_section(ob, text_name, SEC_TEXT, SF_EXEC | SF_ALLOC, 4);
   1095   /* 8 bytes of nop-like text. */
   1096   uint8_t text_bytes[8] = {0};
   1097   obj_write(ob, text_sec, text_bytes, 8);
   1098   obj_symbol(ob, func_name, SB_GLOBAL, SK_FUNC, text_sec, 0, 8);
   1099 
   1100   Sym n_abbrev = pool_intern_slice(cc->global, SLICE_LIT(".debug_abbrev"));
   1101   Sym n_info = pool_intern_slice(cc->global, SLICE_LIT(".debug_info"));
   1102   Sym n_line = pool_intern_slice(cc->global, SLICE_LIT(".debug_line"));
   1103   Sym n_str = pool_intern_slice(cc->global, SLICE_LIT(".debug_str"));
   1104   Sym n_line_str = pool_intern_slice(cc->global, SLICE_LIT(".debug_line_str"));
   1105   ObjSecId s_abbrev = obj_section(ob, n_abbrev, SEC_DEBUG, 0, 1);
   1106   ObjSecId s_info = obj_section(ob, n_info, SEC_DEBUG, 0, 1);
   1107   ObjSecId s_line = obj_section(ob, n_line, SEC_DEBUG, 0, 1);
   1108   ObjSecId s_str = obj_section(ob, n_str, SEC_DEBUG, 0, 1);
   1109   ObjSecId s_line_str = obj_section(ob, n_line_str, SEC_DEBUG, 0, 1);
   1110   obj_write(ob, s_abbrev, abbrev.data, abbrev.len);
   1111   obj_write(ob, s_info, info.data, info.len);
   1112   obj_write(ob, s_line, line.data, line.len);
   1113   obj_write(ob, s_str, str.data, str.len);
   1114   obj_write(ob, s_line_str, line_str.data, line_str.len);
   1115   obj_finalize(ob);
   1116 
   1117   /* Emit ELF to memory. */
   1118   KitWriter* w = NULL;
   1119   (void)kit_writer_mem(&g_u.heap, &w);
   1120   emit_elf(cc, ob, w);
   1121   size_t obj_len = 0;
   1122   const uint8_t* obj_bytes = kit_writer_mem_bytes(w, &obj_len);
   1123   fprintf(stderr, "built obj: %zu bytes\n", obj_len);
   1124 
   1125   /* Optional: dump the synthesized DWARF-bearing ELF to a file so the
   1126    * objdump driver test has a real fixture (cc -g is the usual source, but
   1127    * this keeps the fixture reproducible from a known byte layout). */
   1128   {
   1129     const char* dump = getenv("KIT_DWARF_WRITE_FIXTURE");
   1130     if (dump && obj_bytes) {
   1131       FILE* fp = fopen(dump, "wb");
   1132       if (fp) {
   1133         fwrite(obj_bytes, 1, obj_len, fp);
   1134         fclose(fp);
   1135         fprintf(stderr, "wrote fixture: %s\n", dump);
   1136       }
   1137     }
   1138   }
   1139 
   1140   /* Re-open via the public API. */
   1141   KitSlice in;
   1142   memset(&in, 0, sizeof in);
   1143   in.data = obj_bytes;
   1144   in.len = obj_len;
   1145   KitObjFile* obj = NULL;
   1146   EXPECT(kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("test.o"), &in, &obj) == KIT_OK &&
   1147              obj != NULL,
   1148          "kit_obj_open failed");
   1149   if (obj) {
   1150     KitDebugInfo* di = NULL;
   1151     EXPECT(kit_dwarf_open(&g_u.ctx, obj, &di) == KIT_OK && di != NULL,
   1152            "kit_dwarf_open failed");
   1153     if (di) {
   1154       run_tests(di);
   1155       run_dump_tests(di);
   1156       kit_dwarf_free(di);
   1157     }
   1158     kit_obj_free(obj);
   1159   }
   1160 
   1161   if (w->close) w->close(w);
   1162   obj_free(ob);
   1163 
   1164   free(abbrev.data);
   1165   free(info.data);
   1166   free(line.data);
   1167   free(str.data);
   1168   free(line_str.data);
   1169 
   1170   kit_compiler_free(cc);
   1171 
   1172   if (g_u.fails) {
   1173     fprintf(stderr, "%d failure(s)\n", g_u.fails);
   1174     return 1;
   1175   }
   1176   printf("OK\n");
   1177   return 0;
   1178 }