// Copyright 2015 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 report import ( "bytes" "fmt" "io/ioutil" "path/filepath" "reflect" "runtime" "strings" "testing" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/symbolizer" "github.com/google/syzkaller/sys/targets" ) func TestLinuxIgnores(t *testing.T) { cfg := &mgrconfig.Config{ Derived: mgrconfig.Derived{ TargetOS: targets.Linux, TargetArch: targets.AMD64, }, } reporter, err := NewReporter(cfg) if err != nil { t.Fatal(err) } cfg.Ignores = []string{"BUG: bug3"} reporter1, err := NewReporter(cfg) if err != nil { t.Fatal(err) } cfg.Ignores = []string{"BUG: bug3", "BUG: bug1"} reporter2, err := NewReporter(cfg) if err != nil { t.Fatal(err) } cfg.Ignores = []string{"BUG: bug3", "BUG: bug1", "BUG: bug2"} reporter3, err := NewReporter(cfg) if err != nil { t.Fatal(err) } const log = ` [ 0.000000] BUG: bug1 [ 0.000000] BUG: bug2 ` if !reporter.ContainsCrash([]byte(log)) { t.Fatalf("no crash") } if rep := reporter.Parse([]byte(log)); rep.Title != "BUG: bug1" { t.Fatalf("want `BUG: bug1`, found `%v`", rep.Title) } if !reporter1.ContainsCrash([]byte(log)) { t.Fatalf("no crash") } if rep := reporter1.Parse([]byte(log)); rep.Title != "BUG: bug1" { t.Fatalf("want `BUG: bug1`, found `%v`", rep.Title) } if !reporter2.ContainsCrash([]byte(log)) { t.Fatalf("no crash") } if rep := reporter2.Parse([]byte(log)); rep.Title != "BUG: bug2" { t.Fatalf("want `BUG: bug2`, found `%v`", rep.Title) } if reporter3.ContainsCrash([]byte(log)) { t.Fatalf("found crash, should be ignored") } if rep := reporter3.Parse([]byte(log)); rep != nil { t.Fatalf("found `%v`, should be ignored", rep.Title) } } func TestLinuxSymbolizeLine(t *testing.T) { tests := []struct { line string result string }{ // Normal symbolization. { "[ 2713.153531] [] foo+0x101/0x185\n", "[ 2713.153531] [] foo+0x101/0x185 foo.c:555\n", }, { "RIP: 0010:[] [] foo+0x101/0x185\n", "RIP: 0010:[] [] foo+0x101/0x185 foo.c:550\n", }, // Strip "./" file prefix. { "[ 2713.153531] [] foo+0x111/0x185\n", "[ 2713.153531] [] foo+0x111/0x185 foo.h:111\n", }, // Needs symbolization, but symbolizer returns nothing. { "[ 2713.153531] [] foo+0x121/0x185\n", "[ 2713.153531] [] foo+0x121/0x185\n", }, // Needs symbolization, but symbolizer returns error. { "[ 2713.153531] [] foo+0x131/0x185\n", "[ 2713.153531] [] foo+0x131/0x185\n", }, // Needs symbolization, but symbol is missing. { "[ 2713.153531] [] bar+0x131/0x185\n", "[ 2713.153531] [] bar+0x131/0x185\n", }, // Bad offset. { "[ 2713.153531] [] bar+0xffffffffffffffffffff/0x185\n", "[ 2713.153531] [] 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 foo.c:555\n", }, // Tricky function name. { " [] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 \n", " [] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 net.c:111 \n", }, // Old KASAN frame format (with tab). { "[ 50.419727] baz+0x101/0x200\n", "[ 50.419727] baz+0x101/0x200 baz.c:100\n", }, // Inlined frames. { " [] foo+0x141/0x185\n", " [] inlined1 net.c:111 [inline]\n" + " [] inlined2 mm.c:222 [inline]\n" + " [] foo+0x141/0x185 kasan.c:333\n", }, // Several symbols with the same name. { "[] baz+0x101/0x200\n", "[] baz+0x101/0x200 baz.c:100\n", }, } symbols := map[string][]symbolizer.Symbol{ "foo": { {Addr: 0x1000000, Size: 0x190}, }, "do_ipv6_setsockopt.isra.7.part.3": { {Addr: 0x2000000, Size: 0x2830}, }, "baz": { {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 0x1000101: return []symbolizer.Frame{ { File: "/linux/foo.c", Line: 550, }, }, 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)) } }) } } func prepareLinuxReporter(t *testing.T, arch string) *linux { config := &config{ target: targets.Get(targets.Linux, arch), } reporter, _, err := ctorLinux(config) if err != nil { t.Errorf("Failed to create a reporter instance %#v", arch) } return reporter.(*linux) } func TestParseLinuxOpcodes(t *testing.T) { type opcodeTest struct { arch string input string output *parsedOpcodes } tests := []opcodeTest{ // LE tests. { arch: targets.AMD64, input: "31 c0 f5 bf f7 ff", output: &parsedOpcodes{ rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xff}, offset: 2, }, }, { arch: targets.AMD64, input: "c031 f7bf fff7 00ff", output: &parsedOpcodes{ rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xf7, 0xff, 0xff, 0x00}, offset: 2, }, }, { arch: targets.AMD64, input: "(33221100) 77665544", output: &parsedOpcodes{ rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, offset: 0, }, }, // BE tests. { arch: targets.S390x, input: "31 c0 f5 bf f7 ff", output: &parsedOpcodes{ rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xff}, offset: 2, }, }, { arch: targets.S390x, input: "31c0 bff5 f7ff ff00", output: &parsedOpcodes{ rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf5, 0xf7, 0xff, 0xff, 0x00}, offset: 2, }, }, { arch: targets.S390x, input: "<00112233> 44556677", output: &parsedOpcodes{ rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, offset: 0, }, }, // ARM Thumb tests. { arch: targets.ARM, input: "0011 (2233) 4455", output: &parsedOpcodes{ rawBytes: []byte{0x11, 0x00, 0x33, 0x22, 0x55, 0x44}, decompileFlags: FlagForceArmThumbMode, offset: 2, }, }, { arch: targets.ARM, input: "(33221100) 77665544", output: &parsedOpcodes{ rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, offset: 0, }, }, // Bad input tests. { arch: targets.AMD64, input: "00 11 22 33", output: nil, }, { arch: targets.AMD64, input: "aa bb zz", output: nil, }, { arch: targets.AMD64, input: "<00> 11 22 <33>", output: nil, }, { arch: targets.AMD64, input: "aa ", output: nil, }, { arch: targets.AMD64, input: "001122 334455", output: nil, }, { arch: targets.AMD64, input: "0011223344556677", output: nil, }, } for idx, test := range tests { test := test // Capturing the value. t.Run(fmt.Sprintf("%s/%v", test.arch, idx), func(t *testing.T) { t.Parallel() reporter := prepareLinuxReporter(t, test.arch) ret, err := reporter.parseOpcodes(test.input) if test.output == nil && err == nil { t.Errorf("Expected an error on input %#v", test) } else if test.output != nil && err != nil { t.Errorf("Unexpected error %v on input %#v", err, test.input) } else if test.output != nil && !reflect.DeepEqual(ret, *test.output) { t.Errorf("Expected: %#v, got: %#v", test.output, ret) } }) } } func TestDisassemblyInReports(t *testing.T) { if runtime.GOOS != targets.Linux { t.Skipf("the test is meant to be run only under Linux") } archPath := filepath.Join("testdata", "linux", "decompile") subFolders, err := ioutil.ReadDir(archPath) if err != nil { t.Fatalf("disassembly reports failed: %v", err) } for _, obj := range subFolders { if !obj.IsDir() { continue } reporter := prepareLinuxReporter(t, obj.Name()) if reporter.target.BrokenCompiler != "" { t.Skip("skipping the test due to broken cross-compiler:\n" + reporter.target.BrokenCompiler) } testPath := filepath.Join(archPath, obj.Name()) testFiles, err := ioutil.ReadDir(testPath) if err != nil { t.Fatalf("failed to list tests for %v: %v", obj.Name(), err) } for _, file := range testFiles { if !strings.HasSuffix(file.Name(), ".in") { continue } filePath := filepath.Join(testPath, strings.TrimSuffix(file.Name(), ".in")) t.Run(obj.Name()+"/"+file.Name(), func(t *testing.T) { testDisassembly(t, reporter, filePath) }) } } } func testDisassembly(t *testing.T, reporter *linux, testFilePrefix string) { t.Parallel() input, err := ioutil.ReadFile(testFilePrefix + ".in") if err != nil { t.Fatalf("failed to read input file: %v", err) } result := reporter.decompileReportOpcodes(input) if *flagUpdate { osutil.WriteFile(testFilePrefix+".out", result) } output, err := ioutil.ReadFile(testFilePrefix + ".out") if err != nil { t.Fatalf("failed to read output file: %v", err) } if !bytes.Equal(output, result) { t.Fatalf("Expected:\n%s\nGot:\n%s\n", output, result) } }