aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/symbolizer
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2025-02-28 13:57:50 +0100
committerTaras Madan <tarasmadan@google.com>2025-03-07 10:23:01 +0000
commit7e3bd60dd6c8f783f5a418c64aa75f6818236dc4 (patch)
tree24dce56dc037521e340e73f45ba41c8b6c264af2 /pkg/symbolizer
parent831e3629115be4f5e68e6e0df3a5b07eed26daa4 (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.go184
-rw-r--r--pkg/symbolizer/addr2line_test.go (renamed from pkg/symbolizer/symbolizer_test.go)0
-rw-r--r--pkg/symbolizer/cache.go2
-rw-r--r--pkg/symbolizer/cache_test.go18
-rw-r--r--pkg/symbolizer/nm.go4
-rw-r--r--pkg/symbolizer/nm_test.go3
-rw-r--r--pkg/symbolizer/symbolizer.go191
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}
}