1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
// 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 report contains functions that process kernel output,
// detect/extract crash messages, symbolize them, etc.
package report
import (
"bytes"
"fmt"
"regexp"
"strings"
"github.com/google/syzkaller/pkg/symbolizer"
)
type Reporter interface {
// ContainsCrash searches kernel console output for oops messages.
ContainsCrash(output []byte) bool
// Parse extracts information about oops from console output.
// Returns nil if no oops found.
Parse(output []byte) *Report
// Symbolize symbolizes rep.Report and fills in Maintainers.
Symbolize(rep *Report) error
}
type Report struct {
// Title contains a representative description of the first oops.
Title string
// Report contains whole oops text.
Report []byte
// Output contains whole raw console output as passed to Reporter.Parse.
Output []byte
// StartPos/EndPos denote region of output with oops message(s).
StartPos int
EndPos int
// Corrupted indicates whether the report is truncated of corrupted in some other way.
Corrupted bool
// Maintainers is list of maintainer emails.
Maintainers []string
}
// NewReporter creates reporter for the specified OS:
// kernelSrc: path to kernel sources directory
// kernelObj: path to kernel build directory (can be empty for in-tree build)
// symbols: kernel symbols (result of pkg/symbolizer.ReadSymbols on kernel object file)
// ignores: optional list of regexps to ignore (must match first line of crash message)
func NewReporter(os, kernelSrc, kernelObj string, symbols map[string][]symbolizer.Symbol,
ignores []*regexp.Regexp) (Reporter, error) {
ctor := ctors[os]
if ctor == nil {
return nil, fmt.Errorf("unknown os: %v", os)
}
if kernelObj == "" {
kernelObj = kernelSrc // assume in-tree build
}
return ctor(kernelSrc, kernelObj, symbols, ignores)
}
var ctors = map[string]fn{
"akaros": ctorAkaros,
"linux": ctorLinux,
"freebsd": ctorFreebsd,
"netbsd": ctorNetbsd,
"fuchsia": ctorFuchsia,
"windows": ctorWindows,
}
type fn func(string, string, map[string][]symbolizer.Symbol, []*regexp.Regexp) (Reporter, error)
type oops struct {
header []byte
formats []oopsFormat
suppressions []*regexp.Regexp
}
type oopsFormat struct {
title *regexp.Regexp
report *regexp.Regexp
fmt string
noStackTrace bool
corrupted bool
}
func compile(re string) *regexp.Regexp {
re = strings.Replace(re, "{{ADDR}}", "0x[0-9a-f]+", -1)
re = strings.Replace(re, "{{PC}}", "\\[\\<[0-9a-f]+\\>\\]", -1)
re = strings.Replace(re, "{{FUNC}}", "([a-zA-Z0-9_]+)(?:\\.|\\+)", -1)
re = strings.Replace(re, "{{SRC}}", "([a-zA-Z0-9-_/.]+\\.[a-z]+:[0-9]+)", -1)
return regexp.MustCompile(re)
}
func containsCrash(output []byte, oopses []*oops, ignores []*regexp.Regexp) bool {
for pos := 0; pos < len(output); {
next := bytes.IndexByte(output[pos:], '\n')
if next != -1 {
next += pos
} else {
next = len(output)
}
for _, oops := range oopses {
match := matchOops(output[pos:next], oops, ignores)
if match == -1 {
continue
}
return true
}
pos = next + 1
}
return false
}
func matchOops(line []byte, oops *oops, ignores []*regexp.Regexp) int {
match := bytes.Index(line, oops.header)
if match == -1 {
return -1
}
for _, supp := range oops.suppressions {
if supp.Match(line) {
return -1
}
}
for _, ignore := range ignores {
if ignore.Match(line) {
return -1
}
}
return match
}
func extractDescription(output []byte, oops *oops) (desc string, format oopsFormat) {
startPos := -1
for _, f := range oops.formats {
match := f.title.FindSubmatchIndex(output)
if match == nil {
continue
}
if startPos != -1 && startPos <= match[0] {
continue
}
startPos = match[0]
var args []interface{}
for i := 2; i < len(match); i += 2 {
args = append(args, string(output[match[i]:match[i+1]]))
}
desc = fmt.Sprintf(f.fmt, args...)
format = f
}
if len(desc) == 0 {
pos := bytes.Index(output, oops.header)
if pos == -1 {
return
}
end := bytes.IndexByte(output[pos:], '\n')
if end == -1 {
end = len(output)
} else {
end += pos
}
desc = string(output[pos:end])
}
if len(desc) > 0 && desc[len(desc)-1] == '\r' {
desc = desc[:len(desc)-1]
}
// Corrupted/intermixed lines can be very long.
const maxDescLen = 180
if len(desc) > maxDescLen {
desc = desc[:maxDescLen]
}
return
}
// 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
}
|