diff options
| author | Taras Madan <tarasmadan@google.com> | 2025-02-28 13:57:50 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2025-03-07 10:23:01 +0000 |
| commit | 7e3bd60dd6c8f783f5a418c64aa75f6818236dc4 (patch) | |
| tree | 24dce56dc037521e340e73f45ba41c8b6c264af2 /pkg/symbolizer | |
| parent | 831e3629115be4f5e68e6e0df3a5b07eed26daa4 (diff) | |
pkg/symbolizer: introduce Symbolizer interface
To simplify interface Read*Symbols were moved out from symbolizer.Symbolizer.
Diffstat (limited to 'pkg/symbolizer')
| -rw-r--r-- | pkg/symbolizer/addr2line.go | 184 | ||||
| -rw-r--r-- | pkg/symbolizer/addr2line_test.go (renamed from pkg/symbolizer/symbolizer_test.go) | 0 | ||||
| -rw-r--r-- | pkg/symbolizer/cache.go | 2 | ||||
| -rw-r--r-- | pkg/symbolizer/cache_test.go | 18 | ||||
| -rw-r--r-- | pkg/symbolizer/nm.go | 4 | ||||
| -rw-r--r-- | pkg/symbolizer/nm_test.go | 3 | ||||
| -rw-r--r-- | pkg/symbolizer/symbolizer.go | 191 |
7 files changed, 206 insertions, 196 deletions
diff --git a/pkg/symbolizer/addr2line.go b/pkg/symbolizer/addr2line.go new file mode 100644 index 000000000..5187f72ed --- /dev/null +++ b/pkg/symbolizer/addr2line.go @@ -0,0 +1,184 @@ +// Copyright 2016 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// TODO: strip " (discriminator N)", "constprop", "isra" from function names. + +package symbolizer + +import ( + "bufio" + "fmt" + "io" + "os/exec" + "strconv" + "strings" + + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/sys/targets" +) + +type addr2Line struct { + target *targets.Target + subprocs map[string]*subprocess + interner Interner +} + +type subprocess struct { + cmd *exec.Cmd + stdin io.Closer + stdout io.Closer + input *bufio.Writer + scanner *bufio.Scanner +} + +func (s *addr2Line) Symbolize(bin string, pcs ...uint64) ([]Frame, error) { + sub, err := s.getSubprocess(bin) + if err != nil { + return nil, err + } + return symbolize(&s.interner, sub.input, sub.scanner, pcs) +} + +func (s *addr2Line) Close() { + for _, sub := range s.subprocs { + sub.stdin.Close() + sub.stdout.Close() + sub.cmd.Process.Kill() + sub.cmd.Wait() + } +} + +func (s *addr2Line) getSubprocess(bin string) (*subprocess, error) { + if sub := s.subprocs[bin]; sub != nil { + return sub, nil + } + addr2line, err := s.target.Addr2Line() + if err != nil { + return nil, err + } + cmd := osutil.Command(addr2line, "-afi", "-e", bin) + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + stdout, err := cmd.StdoutPipe() + if err != nil { + stdin.Close() + return nil, err + } + if err := cmd.Start(); err != nil { + stdin.Close() + stdout.Close() + return nil, err + } + sub := &subprocess{ + cmd: cmd, + stdin: stdin, + stdout: stdout, + input: bufio.NewWriter(stdin), + scanner: bufio.NewScanner(stdout), + } + if s.subprocs == nil { + s.subprocs = make(map[string]*subprocess) + } + s.subprocs[bin] = sub + return sub, nil +} + +func symbolize(interner *Interner, input *bufio.Writer, scanner *bufio.Scanner, pcs []uint64) ([]Frame, error) { + var frames []Frame + done := make(chan error, 1) + go func() { + var err error + defer func() { + done <- err + }() + if !scanner.Scan() { + if err = scanner.Err(); err == nil { + err = io.EOF + } + return + } + for range pcs { + var frames1 []Frame + frames1, err = parse(interner, scanner) + if err != nil { + return + } + frames = append(frames, frames1...) + } + for i := 0; i < 2; i++ { + scanner.Scan() + } + }() + + for _, pc := range pcs { + if _, err := fmt.Fprintf(input, "0x%x\n", pc); err != nil { + return nil, err + } + } + // Write an invalid PC so that parse doesn't block reading input. + // We read out result for this PC at the end of the function. + if _, err := fmt.Fprintf(input, "0xffffffffffffffff\n"); err != nil { + return nil, err + } + input.Flush() + + if err := <-done; err != nil { + return nil, err + } + return frames, nil +} + +func parse(interner *Interner, s *bufio.Scanner) ([]Frame, error) { + pc, err := strconv.ParseUint(s.Text(), 0, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse pc '%v' in addr2line output: %w", s.Text(), err) + } + var frames []Frame + for { + if !s.Scan() { + break + } + ln := s.Text() + if len(ln) > 3 && ln[0] == '0' && ln[1] == 'x' { + break + } + fn := ln + if !s.Scan() { + err := s.Err() + if err == nil { + err = io.EOF + } + return nil, fmt.Errorf("failed to read file:line from addr2line: %w", err) + } + ln = s.Text() + colon := strings.LastIndexByte(ln, ':') + if colon == -1 { + return nil, fmt.Errorf("failed to parse file:line in addr2line output: %v", ln) + } + lineEnd := colon + 1 + for lineEnd < len(ln) && ln[lineEnd] >= '0' && ln[lineEnd] <= '9' { + lineEnd++ + } + file := ln[:colon] + line, err := strconv.Atoi(ln[colon+1 : lineEnd]) + if err != nil || fn == "" || fn == "??" || file == "" || file == "??" || line <= 0 { + continue + } + frames = append(frames, Frame{ + PC: pc, + Func: interner.Do(fn), + File: interner.Do(file), + Line: line, + Inline: true, + }) + } + if err := s.Err(); err != nil { + return nil, err + } + if len(frames) != 0 { + frames[len(frames)-1].Inline = false + } + return frames, nil +} diff --git a/pkg/symbolizer/symbolizer_test.go b/pkg/symbolizer/addr2line_test.go index f6445367e..f6445367e 100644 --- a/pkg/symbolizer/symbolizer_test.go +++ b/pkg/symbolizer/addr2line_test.go diff --git a/pkg/symbolizer/cache.go b/pkg/symbolizer/cache.go index 65e34aae7..35dc2951b 100644 --- a/pkg/symbolizer/cache.go +++ b/pkg/symbolizer/cache.go @@ -24,7 +24,7 @@ type cacheVal struct { err error } -func (c *Cache) Symbolize(inner func(string, uint64) ([]Frame, error), bin string, pc uint64) ([]Frame, error) { +func (c *Cache) Symbolize(inner func(string, ...uint64) ([]Frame, error), bin string, pc uint64) ([]Frame, error) { key := cacheKey{bin, pc} c.mu.RLock() val, ok := c.cache[key] diff --git a/pkg/symbolizer/cache_test.go b/pkg/symbolizer/cache_test.go index eceb61f30..064c96eeb 100644 --- a/pkg/symbolizer/cache_test.go +++ b/pkg/symbolizer/cache_test.go @@ -13,14 +13,18 @@ import ( func TestCache(t *testing.T) { called := make(map[cacheKey]bool) - inner := func(bin string, pc uint64) ([]Frame, error) { - key := cacheKey{bin, pc} - assert.False(t, called[key]) - called[key] = true - if bin == "error" { - return nil, fmt.Errorf("error %v", pc) + inner := func(bin string, pcs ...uint64) ([]Frame, error) { + var res []Frame + for _, pc := range pcs { + key := cacheKey{bin, pc} + assert.False(t, called[key]) + called[key] = true + if bin == "error" { + return nil, fmt.Errorf("error %v", pc) + } + res = append(res, Frame{PC: pc, Func: bin + "_func"}) } - return []Frame{{PC: pc, Func: bin + "_func"}}, nil + return res, nil } var cache Cache check := func(bin string, pc uint64, frames []Frame, err error) { diff --git a/pkg/symbolizer/nm.go b/pkg/symbolizer/nm.go index 19d519a91..1696fc0a3 100644 --- a/pkg/symbolizer/nm.go +++ b/pkg/symbolizer/nm.go @@ -15,12 +15,12 @@ type Symbol struct { } // ReadTextSymbols returns list of text symbols in the binary bin. -func (s *Symbolizer) ReadTextSymbols(bin string) (map[string][]Symbol, error) { +func ReadTextSymbols(bin string) (map[string][]Symbol, error) { return read(bin, true) } // ReadRodataSymbols returns list of rodata symbols in the binary bin. -func (s *Symbolizer) ReadRodataSymbols(bin string) (map[string][]Symbol, error) { +func ReadRodataSymbols(bin string) (map[string][]Symbol, error) { return read(bin, false) } diff --git a/pkg/symbolizer/nm_test.go b/pkg/symbolizer/nm_test.go index e5c00b3eb..263eeb71a 100644 --- a/pkg/symbolizer/nm_test.go +++ b/pkg/symbolizer/nm_test.go @@ -8,8 +8,7 @@ import ( ) func TestSymbols(t *testing.T) { - symb := NewSymbolizer(nil) - symbols, err := symb.ReadTextSymbols("testdata/nm.test.out") + symbols, err := ReadTextSymbols("testdata/nm.test.out") if err != nil { t.Fatalf("failed to read symbols: %v", err) } diff --git a/pkg/symbolizer/symbolizer.go b/pkg/symbolizer/symbolizer.go index 02e6b7693..e3625d313 100644 --- a/pkg/symbolizer/symbolizer.go +++ b/pkg/symbolizer/symbolizer.go @@ -1,27 +1,9 @@ -// Copyright 2016 syzkaller project authors. All rights reserved. +// Copyright 2025 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. -// TODO: strip " (discriminator N)", "constprop", "isra" from function names. - package symbolizer -import ( - "bufio" - "fmt" - "io" - "os/exec" - "strconv" - "strings" - - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/sys/targets" -) - -type Symbolizer struct { - target *targets.Target - subprocs map[string]*subprocess - interner Interner -} +import "github.com/google/syzkaller/sys/targets" type Frame struct { PC uint64 @@ -31,170 +13,11 @@ type Frame struct { Inline bool } -type subprocess struct { - cmd *exec.Cmd - stdin io.Closer - stdout io.Closer - input *bufio.Writer - scanner *bufio.Scanner -} - -func NewSymbolizer(target *targets.Target) *Symbolizer { - return &Symbolizer{target: target} -} - -func (s *Symbolizer) Symbolize(bin string, pc uint64) ([]Frame, error) { - return s.SymbolizeArray(bin, []uint64{pc}) -} - -func (s *Symbolizer) SymbolizeArray(bin string, pcs []uint64) ([]Frame, error) { - sub, err := s.getSubprocess(bin) - if err != nil { - return nil, err - } - return symbolize(&s.interner, sub.input, sub.scanner, pcs) -} - -func (s *Symbolizer) Close() { - for _, sub := range s.subprocs { - sub.stdin.Close() - sub.stdout.Close() - sub.cmd.Process.Kill() - sub.cmd.Wait() - } -} - -func (s *Symbolizer) getSubprocess(bin string) (*subprocess, error) { - if sub := s.subprocs[bin]; sub != nil { - return sub, nil - } - addr2line, err := s.target.Addr2Line() - if err != nil { - return nil, err - } - cmd := osutil.Command(addr2line, "-afi", "-e", bin) - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - stdout, err := cmd.StdoutPipe() - if err != nil { - stdin.Close() - return nil, err - } - if err := cmd.Start(); err != nil { - stdin.Close() - stdout.Close() - return nil, err - } - sub := &subprocess{ - cmd: cmd, - stdin: stdin, - stdout: stdout, - input: bufio.NewWriter(stdin), - scanner: bufio.NewScanner(stdout), - } - if s.subprocs == nil { - s.subprocs = make(map[string]*subprocess) - } - s.subprocs[bin] = sub - return sub, nil -} - -func symbolize(interner *Interner, input *bufio.Writer, scanner *bufio.Scanner, pcs []uint64) ([]Frame, error) { - var frames []Frame - done := make(chan error, 1) - go func() { - var err error - defer func() { - done <- err - }() - if !scanner.Scan() { - if err = scanner.Err(); err == nil { - err = io.EOF - } - return - } - for range pcs { - var frames1 []Frame - frames1, err = parse(interner, scanner) - if err != nil { - return - } - frames = append(frames, frames1...) - } - for i := 0; i < 2; i++ { - scanner.Scan() - } - }() - - for _, pc := range pcs { - if _, err := fmt.Fprintf(input, "0x%x\n", pc); err != nil { - return nil, err - } - } - // Write an invalid PC so that parse doesn't block reading input. - // We read out result for this PC at the end of the function. - if _, err := fmt.Fprintf(input, "0xffffffffffffffff\n"); err != nil { - return nil, err - } - input.Flush() - - if err := <-done; err != nil { - return nil, err - } - return frames, nil +type Symbolizer interface { + Symbolize(bin string, pcs ...uint64) ([]Frame, error) + Close() } -func parse(interner *Interner, s *bufio.Scanner) ([]Frame, error) { - pc, err := strconv.ParseUint(s.Text(), 0, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse pc '%v' in addr2line output: %w", s.Text(), err) - } - var frames []Frame - for { - if !s.Scan() { - break - } - ln := s.Text() - if len(ln) > 3 && ln[0] == '0' && ln[1] == 'x' { - break - } - fn := ln - if !s.Scan() { - err := s.Err() - if err == nil { - err = io.EOF - } - return nil, fmt.Errorf("failed to read file:line from addr2line: %w", err) - } - ln = s.Text() - colon := strings.LastIndexByte(ln, ':') - if colon == -1 { - return nil, fmt.Errorf("failed to parse file:line in addr2line output: %v", ln) - } - lineEnd := colon + 1 - for lineEnd < len(ln) && ln[lineEnd] >= '0' && ln[lineEnd] <= '9' { - lineEnd++ - } - file := ln[:colon] - line, err := strconv.Atoi(ln[colon+1 : lineEnd]) - if err != nil || fn == "" || fn == "??" || file == "" || file == "??" || line <= 0 { - continue - } - frames = append(frames, Frame{ - PC: pc, - Func: interner.Do(fn), - File: interner.Do(file), - Line: line, - Inline: true, - }) - } - if err := s.Err(); err != nil { - return nil, err - } - if len(frames) != 0 { - frames[len(frames)-1].Inline = false - } - return frames, nil +func Make(target *targets.Target) Symbolizer { + return &addr2Line{target: target} } |
