| // Copyright 2017 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 ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "path/filepath" |
| "regexp" |
| "strconv" |
| "strings" |
| |
| "github.com/google/syzkaller/pkg/symbolizer" |
| "github.com/ianlancetaylor/demangle" |
| ) |
| |
| type fuchsia struct { |
| *config |
| obj string |
| } |
| |
| var ( |
| zirconRIP = regexp.MustCompile(` RIP: (0x[0-9a-f]+) `) |
| zirconBT = regexp.MustCompile(`^bt#[0-9]+: (0x[0-9a-f]+)`) |
| zirconReportEnd = []byte("Halted") |
| zirconAssertFailed = []byte("ASSERT FAILED at") |
| zirconLinePrefix = regexp.MustCompile(`^\[\d+\.\d+\] \d+\.\d+> `) |
| zirconUnrelated = []*regexp.Regexp{ |
| regexp.MustCompile(`^$`), |
| regexp.MustCompile(`stopping other cpus`), |
| regexp.MustCompile(`^halting cpu`), |
| regexp.MustCompile(`^dso: `), |
| regexp.MustCompile(`^UPTIME: `), |
| regexp.MustCompile(`^BUILDID `), |
| regexp.MustCompile(`^Halting\.\.\.`), |
| } |
| ) |
| |
| func ctorFuchsia(cfg *config) (Reporter, []string, error) { |
| ctx := &fuchsia{ |
| config: cfg, |
| } |
| if ctx.kernelObj != "" { |
| ctx.obj = filepath.Join(ctx.kernelObj, ctx.target.KernelObject) |
| } |
| suppressions := []string{ |
| "fatal exception: process /tmp/syz-fuzzer", // OOM presumably |
| } |
| return ctx, suppressions, nil |
| } |
| |
| func (ctx *fuchsia) ContainsCrash(output []byte) bool { |
| return containsCrash(output, zirconOopses, ctx.ignores) |
| } |
| |
| func (ctx *fuchsia) Parse(output []byte) *Report { |
| // We symbolize here because zircon output does not contain even function names. |
| symbolized := ctx.symbolize(output) |
| rep := simpleLineParser(symbolized, zirconOopses, zirconStackParams, ctx.ignores) |
| if rep == nil { |
| return nil |
| } |
| rep.Output = output |
| if report := ctx.shortenReport(rep.Report); len(report) != 0 { |
| rep.Report = report |
| } |
| return rep |
| } |
| |
| func (ctx *fuchsia) shortenReport(report []byte) []byte { |
| out := new(bytes.Buffer) |
| for s := bufio.NewScanner(bytes.NewReader(report)); s.Scan(); { |
| line := zirconLinePrefix.ReplaceAll(s.Bytes(), nil) |
| if matchesAny(line, zirconUnrelated) { |
| continue |
| } |
| if bytes.Contains(line, zirconReportEnd) { |
| break |
| } |
| out.Write(line) |
| out.WriteByte('\n') |
| } |
| return out.Bytes() |
| } |
| |
| func (ctx *fuchsia) symbolize(output []byte) []byte { |
| symb := symbolizer.NewSymbolizer() |
| defer symb.Close() |
| out := new(bytes.Buffer) |
| for s := bufio.NewScanner(bytes.NewReader(output)); s.Scan(); { |
| line := s.Bytes() |
| if bytes.Contains(line, zirconAssertFailed) && len(line) == 127 { |
| // This is super hacky: but zircon splits the most important information in long assert lines |
| // (and they are always long) into several lines in irreversible way. Try to restore full line. |
| line = append([]byte{}, line...) |
| if s.Scan() { |
| line = append(line, s.Bytes()...) |
| } |
| } |
| if ctx.obj != "" { |
| if match := zirconRIP.FindSubmatchIndex(line); match != nil { |
| if ctx.processPC(out, symb, line, match, false) { |
| continue |
| } |
| } else if match := zirconBT.FindSubmatchIndex(line); match != nil { |
| if ctx.processPC(out, symb, line, match, true) { |
| continue |
| } |
| } |
| } |
| out.Write(line) |
| out.WriteByte('\n') |
| } |
| return out.Bytes() |
| } |
| |
| func (ctx *fuchsia) processPC(out *bytes.Buffer, symb *symbolizer.Symbolizer, |
| line []byte, match []int, call bool) bool { |
| prefix := line[match[0]:match[1]] |
| pcStart := match[2] - match[0] |
| pcEnd := match[3] - match[0] |
| pcStr := prefix[pcStart:pcEnd] |
| pc, err := strconv.ParseUint(string(pcStr), 0, 64) |
| if err != nil { |
| return false |
| } |
| shortPC := pc & 0xfffffff |
| pc = 0xffffffff80000000 | shortPC |
| if call { |
| pc-- |
| } |
| frames, err := symb.Symbolize(ctx.obj, pc) |
| if err != nil || len(frames) == 0 { |
| return false |
| } |
| for _, frame := range frames { |
| file := ctx.trimFile(frame.File) |
| name := demangle.Filter(frame.Func, demangle.NoParams, demangle.NoTemplateParams) |
| if strings.Contains(name, "<lambda(") { |
| // demangle produces super long (full) names for lambdas. |
| name = "lambda" |
| } |
| id := "[ inline ]" |
| if !frame.Inline { |
| id = fmt.Sprintf("0x%08x", shortPC) |
| } |
| start := replace(append([]byte{}, prefix...), pcStart, pcEnd, []byte(id)) |
| fmt.Fprintf(out, "%s %v %v:%v\n", start, name, file, frame.Line) |
| } |
| return true |
| } |
| |
| func (ctx *fuchsia) trimFile(file string) string { |
| const ( |
| prefix1 = "zircon/kernel/" |
| prefix2 = "zircon/" |
| ) |
| if pos := strings.LastIndex(file, prefix1); pos != -1 { |
| return file[pos+len(prefix1):] |
| } |
| if pos := strings.LastIndex(file, prefix2); pos != -1 { |
| return file[pos+len(prefix2):] |
| } |
| return file |
| } |
| |
| func (ctx *fuchsia) Symbolize(rep *Report) error { |
| // We symbolize in Parse because zircon stacktraces don't contain even function names. |
| return nil |
| } |
| |
| var zirconStackParams = &stackParams{ |
| frameRes: []*regexp.Regexp{ |
| compile(` RIP: 0x[0-9a-f]{8} +([a-zA-Z0-9_:~]+)`), |
| compile(` RIP: \[ inline \] +([a-zA-Z0-9_:~]+)`), |
| compile(`^bt#[0-9]+: 0x[0-9a-f]{8} +([a-zA-Z0-9_:~]+)`), |
| compile(`^bt#[0-9]+: \[ inline \] +([a-zA-Z0-9_:~]+)`), |
| }, |
| skipPatterns: []string{ |
| "^platform_halt$", |
| "^exception_die$", |
| "^_panic$", |
| }, |
| } |
| |
| var zirconOopses = []*oops{ |
| { |
| []byte("ZIRCON KERNEL PANIC"), |
| []oopsFormat{ |
| { |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*ASSERT FAILED(?:.*\\n)+?.*bt#00:"), |
| fmt: "ASSERT FAILED in %[1]v", |
| stack: &stackFmt{ |
| parts: []*regexp.Regexp{ |
| parseStackTrace, |
| }, |
| }, |
| }, |
| { |
| // Some debug asserts don't contain stack trace. |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*ASSERT FAILED at \\(.+?\\): (.*)"), |
| fmt: "ASSERT FAILED: %[1]v", |
| noStackTrace: true, |
| }, |
| { |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*double fault, halting(?:.*\\n)+?.*bt#00:"), |
| fmt: "double fault in %[1]v", |
| stack: &stackFmt{ |
| parts: []*regexp.Regexp{ |
| parseStackTrace, |
| }, |
| }, |
| }, |
| { |
| // Some double faults don't contain stack trace. |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*double fault, halting"), |
| fmt: "double fault", |
| noStackTrace: true, |
| }, |
| { |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*Supervisor Page Fault exception, halting"), |
| fmt: "Supervisor Fault in %[1]v", |
| stack: &stackFmt{ |
| parts: []*regexp.Regexp{ |
| parseStackTrace, |
| }, |
| }, |
| }, |
| { |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*recursion in interrupt handler"), |
| fmt: "recursion in interrupt handler in %[1]v", |
| stack: &stackFmt{ |
| parts: []*regexp.Regexp{ |
| parseStackTrace, |
| }, |
| }, |
| }, |
| { |
| title: compile("ZIRCON KERNEL PANIC(?:.*\\n)+?.*KVM internal error"), |
| fmt: "KVM internal error", |
| noStackTrace: true, |
| }, |
| { |
| title: compile("ZIRCON KERNEL PANIC"), |
| fmt: "KERNEL PANIC in %[1]v", |
| stack: &stackFmt{ |
| parts: []*regexp.Regexp{ |
| parseStackTrace, |
| }, |
| }, |
| }, |
| }, |
| []*regexp.Regexp{}, |
| }, |
| { |
| []byte("recursion in interrupt handler"), |
| []oopsFormat{ |
| { |
| title: compile("recursion in interrupt handler(?:.*\\n)+?.*(?:bt#00:|RIP:)"), |
| fmt: "recursion in interrupt handler in %[1]v", |
| stack: &stackFmt{ |
| parts: []*regexp.Regexp{ |
| parseStackTrace, |
| }, |
| }, |
| }, |
| { |
| title: compile("recursion in interrupt handler"), |
| fmt: "recursion in interrupt handler", |
| noStackTrace: true, |
| }, |
| }, |
| []*regexp.Regexp{}, |
| }, |
| // We should detect just "stopping other cpus" as some kernel crash rather then as "lost connection", |
| // but if we add oops for "stopping other cpus", then it will interfere with other formats, |
| // because "stopping other cpus" usually goes after "ZIRCON KERNEL PANIC", but sometimes before. Mess. |
| //{ |
| // []byte("stopping other cpus"), |
| //}, |
| { |
| []byte("welcome to Zircon"), |
| []oopsFormat{ |
| { |
| title: compile("welcome to Zircon"), |
| fmt: unexpectedKernelReboot, |
| noStackTrace: true, |
| }, |
| }, |
| []*regexp.Regexp{}, |
| }, |
| { |
| []byte("KVM internal error"), |
| []oopsFormat{ |
| { |
| title: compile("KVM internal error"), |
| fmt: "KVM internal error", |
| noStackTrace: true, |
| }, |
| }, |
| []*regexp.Regexp{}, |
| }, |
| { |
| []byte("<== fatal exception"), |
| []oopsFormat{ |
| { |
| title: compile("<== fatal exception"), |
| report: compile("<== fatal exception: process ([a-zA-Z0-9_/-]+)"), |
| fmt: "fatal exception in %[1]v", |
| noStackTrace: true, |
| }, |
| }, |
| []*regexp.Regexp{ |
| compile("<== fatal exception: process .+?syz.+?\\["), |
| }, |
| }, |
| { |
| // Panics in Go services. |
| []byte("panic: "), |
| []oopsFormat{ |
| { |
| title: compile("panic: .*"), |
| report: compile("panic: (.*)(?:.*\\n)+?.* goroutine"), |
| fmt: "panic: %[1]v", |
| noStackTrace: true, |
| }, |
| }, |
| []*regexp.Regexp{}, |
| }, |
| } |