Skip to content

debug: establish Go DWARF foundation with stock LLDB support#2141

Merged
cpunion merged 8 commits into
xgo-dev:mainfrom
cpunion:codex/dwarf-debug-v2
Jul 22, 2026
Merged

debug: establish Go DWARF foundation with stock LLDB support#2141
cpunion merged 8 commits into
xgo-dev:mainfrom
cpunion:codex/dwarf-debug-v2

Conversation

@cpunion

@cpunion cpunion commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Problem

LLGo's existing DWARF path has no standards-based contract. Metadata construction is spread across the compiler, several Go source types are encoded incorrectly, recursive named types cannot be completed safely, and source variables and lexical scopes are not represented consistently.

The result may be malformed or misleading DWARF even when a binary builds successfully. Debugger-only checks do not detect those format and type-system errors. Conversely, valid Go DWARF marked DW_LANG_Go is not sufficient for current LLDB: Apple/LLVM LLDB has no Go language plugin and hides otherwise valid frame variables.

Foundation design

This PR establishes a Go DWARF foundation with clear ownership:

  • internal/debuginfo owns the LLVM DIBuilder lifecycle, module flags, file caching, finalization, and thin metadata operations.
  • ssa owns Go source-type lowering and package/function debug metadata.
  • cl owns source scopes, variable storage homes, and ssa.DebugRef integration.
  • Named and recursive types use replaceable metadata followed by RAUW.
  • Globals retain their source type instead of an extra storage pointer.
  • Complex values use DW_ATE_complex_float; function values reference a subroutine type; maps and channels are pointer-sized opaque types.
  • Parameters, named results, locals, lexical blocks, closures, generic instances, methods, declaration locations, and cross-file line records are emitted.

LLVM builder ownership remains independent from Go semantic lowering, while frontend source-variable bookkeeping stays in cl.

LLDB compatibility contract

Compile units temporarily use DW_LANG_C so stock LLDB uses its complete C frame-variable path. The emitted type layouts and attributes remain the Go-oriented DWARF validated by the standard tests.

LLGo identity does not depend on the language field:

  • DW_AT_producer remains LLGo and is the authoritative DWARF identity.
  • A hidden, versioned __llgo_debugger_marker_v1 symbol is emitted only with debug information.
  • The marker is a one-byte link-once constant retained through llvm.used, deduplicates to one final Mach-O/ELF symbol, and has no startup or runtime access cost.
  • The Python plugin checks the marker instead of attaching to every target.
  • Expression lookup now falls back from frame variables to LLGo globals.
  • A plain C executable is tested to ensure the plugin rejects non-LLGo targets.

Native DW_LANG_Go support is intentionally a separate LLDB language-extension project described in #2154. LLGo should switch the language field only after that extension passes its cross-platform acceptance matrix.

Standards coverage

TestStandardDWARF builds and executes a multi-file Go fixture at O0, O1, O2, O3, Os, and Oz, then:

  • parses native Mach-O or ELF DWARF with Go's debug/dwarf;
  • validates compile-unit language and producer attributes;
  • checks aliases, named and recursive types, every primitive width, structs and byte offsets, arrays, strings, slices, maps, channels, interfaces, pointers, globals, scopes, functions, generics, methods, and line tables;
  • validates LLGo's runtime layouts for string/slice/interface headers and two-word function values, including the code pointer's subroutine signature;
  • runs llvm-dwarfdump --verify;
  • runs dsymutil and verifies the produced dSYM on macOS;
  • normalizes a verifier-only Linux copy with llvm-dwarfutil to remove linker tombstones left by section GC, while semantic assertions continue to inspect the original ELF.

The macOS LLDB suite runs at O0 because it asserts exact source-variable values and lifetimes. It now passes 198 checks covering parameters, locals, lexical scopes, standard complex values, globals, pointers, aggregates, marker detection, and non-LLGo rejection. Optimized builds legitimately eliminate or merge some locals, so the six optimization levels are validated structurally rather than compared against O0 variable availability.

The requested optimization level still configures LLVM target-machine code generation. Existing main behavior disables LLGo's IR pass pipeline and optimized C ABI mode whenever DWARF is enabled; this PR intentionally does not change that policy. Decoupling those transformations is covered by #2143.

Fixes #2116.

Verification

Local macOS, Go 1.26.5 / LLVM 19:

  • TestStandardDWARF: O0, O1, O2, O3, Os, and Oz pass;
  • _lldb/runtest.sh at O0: 198/198 pass, including non-LLGo rejection;
  • go test ./internal/debuginfo: pass with 100% statement coverage;
  • go test ./ssa: pass;
  • the four new cl debug/referrer unit tests: pass;
  • shell/Python syntax checks: pass.

The resource-limited full cl package run reached its 15-minute command timeout in the existing _testrt fixture without an assertion failure; CI runs that suite in shards.

Ubuntu arm64 container, Go 1.26.5 / LLVM 19.1.7, limited to 2 CPUs, 15 GiB memory, and 512 PIDs:

  • internal/debuginfo tests pass;
  • TestStandardDWARF passes at all six optimization levels;
  • an O0 LLGo fixture builds and runs;
  • the final ELF contains exactly one __llgo_debugger_marker_v1 symbol.

CI keeps both the standards matrix and the existing LLDB integration suite.

Out of scope

Each area below remains independent from this foundation:

  • LLVM IR optimization with DWARF (#2143);
  • full-LTO debug locations (#2144);
  • C ABI storage rewriting and debug records;
  • Python frontend pseudo-variables;
  • DWARF-sensitive SSA transformation correctness;
  • Go-compatible default -w behavior (#2142);
  • pclntab/source-line precision and cold/warm cache consistency;
  • native LLDB Go language support (#2154).

DWARF remains opt-in for linked builds through -ldflags=-w=false in this PR.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.62611% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cl/compile.go 90.74% 2 Missing and 3 partials ⚠️
cl/debug_scope.go 89.65% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cpunion
cpunion marked this pull request as ready for review July 21, 2026 19:58
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@cpunion
cpunion force-pushed the codex/dwarf-debug-v2 branch from e1080f3 to e1b508c Compare July 22, 2026 06:20
@cpunion cpunion changed the title debug: establish standards-based Go DWARF foundation debug: establish Go DWARF foundation with stock LLDB support Jul 22, 2026
@cpunion
cpunion marked this pull request as ready for review July 22, 2026 08:04
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@cpunion
cpunion merged commit d726187 into xgo-dev:main Jul 22, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DWARF: break recursive type cycles in DI generation

1 participant