Resolve Go symbols from .gopclntab for symbol-stripped Go binaries#311
Merged
Conversation
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
marked this pull request as ready for review
July 16, 2026 18:13
beaubelgrave
approved these changes
Jul 21, 2026
beaubelgrave
left a comment
Collaborator
There was a problem hiding this comment.
LGTM, that's a lot of work, thanks!
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.
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.gopclntabsection, which the ELF symbol path did not consume.What this does
Adds a
.gopclntabparser 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.gopclntabis parsed and its functions are added as symbols.ruwind/go_pclntab.rs(new): parses the Go 1.18+ pclntab layouts (magic0xfffffff0/0xfffffff1), yielding(start, end, name)per function. ReturnsNonefor older/unknown magics so callers fall back cleanly.ruwind/elf.rs: locates.gopclntab(incl. PIE.data.rel.ro), resolves the correct text base (runtime.textsymbol → pcHeadertextStart→ PIE relative-reloc addend →.text), and recovers the table by magic-scan when section headers are stripped.helpers/exporting/symbols.rs:GoPclnTabSymbolReaderexposes the parsed functions as symbols.helpers/exporting/os/linux.rs+mappings.rs: integrates Go symbols into the symbol-file search;add_matching_symbolsnow transparently de-duplicates when unioning a second source onto a mapping (single-source callers pay nothing).Design notes
.gopclntabis found — it does not add a separate reader pass over every binary.pkg.Type.Method);demangle()returnsNone.pub(crate)/private; consumption is automatic through the existing ELF symbol path.Testing
test/assets/go/(self-built minimal Go programs — one symbol-stripped, one section-header-stripped).cargo test --lib→ 128 pass; clippy clean.debug/gosym(reference implementation):go tool objdumpindependently confirmed one-collect's addresses on externally-linked/cgo binaries.