blob: ebe790e783d50ecbfb827979667514471d57a309 [file] [log] [blame]
// Copyright 2018 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"
"regexp"
)
type gvisor struct {
*config
}
func ctorGvisor(cfg *config) (Reporter, []string, error) {
ctx := &gvisor{
config: cfg,
}
suppressions := []string{
"fatal error: runtime: out of memory",
"fatal error: runtime: cannot allocate memory",
"fatal error: newosproc",
"panic: ptrace sysemu failed: no such process", // OOM kill
`panic: ptrace (s|g)et fpregs.* failed: no such process`, // OOM kill
`panic: ptrace (s|g)et regs.* failed: no such process`, // OOM kill
"panic: error initializing first thread: resource temporarily unavailable", // PID exhaustion
"panic: unable to activate mm: creating stub process: resource temporarily unavailable", // PID exhaustion
"panic: executor failed: pthread_create failed", // PID exhaustion
"panic: failed to start executor binary",
"panic: error mapping run data: error mapping runData: cannot allocate memory",
"race: limit on 8128 simultaneously alive goroutines is exceeded, dying",
"ERROR: ThreadSanitizer", // Go race failing due to OOM.
"FATAL: ThreadSanitizer",
}
return ctx, suppressions, nil
}
func (ctx *gvisor) ContainsCrash(output []byte) bool {
return containsCrash(output, gvisorOopses, ctx.ignores)
}
func (ctx *gvisor) Parse(output []byte) *Report {
rep := simpleLineParser(output, gvisorOopses, nil, ctx.ignores)
if rep == nil {
return nil
}
rep.Title = replaceTable(gvisorTitleReplacement, rep.Title)
rep.Report = ctx.shortenReport(rep.Report)
return rep
}
func (ctx *gvisor) shortenReport(report []byte) []byte {
// gvisor panics include stacks of all goroutines.
// This output is too lengthy for report and not very useful.
// So we always take 5 lines from report and then cut it at the next empty line.
// The intention is to capture panic header and traceback of the first goroutine.
pos := 0
for i := 0; i < 5; i++ {
pos1 := bytes.IndexByte(report[pos:], '\n')
if pos1 == -1 {
return report
}
pos += pos1 + 1
}
end := bytes.Index(report[pos:], []byte{'\n', '\n'})
if end == -1 {
return report
}
if bytes.Contains(report, []byte("WARNING: DATA RACE")) {
// For data races extract both stacks.
end2 := bytes.Index(report[pos+end+2:], []byte{'\n', '\n'})
if end2 != -1 {
end += end2 + 2
}
}
return report[:pos+end+1]
}
func (ctx *gvisor) Symbolize(rep *Report) error {
return nil
}
var gvisorTitleReplacement = []replacement{
{
regexp.MustCompile(`container ".*"`),
"container NAME",
},
{
regexp.MustCompile(`sandbox ".*"`),
"sandbox NAME",
},
{
regexp.MustCompile(`(pid|PID) [0-9]+`),
"pid X",
},
}
var gvisorOopses = append([]*oops{
{
[]byte("Panic:"),
[]oopsFormat{
{
title: compile("Panic:(.*)"),
fmt: "Panic:%[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
{
[]byte("fatal error:"),
[]oopsFormat{
{
title: compile("fatal error:(.*)"),
fmt: "fatal error:%[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
{
[]byte("SIGSEGV:"),
[]oopsFormat{
{
title: compile("SIGSEGV:(.*)"),
fmt: "SIGSEGV:%[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
{
[]byte("SIGBUS:"),
[]oopsFormat{
{
title: compile("SIGBUS:(.*)"),
fmt: "SIGBUS:%[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
{
[]byte("FATAL ERROR:"),
[]oopsFormat{
{
title: compile("FATAL ERROR:(.*)"),
fmt: "FATAL ERROR:%[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
{
[]byte("WARNING: DATA RACE"),
[]oopsFormat{
{
title: compile("WARNING: DATA RACE"),
report: compile("WARNING: DATA RACE\n(?:.*\n)*? (?:[a-zA-Z0-9./-_]+/)([a-zA-Z0-9.()*_]+)\\(\\)\n"),
fmt: "DATA RACE in %[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
{
[]byte("Invalid request partialResult"),
[]oopsFormat{
{
title: compile("Invalid request partialResult"),
report: compile("Invalid request partialResult .* for (.*) operation"),
fmt: "Invalid request partialResult in %[1]v",
noStackTrace: true,
},
},
[]*regexp.Regexp{},
},
}, commonOopses...)