From 7e3bd60dd6c8f783f5a418c64aa75f6818236dc4 Mon Sep 17 00:00:00 2001 From: Taras Madan Date: Fri, 28 Feb 2025 13:57:50 +0100 Subject: pkg/symbolizer: introduce Symbolizer interface To simplify interface Read*Symbols were moved out from symbolizer.Symbolizer. --- pkg/cover/backend/dwarf.go | 4 +- pkg/cover/report_test.go | 3 +- pkg/ifaceprobe/ifaceprobe.go | 2 +- pkg/report/bsd.go | 7 +- pkg/report/bsd_test.go | 54 ++++++----- pkg/report/fuchsia.go | 4 +- pkg/report/linux.go | 7 +- pkg/symbolizer/addr2line.go | 184 +++++++++++++++++++++++++++++++++++ pkg/symbolizer/addr2line_test.go | 196 ++++++++++++++++++++++++++++++++++++++ pkg/symbolizer/cache.go | 2 +- pkg/symbolizer/cache_test.go | 18 ++-- pkg/symbolizer/nm.go | 4 +- pkg/symbolizer/nm_test.go | 3 +- pkg/symbolizer/symbolizer.go | 191 ++----------------------------------- pkg/symbolizer/symbolizer_test.go | 196 -------------------------------------- 15 files changed, 442 insertions(+), 433 deletions(-) create mode 100644 pkg/symbolizer/addr2line.go create mode 100644 pkg/symbolizer/addr2line_test.go delete mode 100644 pkg/symbolizer/symbolizer_test.go (limited to 'pkg') 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/addr2line_test.go b/pkg/symbolizer/addr2line_test.go new file mode 100644 index 000000000..f6445367e --- /dev/null +++ b/pkg/symbolizer/addr2line_test.go @@ -0,0 +1,196 @@ +// 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. + +package symbolizer + +import ( + "bufio" + "fmt" + "os" + "reflect" + "strconv" + "testing" +) + +func TestParse(t *testing.T) { + addresses := []struct { + pc uint64 + resp string + frames []Frame + }{ + { + 0xffffffff8180a42e, + "0xffffffff8180a42e\n" + + "__asan_report_load2_noabort\n" + + "mm/kasan/report.c:320\n", + []Frame{ + { + PC: 0xffffffff8180a42e, + Func: "__asan_report_load2_noabort", + File: "mm/kasan/report.c", + Line: 320, + Inline: false, + }, + }, + }, + { + 0xffffffff8180a42d, + "0xffffffff8180a42d\n" + + "kasan_report\n" + + "mm/kasan/report.c:301\n" + + "__asan_report_load2_noabort\n" + + "mm/kasan/report.c:320\n", + []Frame{ + { + PC: 0xffffffff8180a42d, + Func: "kasan_report", + File: "mm/kasan/report.c", + Line: 301, + Inline: true, + }, + { + PC: 0xffffffff8180a42d, + Func: "__asan_report_load2_noabort", + File: "mm/kasan/report.c", + Line: 320, + Inline: false, + }, + }, + }, + { + 0xffffffff82fdbe0b, + "0xffffffff82fdbe0b\n" + + "fbcon_invert_region\n" + + "drivers/video/console/fbcon.c:2750\n", + []Frame{ + { + PC: 0xffffffff82fdbe0b, + Func: "fbcon_invert_region", + File: "drivers/video/console/fbcon.c", + Line: 2750, + Inline: false, + }, + }, + }, + { + 0x123124, + "0x0000000000123124\n" + + "??\n" + + "??:0\n", + nil, + }, + { + 0xffffffffffffffff, + "0xffffffffffffffff\n" + + "??\n" + + "??:0\n", + nil, + }, + { + 0xffffffff81a2aff9, + "0xffffffff81a2aff9\n" + + "devpts_get_priv\n" + + "fs/devpts/inode.c:588 (discriminator 3)\n", + []Frame{ + { + PC: 0xffffffff81a2aff9, + Func: "devpts_get_priv", + File: "fs/devpts/inode.c", + Line: 588, + Inline: false, + }, + }, + }, + } + + // Stub addr2line. + inputr, inputw, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer inputr.Close() + defer inputw.Close() + outputr, outputw, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer outputr.Close() + done := make(chan error) + go func() { + s := bufio.NewScanner(inputr) + loop: + for s.Scan() { + pc, err := strconv.ParseUint(s.Text(), 0, 64) + if err != nil { + outputw.Close() + done <- fmt.Errorf("got unexpected pc: %v", s.Text()) + return + } + for _, addr := range addresses { + if pc == addr.pc { + outputw.Write([]byte(addr.resp)) + continue loop + } + } + outputw.Close() + done <- fmt.Errorf("got unexpected pc: 0x%x", pc) + return + } + outputw.Write([]byte("DONE\n")) + outputw.Close() + close(done) + }() + defer func() { + inputw.Close() + if err := <-done; err != nil { + t.Fatal(err) + } + }() + + // First, symbolize all PCs one-by-one. + input := bufio.NewWriter(inputw) + scanner := bufio.NewScanner(outputr) + var interner Interner + var allPCs []uint64 + var allFrames []Frame + for _, addr := range addresses { + frames, err := symbolize(&interner, input, scanner, []uint64{addr.pc}) + if err != nil { + t.Fatalf("got error: %v", err) + } + if !reflect.DeepEqual(addr.frames, frames) { + t.Fatalf("want frames:\n%+v\ngot:\n%+v", addr.frames, frames) + } + allPCs = append(allPCs, addr.pc) + allFrames = append(allFrames, frames...) + } + + // Symbolize PCs in 2 groups. + for i := 0; i <= len(addresses); i++ { + frames, err := symbolize(&interner, input, scanner, allPCs[:i]) + if err != nil { + t.Fatalf("got error: %v", err) + } + frames2, err := symbolize(&interner, input, scanner, allPCs[i:]) + if err != nil { + t.Fatalf("got error: %v", err) + } + frames = append(frames, frames2...) + if !reflect.DeepEqual(allFrames, frames) { + t.Fatalf("want frames:\n%+v\ngot:\n%+v", allFrames, frames) + } + } + + // Symbolize a huge pile of PCs (test for pipe overflows). + lots := make([]uint64, 1e4) + for i := range lots { + lots[i] = addresses[0].pc + } + frames, err := symbolize(&interner, input, scanner, lots) + if err != nil { + t.Fatalf("got error: %v", err) + } + if want := len(lots) * len(addresses[0].frames); want != len(frames) { + t.Fatalf("want %v frames, got %v", want, len(frames)) + } +} 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} } diff --git a/pkg/symbolizer/symbolizer_test.go b/pkg/symbolizer/symbolizer_test.go deleted file mode 100644 index f6445367e..000000000 --- a/pkg/symbolizer/symbolizer_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// 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. - -package symbolizer - -import ( - "bufio" - "fmt" - "os" - "reflect" - "strconv" - "testing" -) - -func TestParse(t *testing.T) { - addresses := []struct { - pc uint64 - resp string - frames []Frame - }{ - { - 0xffffffff8180a42e, - "0xffffffff8180a42e\n" + - "__asan_report_load2_noabort\n" + - "mm/kasan/report.c:320\n", - []Frame{ - { - PC: 0xffffffff8180a42e, - Func: "__asan_report_load2_noabort", - File: "mm/kasan/report.c", - Line: 320, - Inline: false, - }, - }, - }, - { - 0xffffffff8180a42d, - "0xffffffff8180a42d\n" + - "kasan_report\n" + - "mm/kasan/report.c:301\n" + - "__asan_report_load2_noabort\n" + - "mm/kasan/report.c:320\n", - []Frame{ - { - PC: 0xffffffff8180a42d, - Func: "kasan_report", - File: "mm/kasan/report.c", - Line: 301, - Inline: true, - }, - { - PC: 0xffffffff8180a42d, - Func: "__asan_report_load2_noabort", - File: "mm/kasan/report.c", - Line: 320, - Inline: false, - }, - }, - }, - { - 0xffffffff82fdbe0b, - "0xffffffff82fdbe0b\n" + - "fbcon_invert_region\n" + - "drivers/video/console/fbcon.c:2750\n", - []Frame{ - { - PC: 0xffffffff82fdbe0b, - Func: "fbcon_invert_region", - File: "drivers/video/console/fbcon.c", - Line: 2750, - Inline: false, - }, - }, - }, - { - 0x123124, - "0x0000000000123124\n" + - "??\n" + - "??:0\n", - nil, - }, - { - 0xffffffffffffffff, - "0xffffffffffffffff\n" + - "??\n" + - "??:0\n", - nil, - }, - { - 0xffffffff81a2aff9, - "0xffffffff81a2aff9\n" + - "devpts_get_priv\n" + - "fs/devpts/inode.c:588 (discriminator 3)\n", - []Frame{ - { - PC: 0xffffffff81a2aff9, - Func: "devpts_get_priv", - File: "fs/devpts/inode.c", - Line: 588, - Inline: false, - }, - }, - }, - } - - // Stub addr2line. - inputr, inputw, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - defer inputr.Close() - defer inputw.Close() - outputr, outputw, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - defer outputr.Close() - done := make(chan error) - go func() { - s := bufio.NewScanner(inputr) - loop: - for s.Scan() { - pc, err := strconv.ParseUint(s.Text(), 0, 64) - if err != nil { - outputw.Close() - done <- fmt.Errorf("got unexpected pc: %v", s.Text()) - return - } - for _, addr := range addresses { - if pc == addr.pc { - outputw.Write([]byte(addr.resp)) - continue loop - } - } - outputw.Close() - done <- fmt.Errorf("got unexpected pc: 0x%x", pc) - return - } - outputw.Write([]byte("DONE\n")) - outputw.Close() - close(done) - }() - defer func() { - inputw.Close() - if err := <-done; err != nil { - t.Fatal(err) - } - }() - - // First, symbolize all PCs one-by-one. - input := bufio.NewWriter(inputw) - scanner := bufio.NewScanner(outputr) - var interner Interner - var allPCs []uint64 - var allFrames []Frame - for _, addr := range addresses { - frames, err := symbolize(&interner, input, scanner, []uint64{addr.pc}) - if err != nil { - t.Fatalf("got error: %v", err) - } - if !reflect.DeepEqual(addr.frames, frames) { - t.Fatalf("want frames:\n%+v\ngot:\n%+v", addr.frames, frames) - } - allPCs = append(allPCs, addr.pc) - allFrames = append(allFrames, frames...) - } - - // Symbolize PCs in 2 groups. - for i := 0; i <= len(addresses); i++ { - frames, err := symbolize(&interner, input, scanner, allPCs[:i]) - if err != nil { - t.Fatalf("got error: %v", err) - } - frames2, err := symbolize(&interner, input, scanner, allPCs[i:]) - if err != nil { - t.Fatalf("got error: %v", err) - } - frames = append(frames, frames2...) - if !reflect.DeepEqual(allFrames, frames) { - t.Fatalf("want frames:\n%+v\ngot:\n%+v", allFrames, frames) - } - } - - // Symbolize a huge pile of PCs (test for pipe overflows). - lots := make([]uint64, 1e4) - for i := range lots { - lots[i] = addresses[0].pc - } - frames, err := symbolize(&interner, input, scanner, lots) - if err != nil { - t.Fatalf("got error: %v", err) - } - if want := len(lots) * len(addresses[0].frames); want != len(frames) { - t.Fatalf("want %v frames, got %v", want, len(frames)) - } -} -- cgit mrf-deployment