Skip to content

Commit

Permalink
symbolizer: shell out to addr2line
Browse files Browse the repository at this point in the history
Adds symbolizer flag to specify addr2line binary to shell out to. Create
a new liner type that uses said binary.
  • Loading branch information
danipozo committed Jan 17, 2025
1 parent 446d85e commit 127dd5e
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 24 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ Flags:
Mode to demangle C++ symbols. Default mode
is simplified: no parameters, no templates,
no return type
--symbolizer-external-addr-2-line-path=""
Path to addr2line utility, to be used for
symbolization instead of native implementation
--symbolizer-number-of-tries=3
Number of tries to attempt to symbolize an
unsybolized location
Expand Down
6 changes: 4 additions & 2 deletions pkg/parca/parca.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,9 @@ type FlagsStorage struct {
}

type FlagsSymbolizer struct {
DemangleMode string `default:"simple" help:"Mode to demangle C++ symbols. Default mode is simplified: no parameters, no templates, no return type" enum:"simple,full,none,templates"`
NumberOfTries int `default:"3" help:"Number of tries to attempt to symbolize an unsybolized location"`
DemangleMode string `default:"simple" help:"Mode to demangle C++ symbols. Default mode is simplified: no parameters, no templates, no return type" enum:"simple,full,none,templates"`
ExternalAddr2linePath string `default:"" help:"Path to addr2line utility, to be used for symbolization instead of native implementation"`
NumberOfTries int `default:"3" help:"Number of tries to attempt to symbolize an unsybolized location"`
}

// FlagsDebuginfo configures the Parca Debuginfo client.
Expand Down Expand Up @@ -415,6 +416,7 @@ func Run(ctx context.Context, logger log.Logger, reg *prometheus.Registry, flags
symbolizer.NewBadgerCache(db),
debuginfo.NewFetcher(debuginfodClients, debuginfoBucket),
flags.Debuginfo.CacheDir,
flags.Symbolizer.ExternalAddr2linePath,
symbolizer.WithDemangleMode(flags.Symbolizer.DemangleMode),
),
memory.DefaultAllocator,
Expand Down
158 changes: 136 additions & 22 deletions pkg/symbolizer/symbolizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,23 @@
package symbolizer

import (
"bufio"
"context"
"debug/elf"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

"github.com/go-kit/log"
"github.com/go-kit/log/level"

debuginfopb "github.com/parca-dev/parca/gen/proto/go/parca/debuginfo/v1alpha1"
profilepb "github.com/parca-dev/parca/gen/proto/go/parca/metastore/v1alpha1"
"github.com/parca-dev/parca/pkg/debuginfo"
"github.com/parca-dev/parca/pkg/profile"
"github.com/parca-dev/parca/pkg/symbol/addr2line"
Expand Down Expand Up @@ -63,9 +68,10 @@ func WithDemangleMode(mode string) Option {
type Symbolizer struct {
logger log.Logger

debuginfo DebuginfoFetcher
cache SymbolizerCache
metadata DebuginfoMetadata
debuginfo DebuginfoFetcher
cache SymbolizerCache
metadata DebuginfoMetadata
externalAddr2linePath string

demangler *demangle.Demangler

Expand All @@ -89,19 +95,21 @@ func New(
cache SymbolizerCache,
debuginfo DebuginfoFetcher,
tmpDir string,
externalAddr2linePath string,
opts ...Option,
) *Symbolizer {
const (
defaultDemangleMode = "simple"
)

s := &Symbolizer{
logger: log.With(logger, "component", "symbolizer"),
cache: cache,
debuginfo: debuginfo,
tmpDir: tmpDir,
metadata: metadata,
demangler: demangle.NewDemangler(defaultDemangleMode, false),
logger: log.With(logger, "component", "symbolizer"),
cache: cache,
debuginfo: debuginfo,
externalAddr2linePath: externalAddr2linePath,
tmpDir: tmpDir,
metadata: metadata,
demangler: demangle.NewDemangler(defaultDemangleMode, false),
}

for _, opt := range opts {
Expand All @@ -126,7 +134,7 @@ func (s *Symbolizer) Symbolize(
req SymbolizationRequest,
) error {
if err := s.symbolize(ctx, req); err != nil {
level.Debug(s.logger).Log("msg", "failed to symbolize", "err", err)
level.Debug(s.logger).Log("msg", fmt.Sprintf("failed to symbolize: BuildID %s", req.BuildID), "err", err)
}

return nil
Expand Down Expand Up @@ -272,19 +280,121 @@ func (s *Symbolizer) getDebuginfo(ctx context.Context, buildID string) (string,
}

type cachedLiner struct {
logger log.Logger
demangler *demangle.Demangler
filepath string
f *elf.File
quality *debuginfopb.DebuginfoQuality
buildID string
logger log.Logger
demangler *demangle.Demangler
filepath string
f *elf.File
quality *debuginfopb.DebuginfoQuality
buildID string
externalAddr2linePath string

// this is the concrete liner
liner liner

cache SymbolizerCache
}

type externalExecutableLiner struct {
cmd exec.Cmd
inputPipe io.WriteCloser
scanner *bufio.Scanner

logger log.Logger
demangler *demangle.Demangler
}

func (c *cachedLiner) newExternalExecutableLiner(
filepath string,
) (liner, error) {
// Create cmd with pipes and scanner for reading output line by line
addr2lineCmd := exec.Command(c.externalAddr2linePath, "--exe", filepath, "-afiC")
addr2lineInput, err := addr2lineCmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("error creating input pipe for addr2line: %w", err)
}
addr2lineOutput, err := addr2lineCmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("error creating output pipe for addr2line: %w", err)
}
scanner := bufio.NewScanner(addr2lineOutput)

// Start command to load executable info and wait for input
addr2lineCmd.Start()

Check failure on line 322 in pkg/symbolizer/symbolizer.go

View workflow job for this annotation

GitHub Actions / Go Lint

Error return value of `addr2lineCmd.Start` is not checked (errcheck)

return &externalExecutableLiner{
cmd: *addr2lineCmd,
inputPipe: addr2lineInput,
scanner: scanner,
logger: c.logger,
demangler: c.demangler,
}, nil
}

func (l *externalExecutableLiner) Close() error {
err := l.inputPipe.Close()
if err != nil {
return err
}
err = l.cmd.Wait()
if err != nil {
return err
}
return nil
}

func (l *externalExecutableLiner) PCToLines(ctx context.Context, pc uint64) ([]profile.LocationLine, error) {
// Write the address to addr2line stdin twice in order to get addresses as a proper delimiter
for i := 0; i < 2; i++ {
_, err := io.WriteString(l.inputPipe, fmt.Sprintf("0x%x\n", pc))
if err != nil {
return nil, fmt.Errorf("error writing to addr2line stdin: %w\n", err)
}
}

lines := []profile.LocationLine{}
// addr2line output consists of a variable number of pairs of lines per address
// The first line of each block of output is the address, if we have to skip additional lines
// it means the previous read loop left some output unread
l.scanner.Scan()
addressLine := l.scanner.Text()
for !strings.HasPrefix(addressLine, "0x") {
l.scanner.Scan()
addressLine = l.scanner.Text()
}

keepReading := true
l.scanner.Scan()
for keepReading {
line1 := l.scanner.Text()

l.scanner.Scan()
line2 := l.scanner.Text()

line1 = strings.TrimSuffix(line1, "\n")
line2 = strings.TrimSuffix(line2, "\n")

fileLocation := strings.Split(line2, ":")
lineNum, err := strconv.Atoi(fileLocation[1])
if err != nil {
lineNum = 0
}

lines = append(lines, profile.LocationLine{
Line: int64(lineNum),
Function: &profilepb.Function{
StartLine: int64(lineNum),
Filename: fileLocation[0],
Name: line1,
},
})

l.scanner.Scan()
keepReading = !strings.HasPrefix(l.scanner.Text(), "0x")
}

return lines, nil
}

// newConcreteLiner creates a new liner for the given mapping and object file path.
func (s *Symbolizer) newLiner(
filepath string,
Expand All @@ -293,12 +403,13 @@ func (s *Symbolizer) newLiner(
buildID string,
) liner {
return &cachedLiner{
logger: s.logger,
demangler: s.demangler,
filepath: filepath,
f: f,
quality: quality,
buildID: buildID,
logger: s.logger,
demangler: s.demangler,
filepath: filepath,
f: f,
quality: quality,
buildID: buildID,
externalAddr2linePath: s.externalAddr2linePath,

cache: s.cache,
}
Expand Down Expand Up @@ -344,6 +455,9 @@ func (c *cachedLiner) PCToLines(ctx context.Context, pc uint64) ([]profile.Locat

func (c *cachedLiner) newConcreteLiner(filepath string, f *elf.File, quality *debuginfopb.DebuginfoQuality) (liner, error) {
switch {
case c.externalAddr2linePath != "":
level.Debug(c.logger).Log("msg", fmt.Sprintf("using external addr2line liner with binary: %s", c.externalAddr2linePath))
return c.newExternalExecutableLiner(filepath)
case quality.HasDwarf:
lnr, err := addr2line.DWARF(c.logger, c.filepath, c.f, c.demangler)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/symbolizer/symbolizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func TestSymbolizer(t *testing.T) {
&NoopSymbolizerCache{},
debuginfo.NewFetcher(debuginfodClient, bucket),
symbolizerCacheDir,
"",
)

ctx := context.Background()
Expand Down

0 comments on commit 127dd5e

Please sign in to comment.