report: add a function that symbolizes reports

This commit is contained in:
Dmitry Vyukov 2016-09-01 15:09:39 +02:00
parent 54d923bb5d
commit 9ec6b54fae
5 changed files with 417 additions and 5 deletions

View File

@ -4,10 +4,15 @@
package report
import (
"bufio"
"bytes"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/google/syzkaller/symbolizer"
)
type oops struct {
@ -142,12 +147,17 @@ var oopses = []*oops{
},
}
var consoleOutputRe = regexp.MustCompile("^\\[ *[0-9]+\\.[0-9]+\\] ")
var (
consoleOutputRe = regexp.MustCompile(`^\[ *[0-9]+\.[0-9]+\] `)
questionableRe = regexp.MustCompile(`\[\<[0-9a-f]+\>\] \? +[a-zA-Z0-9_.]+\+0x[0-9a-f]+/[0-9a-f]+`)
symbolizeRe = regexp.MustCompile(`\[\<([0-9a-f]+)\>\] +([a-zA-Z0-9_.]+)\+0x([0-9a-f]+)/0x([0-9a-f]+)`)
eoi = []byte("<EOI>")
)
func compile(re string) *regexp.Regexp {
re = strings.Replace(re, "{{ADDR}}", "0x[0-9a-f]+", -1)
re = strings.Replace(re, "{{PC}}", "\\[\\<[0-9a-z]+\\>\\]", -1)
re = strings.Replace(re, "{{FUNC}}", "([a-zA-Z0-9_]+)(?:\\.(?:constprop|isra)\\.[0-9]+)?\\+", -1)
re = strings.Replace(re, "{{PC}}", "\\[\\<[0-9a-f]+\\>\\]", -1)
re = strings.Replace(re, "{{FUNC}}", "([a-zA-Z0-9_]+)(?:\\.|\\+)", -1)
return regexp.MustCompile(re)
}
@ -199,7 +209,8 @@ func Parse(output []byte) (desc, text string, start int, end int) {
end = next
}
if oops != nil {
if consoleOutputRe.Match(output[pos:next]) {
if consoleOutputRe.Match(output[pos:next]) &&
(!questionableRe.Match(output[pos:next]) || bytes.Index(output[pos:next], eoi) != -1) {
lineStart := bytes.Index(output[pos:next], []byte("] ")) + pos + 2
lineEnd := next
if lineEnd != 0 && output[lineEnd-1] == '\r' {
@ -246,3 +257,88 @@ func extractDescription(output []byte, oops *oops) string {
}
return string(output[pos:end])
}
func Symbolize(vmlinux, text string) (string, error) {
var symbolized []byte
symbols, err := symbolizer.ReadSymbols(vmlinux)
if err != nil {
return "", err
}
symb := symbolizer.NewSymbolizer()
symbFunc := func(bin string, pc uint64) ([]symbolizer.Frame, error) {
return symb.Symbolize(bin, pc)
}
strip, _ := filepath.Abs(vmlinux)
strip = filepath.Dir(strip) + string(filepath.Separator)
s := bufio.NewScanner(strings.NewReader(text))
for s.Scan() {
line := append([]byte{}, s.Bytes()...)
line = append(line, '\n')
line = symbolizeLine(symbFunc, symbols, vmlinux, strip, line)
symbolized = append(symbolized, line...)
}
return string(symbolized), nil
}
func symbolizeLine(symbFunc func(bin string, pc uint64) ([]symbolizer.Frame, error), symbols map[string][]symbolizer.Symbol, vmlinux, strip string, line []byte) []byte {
match := symbolizeRe.FindSubmatchIndex(line)
if match == nil {
return line
}
fn := line[match[4]:match[5]]
off, err := strconv.ParseUint(string(line[match[6]:match[7]]), 16, 64)
if err != nil {
return line
}
size, err := strconv.ParseUint(string(line[match[8]:match[9]]), 16, 64)
if err != nil {
return line
}
symb := symbols[string(fn)]
if len(symb) == 0 {
return line
}
var funcStart uint64
for _, s := range symb {
if funcStart == 0 || int(size) == s.Size {
funcStart = s.Addr
}
}
frames, err := symbFunc(vmlinux, funcStart+off-1)
if err != nil || len(frames) == 0 {
return line
}
var symbolized []byte
for _, frame := range frames {
file := frame.File
if strings.HasPrefix(file, strip) {
file = file[len(strip):]
}
if strings.HasPrefix(file, "./") {
file = file[2:]
}
info := fmt.Sprintf(" %v:%v", file, frame.Line)
modified := append([]byte{}, line...)
modified = replace(modified, match[9], match[9], []byte(info))
if frame.Inline {
modified = replace(modified, match[4], match[9], []byte(frame.Func))
modified = replace(modified, match[2], match[3], []byte(" inline "))
}
symbolized = append(symbolized, modified...)
}
return symbolized
}
// replace replaces [start:end] in where with what, inplace.
func replace(where []byte, start, end int, what []byte) []byte {
if len(what) >= end-start {
where = append(where, what[end-start:]...)
copy(where[start+len(what):], where[end:])
copy(where[start:], what)
} else {
copy(where[start+len(what):], where[end:])
where = where[:len(where)-(end-start-len(what))]
copy(where[start:], what)
}
return where
}

View File

@ -4,8 +4,11 @@
package report
import (
"fmt"
"strings"
"testing"
"github.com/google/syzkaller/symbolizer"
)
func TestParse(t *testing.T) {
@ -95,7 +98,7 @@ unrelateed line
`: `BUG: unable to handle kernel NULL pointer dereference in __lock_acquire`,
`
[ 50.583499] WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 shm_open+0x74/0x80()
[ 50.583499] WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 shm_open.isra.5.part.6+0x74/0x80
[ 50.583499] Modules linked in:
`: `WARNING in shm_open`,
@ -336,3 +339,181 @@ BUG UNIX (Not tainted): kasan: bad access detected
}
}
}
func TestReplace(t *testing.T) {
tests := []struct {
where string
start int
end int
what string
result string
}{
{"0123456789", 3, 5, "abcdef", "012abcdef56789"},
{"0123456789", 3, 5, "ab", "012ab56789"},
{"0123456789", 3, 3, "abcd", "012abcd3456789"},
{"0123456789", 0, 2, "abcd", "abcd23456789"},
{"0123456789", 0, 0, "ab", "ab0123456789"},
{"0123456789", 10, 10, "ab", "0123456789ab"},
{"0123456789", 8, 10, "ab", "01234567ab"},
{"0123456789", 5, 5, "", "0123456789"},
{"0123456789", 3, 8, "", "01289"},
{"0123456789", 3, 8, "ab", "012ab89"},
{"0123456789", 0, 5, "a", "a56789"},
{"0123456789", 5, 10, "ab", "01234ab"},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%+v", test), func(t *testing.T) {
result := replace([]byte(test.where), test.start, test.end, []byte(test.what))
if test.result != string(result) {
t.Errorf("want '%v', got '%v'", test.result, string(result))
}
})
}
}
func TestSymbolizeLine(t *testing.T) {
tests := []struct {
line string
result string
}{
// Normal symbolization.
{
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x101/0x185\n",
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x101/0x185 foo.c:555\n",
},
{
"RIP: 0010:[<ffffffff8188c0e6>] [<ffffffff8188c0e6>] foo+0x101/0x185\n",
"RIP: 0010:[<ffffffff8188c0e6>] [<ffffffff8188c0e6>] foo+0x101/0x185 foo.c:555\n",
},
// Strip "./" file prefix.
{
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x111/0x185\n",
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x111/0x185 foo.h:111\n",
},
// Needs symbolization, but symbolizer returns nothing.
{
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x121/0x185\n",
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x121/0x185\n",
},
// Needs symbolization, but symbolizer returns error.
{
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x131/0x185\n",
"[ 2713.153531] [<ffffffff82d1b1d9>] foo+0x131/0x185\n",
},
// Needs symbolization, but symbol is missing.
{
"[ 2713.153531] [<ffffffff82d1b1d9>] bar+0x131/0x185\n",
"[ 2713.153531] [<ffffffff82d1b1d9>] bar+0x131/0x185\n",
},
// Bad offset.
{
"[ 2713.153531] [<ffffffff82d1b1d9>] bar+0xffffffffffffffffffff/0x185\n",
"[ 2713.153531] [<ffffffff82d1b1d9>] bar+0xffffffffffffffffffff/0x185\n",
},
// Should not be symbolized.
{
"WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 foo+0x101/0x185\n",
"WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 foo+0x101/0x185\n",
},
// Tricky function name.
{
" [<ffffffff84e5bea0>] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 \n",
" [<ffffffff84e5bea0>] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 net.c:111 \n",
},
// Inlined frames.
{
" [<ffffffff84e5bea0>] foo+0x141/0x185\n",
" [< inline >] inlined1 net.c:111\n" +
" [< inline >] inlined2 mm.c:222\n" +
" [<ffffffff84e5bea0>] foo+0x141/0x185 kasan.c:333\n",
},
// Several symbols with the same name.
{
"[<ffffffff82d1b1d9>] baz+0x101/0x200\n",
"[<ffffffff82d1b1d9>] baz+0x101/0x200 baz.c:100\n",
},
}
symbols := map[string][]symbolizer.Symbol{
"foo": []symbolizer.Symbol{
{Addr: 0x1000000, Size: 0x190},
},
"do_ipv6_setsockopt.isra.7.part.3": []symbolizer.Symbol{
{Addr: 0x2000000, Size: 0x2830},
},
"baz": []symbolizer.Symbol{
{Addr: 0x3000000, Size: 0x100},
{Addr: 0x4000000, Size: 0x200},
{Addr: 0x5000000, Size: 0x300},
},
}
symb := func(bin string, pc uint64) ([]symbolizer.Frame, error) {
if bin != "vmlinux" {
return nil, fmt.Errorf("unknown pc 0x%x", pc)
}
switch pc {
case 0x1000100:
return []symbolizer.Frame{
{
File: "/linux/foo.c",
Line: 555,
},
}, nil
case 0x1000110:
return []symbolizer.Frame{
{
File: "/linux/./foo.h",
Line: 111,
},
}, nil
case 0x1000120:
return nil, nil
case 0x1000130:
return nil, fmt.Errorf("unknown pc 0x%x", pc)
case 0x2000100:
return []symbolizer.Frame{
{
File: "/linux/net.c",
Line: 111,
},
}, nil
case 0x1000140:
return []symbolizer.Frame{
{
Func: "inlined1",
File: "/linux/net.c",
Line: 111,
Inline: true,
},
{
Func: "inlined2",
File: "/linux/mm.c",
Line: 222,
Inline: true,
},
{
Func: "noninlined3",
File: "/linux/kasan.c",
Line: 333,
Inline: false,
},
}, nil
case 0x4000100:
return []symbolizer.Frame{
{
File: "/linux/baz.c",
Line: 100,
},
}, nil
default:
return nil, fmt.Errorf("unknown pc 0x%x", pc)
}
}
for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
result := symbolizeLine(symb, symbols, "vmlinux", "/linux/", []byte(test.line))
if test.result != string(result) {
t.Errorf("want %q\n\t get %q", test.result, string(result))
}
})
}
}

69
symbolizer/nm.go Normal file
View File

@ -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
}

30
symbolizer/nm_test.go Normal file
View File

@ -0,0 +1,30 @@
// 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 (
"os"
"testing"
)
func TestSymbols(t *testing.T) {
symbols, err := ReadSymbols(os.Args[0])
if err != nil {
t.Fatalf("failed to read symbols: %v", err)
}
t.Logf("Read %v symbols", len(symbols))
s, ok := symbols["github.com/google/syzkaller/symbolizer.TestSymbols"]
if !ok {
t.Fatalf("symbols don't contain this function")
}
if len(s) != 1 {
t.Fatalf("more than 1 symbol: %v", len(s))
}
if s[0].Addr == 0 {
t.Fatalf("symbol address is 0")
}
if s[0].Size <= 10 || s[0].Size > 1<<20 {
t.Fatalf("bogus symbol size: %v", s[0].Size)
}
}

View File

@ -0,0 +1,36 @@
// 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 main
import (
"fmt"
"io/ioutil"
"os"
"github.com/google/syzkaller/report"
)
func main() {
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "usage: syz-report vmlinux report (args %+v)\n", os.Args)
os.Exit(1)
}
output, err := ioutil.ReadFile(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read report file: %v\n", err)
os.Exit(1)
}
desc, text, _, _ := report.Parse(output)
if desc == "" {
fmt.Fprintf(os.Stderr, "report file does not contain a crash\n")
os.Exit(1)
}
symbolized, err := report.Symbolize(os.Args[1], text)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to symbolize report: %v\n", err)
} else {
text = symbolized
}
fmt.Printf("%v\n\n%v", desc, text)
}