From 017b5dea530573ce49ccc6f09c469a78c7dbaede Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Sat, 17 Jun 2017 12:53:47 +0200 Subject: pkg/symbolizer: move from symbolizer --- pkg/symbolizer/nm.go | 69 +++++++++++++ pkg/symbolizer/nm_test.go | 48 +++++++++ pkg/symbolizer/symbolizer.go | 191 ++++++++++++++++++++++++++++++++++++ pkg/symbolizer/symbolizer_test.go | 184 ++++++++++++++++++++++++++++++++++ pkg/symbolizer/testdata/nm.test.out | Bin 0 -> 8578 bytes 5 files changed, 492 insertions(+) create mode 100644 pkg/symbolizer/nm.go create mode 100644 pkg/symbolizer/nm_test.go create mode 100644 pkg/symbolizer/symbolizer.go create mode 100644 pkg/symbolizer/symbolizer_test.go create mode 100755 pkg/symbolizer/testdata/nm.test.out (limited to 'pkg/symbolizer') diff --git a/pkg/symbolizer/nm.go b/pkg/symbolizer/nm.go new file mode 100644 index 000000000..217536cf1 --- /dev/null +++ b/pkg/symbolizer/nm.go @@ -0,0 +1,69 @@ +// 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" + "bytes" + "os/exec" + "strconv" +) + +type Symbol struct { + Addr uint64 + Size int +} + +// ReadSymbols returns list of text symbols in the binary bin. +func ReadSymbols(bin string) (map[string][]Symbol, error) { + cmd := exec.Command("nm", "-nS", bin) + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + return nil, err + } + defer cmd.Wait() + symbols := make(map[string][]Symbol) + s := bufio.NewScanner(stdout) + text := [][]byte{[]byte(" t "), []byte(" T ")} + for s.Scan() { + // A line looks as: "ffffffff8104db90 0000000000000059 t snb_uncore_msr_enable_box" + ln := s.Bytes() + if bytes.Index(ln, text[0]) == -1 && bytes.Index(ln, text[1]) == -1 { + continue + } + sp1 := bytes.IndexByte(ln, ' ') + if sp1 == -1 { + continue + } + sp2 := bytes.IndexByte(ln[sp1+1:], ' ') + if sp2 == -1 { + continue + } + sp2 += sp1 + 1 + if !bytes.HasPrefix(ln[sp2:], text[0]) && !bytes.HasPrefix(ln[sp2:], text[1]) { + continue + } + addr, err := strconv.ParseUint(string(ln[:sp1]), 16, 64) + if err != nil { + continue + } + size, err := strconv.ParseUint(string(ln[sp1+1:sp2]), 16, 64) + if err != nil { + continue + } + name := string(ln[sp2+len(text[0]):]) + // Note: sizes reported by kernel do not match nm. + // Kernel probably subtracts address of this symbol from address of the next symbol. + // We could do the same, but for now we just round up size to 16. + symbols[name] = append(symbols[name], Symbol{addr, int(size+15) / 16 * 16}) + } + if err := s.Err(); err != nil { + return nil, err + } + return symbols, nil +} diff --git a/pkg/symbolizer/nm_test.go b/pkg/symbolizer/nm_test.go new file mode 100644 index 000000000..5a5723838 --- /dev/null +++ b/pkg/symbolizer/nm_test.go @@ -0,0 +1,48 @@ +// 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 ( + "testing" +) + +func TestSymbols(t *testing.T) { + symbols, err := ReadSymbols("testdata/nm.test.out") + if err != nil { + t.Fatalf("failed to read symbols: %v", err) + } + if len(symbols) != 5 { + t.Fatalf("got %v symbols, want 5", len(symbols)) + } + { + s := symbols["barfoo"] + if len(s) != 1 { + t.Fatalf("got %v barfoo symbols, want 1", len(s)) + } + if s[0].Addr != 0x400507 { + t.Fatalf("bad barfoo address: 0x%x", s[0].Addr) + } + if s[0].Size != 0x30 { + t.Fatalf("bad barfoo size: 0x%x", s[0].Size) + } + } + { + s := symbols["foobar"] + if len(s) != 2 { + t.Fatalf("got %v foobar symbols, want 2", len(s)) + } + if s[0].Addr != 0x4004ed { + t.Fatalf("bad foobar[0] address: 0x%x", s[0].Addr) + } + if s[0].Size != 0x10 { + t.Fatalf("bad foobar[0] size: 0x%x", s[0].Size) + } + if s[1].Addr != 0x4004fa { + t.Fatalf("bad foobar[1] address: 0x%x", s[1].Addr) + } + if s[1].Size != 0x10 { + t.Fatalf("bad foobar[1] size: 0x%x", s[1].Size) + } + } +} diff --git a/pkg/symbolizer/symbolizer.go b/pkg/symbolizer/symbolizer.go new file mode 100644 index 000000000..07356a48a --- /dev/null +++ b/pkg/symbolizer/symbolizer.go @@ -0,0 +1,191 @@ +// 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" +) + +type Symbolizer struct { + subprocs map[string]*subprocess +} + +type Frame struct { + PC uint64 + Func string + File string + Line int + Inline bool +} + +type subprocess struct { + cmd *exec.Cmd + stdin io.Closer + stdout io.Closer + input *bufio.Writer + scanner *bufio.Scanner +} + +func NewSymbolizer() *Symbolizer { + return &Symbolizer{} +} + +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(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 + } + cmd := exec.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(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(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(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: %v", 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: %v", 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: fn, + File: 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/symbolizer_test.go new file mode 100644 index 000000000..c7aeb48cb --- /dev/null +++ b/pkg/symbolizer/symbolizer_test.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. + +package symbolizer + +import ( + "bufio" + "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{ + 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{ + Frame{ + PC: 0xffffffff8180a42d, + Func: "kasan_report", + File: "mm/kasan/report.c", + Line: 301, + Inline: true, + }, + Frame{ + 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{ + 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{ + 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() + go func() { + s := bufio.NewScanner(inputr) + loop: + for s.Scan() { + pc, err := strconv.ParseUint(s.Text(), 0, 64) + if err != nil { + outputw.Close() + t.Fatalf("got unexpected pc: %v", s.Text()) + } + for _, addr := range addresses { + if pc == addr.pc { + outputw.Write([]byte(addr.resp)) + continue loop + } + } + outputw.Close() + t.Fatalf("got unexpected pc: 0x%x", pc) + } + outputw.Write([]byte("DONE\n")) + outputw.Close() + }() + + // First, symbolize all PCs one-by-one. + input := bufio.NewWriter(inputw) + scanner := bufio.NewScanner(outputr) + var allPCs []uint64 + var allFrames []Frame + for _, addr := range addresses { + frames, err := symbolize(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\n", 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(input, scanner, allPCs[:i]) + if err != nil { + t.Fatalf("got error: %v", err) + } + frames2, err := symbolize(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\n", 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(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/testdata/nm.test.out b/pkg/symbolizer/testdata/nm.test.out new file mode 100755 index 000000000..c6ee07240 Binary files /dev/null and b/pkg/symbolizer/testdata/nm.test.out differ -- cgit mrf-deployment