mirror of
https://github.com/reactos/syzkaller.git
synced 2025-02-20 11:30:41 +00:00
report: add a function that symbolizes reports
This commit is contained in:
parent
54d923bb5d
commit
9ec6b54fae
104
report/report.go
104
report/report.go
@ -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
|
||||
}
|
||||
|
@ -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
69
symbolizer/nm.go
Normal 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
30
symbolizer/nm_test.go
Normal 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)
|
||||
}
|
||||
}
|
36
tools/syz-report/syz-report.go
Normal file
36
tools/syz-report/syz-report.go
Normal 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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user