aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/symbolizer
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-06-17 12:53:47 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-06-17 14:41:15 +0200
commit017b5dea530573ce49ccc6f09c469a78c7dbaede (patch)
tree34118ae0f8427b2d3a403c472da8cb13c3cf2aec /pkg/symbolizer
parent949ccff832a08acc9bb3a0eadb0a60536b4adb47 (diff)
pkg/symbolizer: move from symbolizer
Diffstat (limited to 'pkg/symbolizer')
-rw-r--r--pkg/symbolizer/nm.go69
-rw-r--r--pkg/symbolizer/nm_test.go48
-rw-r--r--pkg/symbolizer/symbolizer.go191
-rw-r--r--pkg/symbolizer/symbolizer_test.go184
-rwxr-xr-xpkg/symbolizer/testdata/nm.test.outbin0 -> 8578 bytes
5 files changed, 492 insertions, 0 deletions
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
--- /dev/null
+++ b/pkg/symbolizer/testdata/nm.test.out
Binary files differ