kit

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

commit 888738afcfa1f9f65a1d03938960314974891ea7
parent fcb3b729a797778a0eb41257685491eaaf760cf4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 16:03:14 -0700

debug: DWARF for globals

Diffstat:
Msrc/debug/debug.c | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/debug/debug.h | 3+++
Msrc/debug/debug_emit.c | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/debug/debug_internal.h | 11++++++++++-
Msrc/debug/dwarf_query.c | 6++++--
Mtest/api/cg_type_test.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 233 insertions(+), 25 deletions(-)

diff --git a/src/debug/debug.c b/src/debug/debug.c @@ -75,6 +75,8 @@ void debug_free(Debug* d) { for (i = 0; i < d->nfuncs; ++i) func_free(d, &d->funcs[i]); if (d->funcs) d->heap->free(d->heap, d->funcs, sizeof(*d->funcs) * d->funcs_cap); + if (d->globals) + d->heap->free(d->heap, d->globals, sizeof(*d->globals) * d->globals_cap); for (i = 0; i < d->ntypes; ++i) type_free(d, &d->types[i]); if (d->types) d->heap->free(d->heap, d->types, sizeof(*d->types) * d->types_cap); @@ -232,11 +234,21 @@ DebugTypeId debug_type_func(Debug* d, DebugTypeId ret, DebugTypeBuilder* debug_type_record_begin(Debug* d, Sym tag, int is_union, u32 byte_size, u32 align) { + DebugTypeId id; + DebugType* t; DebugTypeBuilder* b = (DebugTypeBuilder*)d->heap->alloc( d->heap, sizeof(*b), _Alignof(DebugTypeBuilder)); if (!b) debug_oom(d, "rec builder"); memset(b, 0, sizeof(*b)); + id = type_alloc(d); + t = debug_type_at(d, id); + t->kind = DTK_RECORD; + t->is_union = (u8)(is_union ? 1 : 0); + t->name = tag; + t->byte_size = byte_size; + t->align = align; b->d = d; + b->id = id; b->is_union = (u8)(is_union ? 1 : 0); b->tag = tag; b->byte_size = byte_size; @@ -244,6 +256,10 @@ DebugTypeBuilder* debug_type_record_begin(Debug* d, Sym tag, int is_union, return b; } +DebugTypeId debug_type_record_id(DebugTypeBuilder* b) { + return b ? b->id : DEBUG_TYPE_NONE; +} + void debug_type_record_field(DebugTypeBuilder* b, Sym name, DebugTypeId type, u32 byte_offset) { DebugRecField* f; @@ -273,7 +289,7 @@ void debug_type_record_bitfield(DebugTypeBuilder* b, Sym name, DebugTypeId type, DebugTypeId debug_type_record_end(DebugTypeBuilder* b) { Debug* d = b->d; - DebugTypeId id = type_alloc(d); + DebugTypeId id = b->id; DebugType* t = debug_type_at(d, id); t->kind = DTK_RECORD; t->is_union = b->is_union; @@ -470,6 +486,40 @@ void debug_local(Debug* d, Sym name, DebugTypeId type, SrcLoc loc, v->die_offset = 0; } +void debug_global(Debug* d, Sym name, DebugTypeId type, SrcLoc loc, + DebugVarLoc vloc, int external) { + DebugVarDIE* v; + u32 i; + if (!d || vloc.kind != DVL_GLOBAL) return; + for (i = 0; i < d->nglobals; ++i) { + v = &d->globals[i]; + if (v->loc.kind == DVL_GLOBAL && v->loc.v.global == vloc.v.global) { + v->is_param = 0; + v->external = (u8)(external ? 1 : 0); + v->param_idx = 0; + v->name = name; + v->type = type; + v->decl = loc; + v->loc = vloc; + v->scope_idx = -1; + v->die_offset = 0; + return; + } + } + if (VEC_GROW(d->heap, d->globals, d->globals_cap, d->nglobals + 1)) + debug_oom(d, "globals"); + v = &d->globals[d->nglobals++]; + v->is_param = 0; + v->external = (u8)(external ? 1 : 0); + v->param_idx = 0; + v->name = name; + v->type = type; + v->decl = loc; + v->loc = vloc; + v->scope_idx = -1; + v->die_offset = 0; +} + /* ---- line program input ---- */ void debug_set_pending_loc(Debug* d, SrcLoc loc) { diff --git a/src/debug/debug.h b/src/debug/debug.h @@ -77,6 +77,7 @@ DebugTypeId debug_type_func(Debug*, DebugTypeId ret, const DebugTypeId* params, typedef struct DebugTypeBuilder DebugTypeBuilder; DebugTypeBuilder* debug_type_record_begin(Debug*, Sym tag, int is_union, u32 byte_size, u32 align); +DebugTypeId debug_type_record_id(DebugTypeBuilder*); void debug_type_record_field(DebugTypeBuilder*, Sym name, DebugTypeId type, u32 byte_offset); void debug_type_record_bitfield(DebugTypeBuilder*, Sym name, DebugTypeId type, @@ -122,6 +123,8 @@ typedef struct DebugVarLoc { void debug_param(Debug*, Sym name, DebugTypeId, SrcLoc, u32 idx, DebugVarLoc); void debug_local(Debug*, Sym name, DebugTypeId, SrcLoc, DebugVarLoc); +void debug_global(Debug*, Sym name, DebugTypeId, SrcLoc, DebugVarLoc, + int external); /* line program */ void debug_line(Debug*, ObjSecId text_section_id, u32 text_offset, SrcLoc, diff --git a/src/debug/debug_emit.c b/src/debug/debug_emit.c @@ -8,8 +8,8 @@ * - DW_FORM_strx4 used uniformly for string refs from .debug_info. * - DW_FORM_line_strp for line program file/dir paths. * - DW_FORM_ref4 for intra-CU DIE refs (CU-relative offset). - * - DW_AT_low_pc encoded as DW_FORM_addr with R_ABS64 reloc against the - * function symbol; DW_AT_high_pc is DW_FORM_data4 holding func size. + * - DW_AT_low_pc encoded as DW_FORM_addr with an address-width reloc against + * the function symbol; DW_AT_high_pc is DW_FORM_data4 holding func size. * - DW_AT_frame_base is exprloc { DW_OP_call_frame_cfa }. * - Abbrev codes are assigned in first-use order, starting at 1. * - File 0 in .debug_line is the CU primary file (DW5 convention). */ @@ -131,11 +131,13 @@ typedef struct EmitCtx { u32 abbr_struct; u32 abbr_union; u32 abbr_member; + u32 abbr_member_bitfield; u32 abbr_enum; u32 abbr_enum_val; u32 abbr_subprogram; u32 abbr_param; u32 abbr_var; + u32 abbr_var_external; u32 abbr_lexical_block; /* CU body (post-CU-header DIE bytes). */ @@ -228,6 +230,10 @@ static void add_addr_reloc(EmitCtx* e, AddrReloc** arr, u32* n, u32* cap, r->section = OBJ_SEC_NONE; } +static RelocKind debug_addr_reloc_kind(const Debug* d) { + return d && d->c && d->c->target.ptr_size == 4 ? R_ABS32 : R_ABS64; +} + /* ---------------------------------------------------------------- */ /* String emit shortcuts. */ @@ -339,6 +345,15 @@ static void resolve_abbrevs(EmitCtx* e) { { DebugAbbrevAttr a[] = {{DW_AT_name, DW_FORM_strx4, 0}, {DW_AT_type, DW_FORM_ref4, 0}, + {DW_AT_data_member_location, DW_FORM_udata, 0}, + {DW_AT_data_bit_offset, DW_FORM_udata, 0}, + {DW_AT_bit_size, DW_FORM_udata, 0}}; + e->abbr_member_bitfield = + abbr_intern(e, DW_TAG_member, DW_CHILDREN_no, a, 5); + } + { + DebugAbbrevAttr a[] = {{DW_AT_name, DW_FORM_strx4, 0}, + {DW_AT_type, DW_FORM_ref4, 0}, {DW_AT_byte_size, DW_FORM_udata, 0}}; e->abbr_enum = abbr_intern(e, DW_TAG_enumeration_type, DW_CHILDREN_yes, a, 3); @@ -374,6 +389,16 @@ static void resolve_abbrevs(EmitCtx* e) { e->abbr_var = abbr_intern(e, DW_TAG_variable, DW_CHILDREN_no, a, 5); } { + DebugAbbrevAttr a[] = {{DW_AT_external, DW_FORM_flag_present, 0}, + {DW_AT_name, DW_FORM_strx4, 0}, + {DW_AT_decl_file, DW_FORM_udata, 0}, + {DW_AT_decl_line, DW_FORM_udata, 0}, + {DW_AT_type, DW_FORM_ref4, 0}, + {DW_AT_location, DW_FORM_exprloc, 0}}; + e->abbr_var_external = + abbr_intern(e, DW_TAG_variable, DW_CHILDREN_no, a, 6); + } + { e->abbr_lexical_block = abbr_intern(e, DW_TAG_lexical_block, DW_CHILDREN_yes, NULL, 0); } @@ -493,10 +518,15 @@ static void emit_type_die(EmitCtx* e, DebugTypeId id) { form_uleb(&e->info_body, t->byte_size); for (i = 0; i < t->nfields; ++i) { DebugRecField* f = &t->fields[i]; - form_uleb(&e->info_body, e->abbr_member); + form_uleb(&e->info_body, + f->bit_width ? e->abbr_member_bitfield : e->abbr_member); emit_strx4(e, &e->info_body, f->name); emit_type_ref(e, f->type); form_uleb(&e->info_body, f->byte_offset); + if (f->bit_width) { + form_uleb(&e->info_body, f->bit_offset); + form_uleb(&e->info_body, f->bit_width); + } } form_uleb(&e->info_body, 0); return; @@ -529,6 +559,7 @@ static void emit_type_die(EmitCtx* e, DebugTypeId id) { static void emit_var_loc_exprloc(EmitCtx* e, Buf* b, DebugVarLoc loc) { u8 expr[32]; u32 n = 0; + u32 addr_payload_off = UINT32_MAX; switch ((DebugVarLocKind)loc.kind) { case DVL_REG: if (loc.v.reg < 32) { @@ -543,27 +574,30 @@ static void emit_var_loc_exprloc(EmitCtx* e, Buf* b, DebugVarLoc loc) { n += form_sleb_inline(&expr[n], loc.v.frame_ofs); break; case DVL_GLOBAL: { - /* DW_OP_addr <ptr_size>: relocation against the symbol. We can't - * place a section reloc inside an exprloc body without computing - * its absolute info-section offset post-emit. For Phase 1 we emit - * the literal symbol value as zero and trust that DVL_GLOBAL is - * not yet exercised by any harness case. Documented in the - * agent report as a Phase-1 limitation. */ u32 i; expr[n++] = DW_OP_addr; + addr_payload_off = n; for (i = 0; i < e->d->c->target.ptr_size; ++i) expr[n++] = 0; - (void)loc.v.global; break; } } form_uleb(b, n); - buf_write(b, expr, n); + { + u32 expr_start = buf_pos(b); + buf_write(b, expr, n); + if (loc.kind == DVL_GLOBAL && addr_payload_off != UINT32_MAX) { + add_addr_reloc(e, &e->info_relocs, &e->ninfo_relocs, &e->info_relocs_cap, + expr_start + addr_payload_off, loc.v.global); + } + } } static void emit_var_die(EmitCtx* e, DebugVarDIE* v) { - u32 abbrev = v->is_param ? e->abbr_param : e->abbr_var; + u32 abbrev = v->is_param ? e->abbr_param + : (v->external ? e->abbr_var_external : e->abbr_var); v->die_offset = buf_pos(&e->info_body); form_uleb(&e->info_body, abbrev); + /* DW_AT_external is flag_present and carries no body. */ emit_strx4(e, &e->info_body, v->name); form_uleb(&e->info_body, debug_file(e->d, v->decl.file_id)); form_uleb(&e->info_body, v->decl.line); @@ -1002,7 +1036,7 @@ static void emit_section_line(EmitCtx* e) { u32 k; for (k = 0; k < e->nline_relocs; ++k) { obj_reloc(e->ob, e->sec_line, prog_start + e->line_relocs[k].buf_offset, - R_ABS64, e->line_relocs[k].sym, 0); + debug_addr_reloc_kind(e->d), e->line_relocs[k].sym, 0); } for (k = 0; k < nlsp; ++k) { obj_reloc(e->ob, e->sec_line, hdr_start + lsp_slots[k].at, R_ABS32, @@ -1084,8 +1118,8 @@ static void emit_section_aranges(EmitCtx* e) { * map all addresses to the first input's CU. */ obj_reloc(e->ob, e->sec_aranges, 6u, R_ABS32, e->ssym_info, 0); for (i = 0; i < e->naranges_relocs; ++i) { - obj_reloc(e->ob, e->sec_aranges, e->aranges_relocs[i].buf_offset, R_ABS64, - e->aranges_relocs[i].sym, 0); + obj_reloc(e->ob, e->sec_aranges, e->aranges_relocs[i].buf_offset, + debug_addr_reloc_kind(e->d), e->aranges_relocs[i].sym, 0); } buf_fini(&b); } @@ -1127,8 +1161,8 @@ static void emit_section_rnglists(EmitCtx* e) { } flatten_to_section(e, e->sec_rnglists, &b); for (i = 0; i < e->nrng_relocs; ++i) { - obj_reloc(e->ob, e->sec_rnglists, e->rng_relocs[i].buf_offset, R_ABS64, - e->rng_relocs[i].sym, 0); + obj_reloc(e->ob, e->sec_rnglists, e->rng_relocs[i].buf_offset, + debug_addr_reloc_kind(e->d), e->rng_relocs[i].sym, 0); } buf_fini(&b); } @@ -1195,8 +1229,8 @@ static void emit_section_info(EmitCtx* e) { } for (i = 0; i < e->ninfo_relocs; ++i) { obj_reloc(e->ob, e->sec_info, - cu_header_size + e->info_relocs[i].buf_offset, R_ABS64, - e->info_relocs[i].sym, 0); + cu_header_size + e->info_relocs[i].buf_offset, + debug_addr_reloc_kind(e->d), e->info_relocs[i].sym, 0); } } buf_fini(&out); @@ -1259,8 +1293,12 @@ void debug_emit(Debug* d) { * invoked later (child-DIE decl_file / line program), so without this the * CU name and comp_dir come out empty. Seed it from the first function's * declaration site. */ - if (d->nfiles == 0 && d->nfuncs > 0) { - (void)debug_file(d, d->funcs[0].decl.file_id); + if (d->nfiles == 0) { + if (d->nfuncs > 0) { + (void)debug_file(d, d->funcs[0].decl.file_id); + } else if (d->nglobals > 0) { + (void)debug_file(d, d->globals[0].decl.file_id); + } } if (d->nfiles > 0) { primary_dir = d->files[0].dir; @@ -1295,6 +1333,7 @@ void debug_emit(Debug* d) { form_u32(&ec.info_body, 8); for (i = 0; i < d->ntypes; ++i) emit_type_die(&ec, (DebugTypeId)(i + 1)); + for (i = 0; i < d->nglobals; ++i) emit_var_die(&ec, &d->globals[i]); for (i = 0; i < d->nfuncs; ++i) emit_subprogram_die(&ec, &d->funcs[i]); form_uleb(&ec.info_body, 0); /* end of CU children */ diff --git a/src/debug/debug_internal.h b/src/debug/debug_internal.h @@ -69,6 +69,7 @@ typedef struct DebugType { * pointers escape through the public API. */ struct DebugTypeBuilder { Debug* d; + DebugTypeId id; u8 is_union; Sym tag; u32 byte_size; @@ -92,7 +93,8 @@ struct DebugEnumBuilder { typedef struct DebugVarDIE { u8 is_param; /* 1 = formal_parameter; 0 = variable */ - u8 pad[3]; + u8 external; /* top-level variable only */ + u8 pad[2]; u32 param_idx; /* for params */ Sym name; DebugTypeId type; @@ -215,6 +217,13 @@ struct Debug { u32 funcs_cap; i32 cur_func; /* -1 if none open */ + /* Top-level static-storage object DIEs. Function-scope statics are still + * stored on the owning DebugFunc as variable DIEs with DVL_GLOBAL locations. + */ + DebugVarDIE* globals; + u32 nglobals; + u32 globals_cap; + /* Line rows pending: latest set_loc */ SrcLoc pending_loc; diff --git a/src/debug/dwarf_query.c b/src/debug/dwarf_query.c @@ -162,10 +162,12 @@ static void fill_varloc(KitDebugInfo* d, u32 cu_idx, const DwLocal* v, u64 pc, return; } } - if (e[0] == DW_OP_addr && llen == 9) { + if (e[0] == DW_OP_addr && cu_idx < d->ncus && + llen == 1u + d->cus[cu_idx].address_size) { u32 off = 1; out->kind = KIT_DLOC_GLOBAL; - out->v.global = dw_u64(e, llen, &off); + out->v.global = d->cus[cu_idx].address_size == 4 ? dw_u32(e, llen, &off) + : dw_u64(e, llen, &off); return; } /* Fallback: opaque expression bytes. */ diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -1,8 +1,10 @@ #include <kit/cg.h> #include <kit/compile.h> #include <kit/core.h> +#include <kit/dwarf.h> #include <kit/frontend.h> #include <kit/object.h> +#include <kit/source.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -401,6 +403,108 @@ static void exercise_cg_data_entsize(KitCompiler* c, KitCgTypeId i8_ty) { kit_obj_builder_free(ob); } +static void exercise_cg_debug_global_object(KitCompiler* c, + KitCgTypeId i32_ty) { + KitCodeOptions opts; + KitObjBuilder* ob; + KitCg* cg; + KitCgDecl decl; + KitCgSym sym; + KitCgDataDefAttrs data_attrs; + KitSrcLoc loc; + uint32_t file_id = 0; + + memset(&opts, 0, sizeof opts); + opts.debug_info = true; + ob = new_obj(c); + EXPECT(ob != NULL, "debug global obj builder allocation failed"); + if (!ob) return; + cg = NULL; + EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg != NULL, + "debug global cg allocation failed"); + if (!cg) { + kit_obj_builder_free(ob); + return; + } + EXPECT(kit_cg_begin(cg, ob, &opts) == KIT_OK, "debug global cg begin failed"); + + EXPECT(kit_source_add_memory(c, KIT_SLICE_LIT("cg_debug_global.c"), + &file_id) == KIT_OK, + "debug global source file add failed"); + loc.file_id = file_id; + loc.line = 7; + loc.col = 3; + kit_cg_set_loc(cg, loc); + + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_sym_intern(c, KIT_SLICE_LIT("cg_dbg_global_link")); + decl.display_name = kit_sym_intern(c, KIT_SLICE_LIT("cg_dbg_global")); + decl.type = i32_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + sym = kit_cg_decl(cg, decl); + EXPECT(sym != KIT_CG_SYM_NONE, "debug global object decl failed"); + + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.align = kit_cg_type_align(c, i32_ty); + kit_cg_data_begin(cg, sym, data_attrs); + kit_cg_data_int(cg, 42, i32_ty); + kit_cg_data_end(cg); + finish_cg(cg, "debug global"); + kit_cg_free(cg); + + { + KitWriter* w = NULL; + KitObjFile* f = NULL; + KitDebugInfo* di = NULL; + KitDwarfVarLoc vloc; + KitDwarfVarIter* vit = NULL; + KitDwarfVar var; + int iter_found = 0; + + if (!open_emitted_obj(c, ob, &w, &f)) { + EXPECT(0, "failed to inspect debug global object"); + kit_obj_builder_free(ob); + return; + } + EXPECT( + kit_dwarf_open(kit_compiler_context(c), f, &di) == KIT_OK && di != NULL, + "debug global dwarf open failed"); + if (di) { + EXPECT(kit_dwarf_var_at(di, 0, KIT_SLICE_LIT("cg_dbg_global"), &vloc) == + KIT_OK, + "debug global variable not found by display name"); + EXPECT(vloc.kind == KIT_DLOC_GLOBAL, + "debug global location kind %d, expected global", (int)vloc.kind); + EXPECT(vloc.byte_size == 4, "debug global byte size %u, expected 4", + vloc.byte_size); + EXPECT(vloc.type != NULL, "debug global type missing"); + + EXPECT(kit_dwarf_vars_at_new(di, 0, KIT_DVRM_GLOBAL, &vit) == KIT_OK && + vit != NULL, + "debug global iterator allocation failed"); + if (vit) { + while (kit_dwarf_vars_at_next(vit, &var) == KIT_ITER_ITEM) { + if (kit_slice_eq_cstr(var.name, "cg_dbg_global") && + var.role == KIT_DVR_GLOBAL) { + iter_found = 1; + EXPECT(var.loc.kind == KIT_DLOC_GLOBAL, + "debug global iterator location kind %d", (int)var.loc.kind); + } + } + kit_dwarf_vars_at_free(vit); + } + EXPECT(iter_found, "debug global iterator did not report global"); + kit_dwarf_free(di); + } + kit_obj_free(f); + kit_writer_close(w); + } + + kit_obj_builder_free(ob); +} + static KitCgSym begin_i32_func(KitCompiler* c, KitCg* cg, KitCgTypeId i32_ty, const char* name) { KitCgFuncSig sig; @@ -2012,6 +2116,7 @@ int main(void) { exercise_cg_late_local_addr(c, i32_ty, 0); exercise_cg_late_local_addr(c, i32_ty, 1); exercise_cg_data_entsize(c, i8_ty); + exercise_cg_debug_global_object(c, i32_ty); exercise_cg_literal_folds(c, i32_ty); exercise_cg_constfold_phases(c, i32_ty, i8_ty); exercise_cg_memop_constfold_shape(c, i32_ty, i64_ty, ptr_i32);