commit 888738afcfa1f9f65a1d03938960314974891ea7
parent fcb3b729a797778a0eb41257685491eaaf760cf4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 15 Jun 2026 16:03:14 -0700
debug: DWARF for globals
Diffstat:
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);