Skip to content

Resolve Go symbols from .gopclntab for symbol-stripped Go binaries#311

Merged
brianrob merged 1 commit into
microsoft:mainfrom
brianrob:brianrob/go
Jul 21, 2026
Merged

Resolve Go symbols from .gopclntab for symbol-stripped Go binaries#311
brianrob merged 1 commit into
microsoft:mainfrom
brianrob:brianrob/go

Conversation

@brianrob

Copy link
Copy Markdown
Member

Problem

Go binaries built with -ldflags "-s" (e.g. kubelet, containerd, and most distro/CI Go binaries) drop the ELF symbol table, so one-collect could not resolve any function names for them — traces showed raw addresses. The Go function/line metadata is still present in the .gopclntab section, which the ELF symbol path did not consume.

What this does

Adds a .gopclntab parser and wires it into the existing ELF symbol-resolution path so Go symbols are recovered automatically, with no API change and no caller opt-in. If a mapping's ELF symbols are missing/stripped, its .gopclntab is parsed and its functions are added as symbols.

  • ruwind/go_pclntab.rs (new): parses the Go 1.18+ pclntab layouts (magic 0xfffffff0/0xfffffff1), yielding (start, end, name) per function. Returns None for older/unknown magics so callers fall back cleanly.
  • ruwind/elf.rs: locates .gopclntab (incl. PIE .data.rel.ro), resolves the correct text base (runtime.text symbol → pcHeader textStart → PIE relative-reloc addend → .text), and recovers the table by magic-scan when section headers are stripped.
  • helpers/exporting/symbols.rs: GoPclnTabSymbolReader exposes the parsed functions as symbols.
  • helpers/exporting/os/linux.rs + mappings.rs: integrates Go symbols into the symbol-file search; add_matching_symbols now transparently de-duplicates when unioning a second source onto a mapping (single-source callers pay nothing).

Design notes

  • Cheap for non-Go binaries. The Go path is only attempted when ELF symbols are absent, and it bails out immediately if no .gopclntab is found — it does not add a separate reader pass over every binary.
  • No demangling needed. Go pclntab names are already final, human-readable form (pkg.Type.Method); demangle() returns None.
  • Scope: Linux/ELF, Go 1.18+. Pre-1.18 binaries are declined by design and fall back to ELF symbols (they retain a symtab in practice).
  • Minimal public surface. New items are pub(crate)/private; consumption is automatic through the existing ELF symbol path.

Testing

  • Unit tests for the parser (both magics, endianness, PIE, recovery) and two committed stripped-binary fixtures under test/assets/go/ (self-built minimal Go programs — one symbol-stripped, one section-header-stripped). cargo test --lib → 128 pass; clippy clean.
  • Corpus validation against Go's debug/gosym (reference implementation):
    • Synthetic matrix: 899/899 at 100.0000% — Go 1.18/1.20/1.22/1.23 × amd64/arm64 × default/strip/PIE/cgo + section-stripped recovery.
    • Real-world: 577/580 at 100.0000% across ~242 OSS projects (Kubernetes components, cosign, victoria-metrics, opa, terraform/vault, minio, prometheus, grafana, etc.); 3 skipped as Go <1.18 (out of scope).
    • ~41M symbols total, 0 parser defects. go tool objdump independently confirmed one-collect's addresses on externally-linked/cgo binaries.

Go binaries built with -ldflags "-s" (e.g. kubelet, containerd) drop the
ELF symbol table, leaving one-collect unable to resolve function names even
though the Go function/line metadata is still present in .gopclntab.

Add a Go 1.18+ pclntab parser and wire it into the existing ELF symbol
path so Go symbols are recovered automatically, with no API change and no
caller opt-in. When a mapping's ELF symbols are missing/stripped, its
.gopclntab is parsed and its functions are added as symbols.

- ruwind/go_pclntab.rs (new): parse the Go 1.18/1.20 pclntab layouts
  (magic 0xfffffff0/0xfffffff1); return None for older magics so callers
  fall back cleanly.
- ruwind/elf.rs: locate .gopclntab (incl. PIE .data.rel.ro), resolve the
  text base (runtime.text symbol -> pcHeader textStart -> PIE relative-reloc
  addend -> .text), and recover the table by magic-scan when section headers
  are stripped.
- helpers/exporting/symbols.rs: GoPclnTabSymbolReader exposes parsed
  functions as symbols.
- helpers/exporting/os/linux.rs + mappings.rs: integrate Go symbols into the
  symbol-file search; add_matching_symbols transparently de-duplicates when
  unioning a second source onto a mapping (single-source callers pay nothing).

Design notes:
- Cheap for non-Go binaries: the Go path is only attempted when ELF symbols
  are absent and bails out immediately when no .gopclntab is present.
- No demangling needed: pclntab names are already final, human-readable form.
- Scope: Linux/ELF, Go 1.18+. Pre-1.18 binaries fall back to ELF symbols.
- Minimal public surface: new items are pub(crate)/private; consumption is
  automatic through the existing ELF symbol path.

Tested with parser unit tests (both magics, endianness, PIE, recovery) and
two committed stripped-binary fixtures under test/assets/go/. Additionally
validated against Go's debug/gosym on a large binary corpus (Go
1.18/1.20/1.22/1.23 x amd64/arm64 x default/strip/PIE/cgo plus ~240
real-world OSS projects) at 100% address+name parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05ec5f08-8081-405b-829b-5031aec5143c
@brianrob
brianrob marked this pull request as ready for review July 16, 2026 18:13
@brianrob
brianrob requested a review from beaubelgrave July 16, 2026 18:13
Comment thread one_collect/src/ruwind/elf.rs

@beaubelgrave beaubelgrave left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, that's a lot of work, thanks!

@brianrob
brianrob merged commit 18cd595 into microsoft:main Jul 21, 2026
17 checks passed
@brianrob
brianrob deleted the brianrob/go branch July 21, 2026 22:48
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.

2 participants