feat(dwarf): DW_TAG_subprogram DIEs — function names in backtraces (#394) - #557
Merged
Conversation
…names (#394) VCR-DBG-001 Tier-1 increment. The emitted CU previously carried a DW_AT_stmt_list line table but ZERO children, so gdb/lldb resolved a `.text` address to `file:line` yet printed no function frame. Attach one `DW_TAG_subprogram` child DIE per compiled function: - DW_AT_name = the export/function name (synth already has it). - DW_AT_low_pc = the function's `.text` address, relocated against the SAME `__synth_text_base` symbol as the CU low_pc (addend = the function's object-relative offset) — one extra `.rel.debug_info` record each, so it shifts correctly when a linker places `.text`. - DW_AT_high_pc = offset (size) form `high_pc - low_pc`, no relocation. No parameters/locals/frame-base yet — that is Tier-2, gated on VCR-RA. Purely additive: subprogram low_pcs reuse `__synth_text_base` via an addend, so no new symbol is defined and `.text`/`.data`/`.bss` stay byte-identical (frozen 3/3, Oracle A green). New Oracle F (red→green): walks the emitted CU the debugger way, asserts one subprogram per `func_N` symbol, every exported wasm function name (derived from the input's export section at runtime — non-circular) present as a DW_AT_name, and each `[low_pc,high_pc)` within `.text`. Oracle C extended: `.debug_info` now carries 1 (CU) + N (subprogram) R_ARM_ABS32 relocs vs `__synth_text_base`, each in-place addend equal to that function's object-relative low_pc (relocates by the base). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced Jul 1, 2026
avrabe
added a commit
that referenced
this pull request
Jul 2, 2026
…me section (#394) (#565) Tier-1.x follow-up to #557: DW_TAG_subprogram DW_AT_name for INTERNAL (non-exported) functions was the synthetic func_N because the wasm `name` custom section (function-names subsection) was never parsed. A backtrace through a panic showed `func_7` instead of `core::panicking::panic_const::panic_const_add_overflow::h...`. - wasm_decoder: parse the `name` custom section (KnownCustom::Name → Name::Function map, best-effort — debug metadata must not fail a compile) into a new `FunctionOps.debug_name`, applied after the parse loop (the section trails the code section). Both decode_wasm_module and decode_wasm_functions. - CLI: thread `debug_name` through ElfFunction into the subprogram compose. Name priority: name-section > export name > func_N. DWARF-only — the symbol table and relocation labels keep using the export-name-or-func_N `name`, so linkability and emitted bytes are unchanged. - Oracle G (dwarf_debug_line_emit_394): ground truth parsed from the fixture at runtime; asserts ≥1 internal function's DIE carries its real name-section name and zero synthetic func_N names remain (msgq_put_359 names every function). RED before (func_7/8/10), GREEN after. Frozen-safe: purely additive DWARF metadata — frozen_codegen_bytes 3/3, .text byte-identical. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Jul 2, 2026
…) (#573) The compile_unit DIE's DW_AT_high_pc was derived from the LINE-TABLE extent (last mapped address + 1), while the child DW_TAG_subprogram DIEs (#557, v0.19.0) carry true CODE extents. A function's code routinely extends past its last line-mapped address (the mapped op itself is >=2 bytes, plus any unmapped epilogue/literal-pool tail), so the tail function fell OUTSIDE its parent CU's range: llvm-dwarfdump --verify fails post-link with "DIE address ranges are not contained in its parent's ranges", and a debugger walking CUs by PC range misses it. Fix: the CU DW_AT_high_pc now covers max(subprogram high_pc) - the .text code extent the compose already passes - falling back to the line-table extent when no subprograms exist. The line program's end_sequence is untouched (.debug_line bytes unchanged). Oracle H (new, red->green): parse the emitted CU with gimli::read and assert [CU low_pc, CU high_pc) CONTAINS every subprogram's [low_pc, high_pc) - the llvm-dwarfdump containment rule, checked in-tree. Red on main with msgq_put_359.wasm: CU [0x0,0x453) failed to contain panic_fmt [0x450,0x456). Green after: CU [0x0,0x456). Includes a non-vacuity guard (code extent > line extent) so the fixture keeps exercising the #564 shape. Confirmed with llvm-dwarfdump --verify on the issue's exact repro (dwarf_coherent.wasm linked at -Ttext=0x8000): "No errors"; CU high_pc now equals clampi's high_pc. Frozen codegen bytes 3/3 green (DWARF additive); oracles A-G green; fmt + clippy clean. Closes #564 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
VCR-DBG-001 Tier-1 increment for #394. synth already emits a real
.debug_line(addr→file:line) on the ARM--relocatablepath behind--debug-line, and the CU root DIE carries name/low_pc/high_pc/stmt_list.This PR adds the missing piece: per-function
DW_TAG_subprogramchildDIEs, so a gdb/lldb backtrace shows the function name instead of a
bare address.
Before → after
DW_TAG_compile_unithad aDW_AT_stmt_listline table but zero children. A debugger resolved a
.textaddressto
file:line, but printed no function frame.DW_TAG_subprogramchild DIE per compiled function:DW_AT_name= the export/function name (synth already holds it inElfFunction.name).DW_AT_low_pc= the function's.textaddress, relocated against thesame
__synth_text_basesymbol as the CU low_pc (addend = thefunction's object-relative
.textoffset) — one extra.rel.debug_infoR_ARM_ABS32record each, so it shifts correctlywhen a linker places
.text.DW_AT_high_pc= offset (size) formhigh_pc - low_pc— a plainconstant, no relocation (matches the CU's high_pc form).
No parameters/locals/frame-base yet — that is Tier-2, gated on VCR-RA.
Files
crates/synth-core/src/dwarf_line.rs— newSubprogramInfostruct;emit_debug_sectionstakes&[SubprogramInfo]and adds the child DIEsunder the CU root.
crates/synth-cli/src/main.rs(DWARF compose block) — builds thesubprogram list from
func.name+func_offsets[i]..+code.len()andthreads it in. Reloc/symbol wiring is unchanged — the extra
.textrelocs flow through the existing per-section
.rel.debug_*path.crates/synth-cli/tests/dwarf_debug_line_emit_394.rs— new Oracle F;Oracle C tightened.
Purely additive (frozen-safe)
Subprogram low_pcs reuse
__synth_text_basevia an addend, so no newsymbol is defined.
.text/.data/.bssstay byte-identical with vswithout
--debug-line. Frozenfrozen_codegen_bytes3/3; Oracle A(byte-identity) green; DWARF anchors did not shift.
Oracle: red → green
New Oracle F (
emitted_debug_info_has_subprogram_dies_per_function_394):walks the emitted CU the debugger way (
dwarf.units()→ CU tree) and assertsDW_TAG_subprogramperfunc_Nsymbol the object defines;section at runtime (non-circular, mirroring Oracle E's filename
check), e.g.
z_impl_k_msgq_put,gale_k_msgq_put_decide— appears asa
DW_AT_name;[low_pc, high_pc)lies within.text.DW_TAG_subprogram children".
Oracle C tightened, not relaxed.
.debug_linestill carries exactlyone reloc (addend 0).
.debug_infonow carries exactly1 + NR_ARM_ABS32relocs vs__synth_text_base(CU low_pc + one persubprogram), and the multiset of in-place addends must equal
{0} ∪ {each subprogram low_pc}— proving each subprogram low_pcrelocates by the base to
text_base + low_pc.Gates (exit-code verified)
cargo test -p synth-cli --test dwarf_debug_line_emit_394— 7/7 (A–F).cargo test -p synth-cli --test frozen_codegen_bytes— 3/3.cargo test --workspace --exclude synth-verify— exit 0 (synth-verifyexcluded due to the local z3-sys build issue).
cargo fmt --checkclean;cargo clippy --workspace --all-targets -- -D warningsclean.Honest scope
Internal (non-exported) functions carry
func_NasDW_AT_name,even though the input wasm's
namecustom section has richer names(
core::panicking::…,gale::msgq::put_decide::…). This matches thetask scope ("
func_3/ the export name") and the dataElfFunction.namealready holds. Propagating name-section names for internals is a natural
Tier-1.x follow-up. Subprograms are emitted only when the unit is
emitted at all (≥1 line row), consistent with the pre-existing additive
no-op contract.
🤖 Generated with Claude Code