Skip to content

fix(tls): read a stripped binary's function table through the open file - #362

Closed
mayankpande88 wants to merge 2 commits into
fix/go-tls-write-oncefrom
fix/symbol-lookup-exited-process
Closed

mayankpande88 wants to merge 2 commits into
fix/go-tls-write-oncefrom
fix/symbol-lookup-exited-process

Conversation

@mayankpande88

Copy link
Copy Markdown
Contributor

Summary

A stripped Go binary (-ldflags="-s -w", the usual production build) has no ELF symbol table. Its functions are found through .gopclntab, which openGoFuncTable read by reopening the binary's path, a /proc/<pid>/exe link.

When the process exited between the ELF open and that reopen, its crypto/tls functions came back "not found". LookupSymbols caches that per binary, and AttachGoTlsUprobes then caches no_symbols, so every later process of the binary went unprobed until the cache evicted it. The first process of a short-lived stripped binary is often gone before the lookup finishes.

  • ELFFile keeps the file it opened, and the function table is mapped from that descriptor. The path is never reopened.
  • A symbol is no longer recorded as missing when the table failed to load for any other reason. The lookup returns an error instead, which is not cached.

Stacked on #359.

Engineering detail
  • TestGetSymbol_StrippedGoBinaryAfterPathIsGone builds a stripped binary, opens it, removes the file, then looks up the TLS functions. It fails on the parent commit (no symbols found) and passes here.
  • Seen locally while fix(tls): send each Go TLS write once #359's first version was slower to attach: 450 attaches of a short-lived test client returned no_symbols, and the binary stayed unprobed after that.

Local e2e: built agent images with and without this change and ran them in a local Docker VM (kernel 6.10). The local short-lived-client scenario (391 TLS clients of one Go binary) captured 390 of 390 with this branch, which includes #359's probe fix. The no_symbols results seen while attaching was slow are gone. The unit test above fails without the change. golangci-lint passes.

A stripped Go binary's symbols come only from .gopclntab, and
openGoFuncTable read it by reopening the path, a /proc/<pid>/exe link.
When the process exited between the ELF open and that reopen, its
crypto/tls functions were "not found". LookupSymbols caches that per
binary and AttachGoTlsUprobes caches no_symbols, so every later process
of the binary went unprobed until the entries were evicted.

Short-lived processes of a stripped binary, the first of which is often
gone before the lookup finishes, hit this. In a local run where attaching
was slower, 450 attaches of the short-lived test client returned
no_symbols.

ELFFile now keeps the opened file and the table is mapped from it. A
symbol is also no longer recorded as missing when the table failed to
load for another reason.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request modifies the ELF tracer to keep the binary file descriptor open in the ELFFile struct instead of reopening the file path when reading the Go function table. This ensures that symbols can still be resolved even if the target process exits and its path becomes unavailable. Feedback suggests adding GOOS=linux to the test build environment in TestGetSymbol_StrippedGoBinaryAfterPathIsGone to prevent test failures on non-Linux development platforms.

Comment thread ebpftracer/gopclntab_test.go Outdated
bin := filepath.Join(t.TempDir(), "stripped")
cmd := exec.Command(goBin, "build", "-ldflags=-s -w", "-o", bin, ".")
cmd.Dir = src
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

On non-Linux development platforms (such as macOS or Windows), running this test will fail because go build without GOOS=linux produces a native binary (Mach-O or PE) which OpenELFFile cannot parse as an ELF file.

Adding GOOS=linux to the environment ensures that an ELF binary is always built, allowing the test to run successfully on all platforms.

Suggested change
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
cmd.Env = append(os.Environ(), "GOOS=linux", "CGO_ENABLED=0")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in 89979b4: the test binary is built with GOOS=linux.

@mayankpande88

Copy link
Copy Markdown
Contributor Author

Folded into #364 with the rest of this stack, so it can be reviewed and merged as one unit (squash-only merges made each stacked merge conflict). The commit is unchanged there; its description and review thread here stay as the detailed write-up.

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.

1 participant