Skip to content

Commit

Permalink
pkg/symbol/elfutils: Fix handling of stripped Go binaries
Browse files Browse the repository at this point in the history
Go binaries that are stripped, for example built with:

```
go build -ldflags "-w -s" main.go
```

Have an empty symbol table section, which some additional build steps
may strip, since it's empty.

Before this patch, those Go binaries would run into an error case when
attempting to load the symbol table.

Since the only truly necessary section for symbolization is the
`.gopclntab` section, this patch proposes to only check for this
section.
  • Loading branch information
brancz committed Nov 9, 2022
1 parent a6cae59 commit 0b6cb75
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 39 deletions.
37 changes: 3 additions & 34 deletions pkg/symbol/elfutils/elfutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,45 +73,14 @@ func readableDWARFSections(f *elf.File) (map[string]struct{}, error) {

// IsSymbolizableGoObjFile checks whether the specified executable or library file is generated by Go toolchain
// and has necessary symbol information attached.
func IsSymbolizableGoObjFile(f *elf.File) (bool, error) {
// Checks ".note.go.buildid" section and symtab better to keep those sections in object file.
isGo := false
for _, s := range f.Sections {
if s.Name == ".note.go.buildid" {
isGo = true
}
}

// In case ".note.go.buildid" section is stripped, check for symbols.
if !isGo {
syms, err := f.Symbols()
if err != nil {
return false, fmt.Errorf("failed to read symbols: %w", err)
}
for _, sym := range syms {
name := sym.Name
if name == "runtime.main" || name == "main.main" {
isGo = true
}
if name == "runtime.buildVersion" {
isGo = true
}
}
}

if !isGo {
return false, nil
}

func IsSymbolizableGoObjFile(f *elf.File) bool {
// Check if the Go binary symbolizable.
// Go binaries has a special case. They use ".gopclntab" section to symbolize addresses.
if sec := f.Section(".gopclntab"); sec != nil {
if sec.Type == elf.SHT_PROGBITS {
return true, nil
}
return true
}

return false, errors.New("failed to detect .gopclntab section or section has no bits")
return false
}

// HasSymbols reports whether the specified executable or library file contains symbols (both.symtab and .dynsym).
Expand Down
28 changes: 28 additions & 0 deletions pkg/symbol/elfutils/elfutils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2022 The Parca Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package elfutils

import (
"debug/elf"
"testing"

"github.com/stretchr/testify/require"
)

func TestIsSymbolizableGoObjFile(t *testing.T) {
f, err := elf.Open("testdata/main")
require.NoError(t, err)

require.True(t, IsSymbolizableGoObjFile(f))
}
3 changes: 3 additions & 0 deletions pkg/symbol/elfutils/testdata/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
all:
GOOS=linux GOARCH=amd64 go build -ldflags "-w -s" main.go
#strip -R ".gosymtab" main
Binary file added pkg/symbol/elfutils/testdata/main
Binary file not shown.
20 changes: 20 additions & 0 deletions pkg/symbol/elfutils/testdata/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2022 The Parca Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import "fmt"

func main() {
fmt.Println("Hello World!")
}
6 changes: 1 addition & 5 deletions pkg/symbol/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,7 @@ func (s *Symbolizer) newLiner(buildID, path string) (liner, error) {

// Go binaries has a special case. They use ".gopclntab" section to symbolize addresses.
// Keep that section and other identifying sections in the debug information file.
isGo, err := elfutils.IsSymbolizableGoObjFile(f)
if err != nil {
level.Debug(logger).Log("msg", "failed to determine if binary is a Go binary", "err", err)
}
if isGo {
if elfutils.IsSymbolizableGoObjFile(f) {
// Right now, this uses "debug/gosym" package, and it won't work for inlined functions,
// so this is just a best-effort implementation, in case we don't have DWARF.
lnr, err := addr2line.Go(logger, f)
Expand Down

0 comments on commit 0b6cb75

Please sign in to comment.