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 | |
| parent | 831e3629115be4f5e68e6e0df3a5b07eed26daa4 (diff) | |
pkg/symbolizer: introduce Symbolizer interface
To simplify interface Read*Symbols were moved out from symbolizer.Symbolizer.
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/cover/backend/dwarf.go | 4 | ||||
| -rw-r--r-- | pkg/cover/report_test.go | 3 | ||||
| -rw-r--r-- | pkg/ifaceprobe/ifaceprobe.go | 2 | ||||
| -rw-r--r-- | pkg/report/bsd.go | 7 | ||||
| -rw-r--r-- | pkg/report/bsd_test.go | 54 | ||||
| -rw-r--r-- | pkg/report/fuchsia.go | 4 | ||||
| -rw-r--r-- | pkg/report/linux.go | 7 | ||||
| -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 |
14 files changed, 246 insertions, 237 deletions
diff --git a/pkg/cover/backend/dwarf.go b/pkg/cover/backend/dwarf.go index a6045b91e..7be2832d3 100644 --- a/pkg/cover/backend/dwarf.go +++ b/pkg/cover/backend/dwarf.go @@ -419,7 +419,7 @@ func symbolizeModule(target *targets.Target, interner *symbolizer.Interner, objD pcchan := make(chan []uint64, procs) for p := 0; p < procs; p++ { go func() { - symb := symbolizer.NewSymbolizer(target) + symb := symbolizer.Make(target) defer symb.Close() var res symbolizerResult for pcs := range pcchan { @@ -430,7 +430,7 @@ func symbolizeModule(target *targets.Target, interner *symbolizer.Interner, objD pcs[i] = pc - mod.Addr } } - frames, err := symb.SymbolizeArray(mod.Path, pcs) + frames, err := symb.Symbolize(mod.Path, pcs...) if err != nil { res.err = fmt.Errorf("failed to symbolize: %w", err) } diff --git a/pkg/cover/report_test.go b/pkg/cover/report_test.go index 77b1aaffc..8f27590e9 100644 --- a/pkg/cover/report_test.go +++ b/pkg/cover/report_test.go @@ -357,8 +357,7 @@ func generateReport(t *testing.T, target *targets.Target, test *Test) (*reports, } else if target.OS == runtime.GOOS && (target.Arch == runtime.GOARCH || target.VMArch == runtime.GOARCH) { t.Fatal(err) } else { - symb := symbolizer.NewSymbolizer(target) - text, err := symb.ReadTextSymbols(bin) + text, err := symbolizer.ReadTextSymbols(bin) if err != nil { t.Fatal(err) } diff --git a/pkg/ifaceprobe/ifaceprobe.go b/pkg/ifaceprobe/ifaceprobe.go index f16be42a4..c5bb63639 100644 --- a/pkg/ifaceprobe/ifaceprobe.go +++ b/pkg/ifaceprobe/ifaceprobe.go @@ -68,7 +68,7 @@ type fileDesc struct { } func (pr *prober) run() (*Info, error) { - symb := symbolizer.NewSymbolizer(pr.cfg.SysTarget) + symb := symbolizer.Make(pr.cfg.SysTarget) defer symb.Close() for _, glob := range globList() { diff --git a/pkg/report/bsd.go b/pkg/report/bsd.go index 75cfa3426..05ae5a4cf 100644 --- a/pkg/report/bsd.go +++ b/pkg/report/bsd.go @@ -28,8 +28,7 @@ func ctorBSD(cfg *config, oopses []*oops, symbolizeRes []*regexp.Regexp) (report if cfg.kernelObj != "" { kernelObject = filepath.Join(cfg.kernelObj, cfg.target.KernelObject) var err error - symb := symbolizer.NewSymbolizer(cfg.target) - symbols, err = symb.ReadTextSymbols(kernelObject) + symbols, err = symbolizer.ReadTextSymbols(kernelObject) if err != nil { return nil, err } @@ -53,7 +52,7 @@ func (ctx *bsd) Parse(output []byte) *Report { } func (ctx *bsd) Symbolize(rep *Report) error { - symb := symbolizer.NewSymbolizer(ctx.config.target) + symb := symbolizer.Make(ctx.config.target) defer symb.Close() var symbolized []byte prefix := rep.reportPrefixLen @@ -70,7 +69,7 @@ func (ctx *bsd) Symbolize(rep *Report) error { return nil } -func (ctx *bsd) symbolizeLine(symbFunc func(bin string, pc uint64) ([]symbolizer.Frame, error), +func (ctx *bsd) symbolizeLine(symbFunc func(string, ...uint64) ([]symbolizer.Frame, error), line []byte) []byte { var match []int // Check whether the line corresponds to the any of the parts that require symbolization. diff --git a/pkg/report/bsd_test.go b/pkg/report/bsd_test.go index 1fd721d3f..f3a1bf31d 100644 --- a/pkg/report/bsd_test.go +++ b/pkg/report/bsd_test.go @@ -24,37 +24,39 @@ func testSymbolizeLine(t *testing.T, ctor fn, tests []symbolizeLineTest) { {Addr: 0x81237520, Size: 0x173}, }, } - symb := func(bin string, pc uint64) ([]symbolizer.Frame, error) { - if bin != "bsd.gdb" { - return nil, fmt.Errorf("unknown pc 0x%x", pc) - } + symb := func(bin string, pcs ...uint64) ([]symbolizer.Frame, error) { + var res []symbolizer.Frame + for _, pc := range pcs { + if bin != "bsd.gdb" { + return nil, fmt.Errorf("unknown pc 0x%x", pc) + } - switch pc & 0xffffffff { - case 0x8150894f: - return []symbolizer.Frame{ - { + switch pc & 0xffffffff { + case 0x8150894f: + res = append(res, symbolizer.Frame{ Func: "closef", File: "/bsd/src/kern_descrip.c", Line: 1241, - }, - }, nil - case 0x81237542: - return []symbolizer.Frame{ - { - Func: "sleep_finish_timeout", - File: "/bsd/src/kern_synch.c", - Line: 336, - Inline: true, - }, - { - Func: "sleep_finish_all", - File: "/bsd/src/kern_synch.c", - Line: 157, - }, - }, nil - default: - return nil, fmt.Errorf("unknown pc 0x%x", pc) + }) + case 0x81237542: + res = append(res, + symbolizer.Frame{ + Func: "sleep_finish_timeout", + File: "/bsd/src/kern_synch.c", + Line: 336, + Inline: true, + }, + symbolizer.Frame{ + Func: "sleep_finish_all", + File: "/bsd/src/kern_synch.c", + Line: 157, + }, + ) + default: + return nil, fmt.Errorf("unknown pc 0x%x", pc) + } } + return res, nil } reporter, _, err := ctor(&config{ kernelSrc: "/bsd/src2", diff --git a/pkg/report/fuchsia.go b/pkg/report/fuchsia.go index 51d200251..8d66365f7 100644 --- a/pkg/report/fuchsia.go +++ b/pkg/report/fuchsia.go @@ -134,7 +134,7 @@ func (ctx *fuchsia) shortenReport(report []byte) []byte { } func (ctx *fuchsia) symbolize(output []byte) []byte { - symb := symbolizer.NewSymbolizer(ctx.config.target) + symb := symbolizer.Make(ctx.config.target) defer symb.Close() out := new(bytes.Buffer) @@ -167,7 +167,7 @@ func (ctx *fuchsia) symbolize(output []byte) []byte { return out.Bytes() } -func (ctx *fuchsia) processPC(out *bytes.Buffer, symb *symbolizer.Symbolizer, +func (ctx *fuchsia) processPC(out *bytes.Buffer, symb symbolizer.Symbolizer, line []byte, match []int, call bool) bool { prefix := line[match[0]:match[1]] pcStart := match[2] - match[0] diff --git a/pkg/report/linux.go b/pkg/report/linux.go index cbe4a44be..a275236e0 100644 --- a/pkg/report/linux.go +++ b/pkg/report/linux.go @@ -42,8 +42,7 @@ func ctorLinux(cfg *config) (reporterImpl, []string, error) { if cfg.kernelObj != "" { vmlinux = filepath.Join(cfg.kernelObj, cfg.target.KernelObject) var err error - symb := symbolizer.NewSymbolizer(cfg.target) - symbols[""], err = symb.ReadTextSymbols(vmlinux) + symbols[""], err = symbolizer.ReadTextSymbols(vmlinux) if err != nil { return nil, nil, err } @@ -51,7 +50,7 @@ func ctorLinux(cfg *config) (reporterImpl, []string, error) { if mod.Name == "" { continue } - ss, err := symb.ReadTextSymbols(mod.Path) + ss, err := symbolizer.ReadTextSymbols(mod.Path) if err != nil { continue } @@ -408,7 +407,7 @@ func (ctx *linux) Symbolize(rep *Report) error { } func (ctx *linux) symbolize(rep *Report) error { - symb := symbolizer.NewSymbolizer(ctx.config.target) + symb := symbolizer.Make(ctx.config.target) defer symb.Close() symbFunc := func(bin string, pc uint64) ([]symbolizer.Frame, error) { return ctx.symbolizerCache.Symbolize(symb.Symbolize, bin, pc) 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} } |
