OLD | NEW |
1 package data | 1 package data |
2 | 2 |
3 import ( | 3 import ( |
4 "fmt" | 4 "fmt" |
| 5 "regexp" |
| 6 "sort" |
5 "strings" | 7 "strings" |
| 8 |
| 9 "go.skia.org/infra/fuzzer/go/common" |
6 ) | 10 ) |
7 | 11 |
8 // Represents the metadata about a crash, hopefully easing debugging. | 12 // Represents the metadata about a crash, hopefully easing debugging. |
9 type FuzzResult struct { | 13 type FuzzResult struct { |
10 » DebugStackTrace StackTrace | 14 » Debug BuildData |
11 » ReleaseStackTrace StackTrace | 15 » Release BuildData |
12 » DebugDump string | |
13 » ReleaseDump string | |
14 » DebugStdErr string | |
15 » ReleaseStdErr string | |
16 » Flags FuzzFlag | |
17 } | 16 } |
18 | 17 |
19 // A bit mask representing what happened when a fuzz ran against a debug version
of Skia and a release version | 18 // BuildData represents the results of parsing a given skia build's output. |
| 19 type BuildData struct { |
| 20 » OutputFiles |
| 21 » StackTrace StackTrace |
| 22 » Flags FuzzFlag |
| 23 } |
| 24 |
| 25 // OutputFiles are the files output by the analysis |
| 26 type OutputFiles struct { |
| 27 » Asan string |
| 28 » Dump string |
| 29 » StdErr string |
| 30 } |
| 31 |
| 32 // GCSPackage is a struct containing all the pieces of a fuzz that exist in Goog
le Storage. |
| 33 type GCSPackage struct { |
| 34 » Name string |
| 35 » FuzzCategory string |
| 36 » Debug OutputFiles |
| 37 » Release OutputFiles |
| 38 } |
| 39 |
| 40 // A bit mask representing what happened when a fuzz ran against Skia. |
20 type FuzzFlag int | 41 type FuzzFlag int |
21 | 42 |
22 const ( | 43 const ( |
23 » DebugCrashed FuzzFlag = 1 << iota | 44 » TerminatedGracefully FuzzFlag = 1 << iota |
24 » ReleaseCrashed | 45 » ClangCrashed |
25 » DebugFailedGracefully | 46 » ASANCrashed |
26 » ReleaseFailedGracefully | 47 » AssertionViolated |
27 » DebugAssertionViolated | 48 » BadAlloc |
28 » DebugBadAlloc | 49 » NoStackTrace |
29 » ReleaseBadAlloc | 50 » SKAbortHit |
30 » DebugTimedOut | 51 » TimedOut |
31 » ReleaseTimedOut | 52 » Other |
32 » DebugNoStackTrace | 53 |
33 » ReleaseNoStackTrace | 54 » ASAN_GlobalBufferOverflow |
34 » DebugOther | 55 » ASAN_HeapBufferOverflow |
35 » ReleaseOther | 56 » ASAN_StackBufferOverflow |
| 57 » ASAN_HeapUseAfterFree |
| 58 |
| 59 » SKPICTURE_DuringRendering |
36 ) | 60 ) |
37 | 61 |
38 var flagNames = []string{ | 62 var flagNames = []string{ |
39 » "DebugCrashed", | 63 » "FailedGracefully", |
40 » "ReleaseCrashed", | 64 » "ClangCrashed", |
41 » "DebugFailedGracefully", | 65 » "ASANCrashed", |
42 » "ReleaseFailedGracefully", | 66 » "AssertionViolated", |
43 » "DebugAssertionViolated", | 67 » "BadAlloc", |
44 » "DebugBadAlloc", | 68 » "NoStackTrace", |
45 » "ReleaseBadAlloc", | 69 » "SKAbortHit", |
46 » "DebugTimedOut", | 70 » "TimedOut", |
47 » "ReleaseTimedOut", | 71 » "Other", |
48 » "DebugNoStackTrace", | 72 |
49 » "ReleaseNoStackTrace", | 73 » "ASAN_global-buffer-overflow", |
50 » "DebugOther", | 74 » "ASAN_heap-buffer-overflow", |
51 » "ReleaseOther", | 75 » "ASAN_stack-buffer-overflow", |
| 76 » "ASAN_heap-use-after-free", |
| 77 |
| 78 » "SKPICTURE_DuringRendering", |
52 } | 79 } |
53 | 80 |
| 81 // ToHumanReadableFlags creates a sorted slice of strings that represents the fl
ags. The slice |
| 82 // is sorted by unicode points, as per sort.Strings(). |
54 func (f FuzzFlag) ToHumanReadableFlags() []string { | 83 func (f FuzzFlag) ToHumanReadableFlags() []string { |
55 flags := make([]string, 0) | 84 flags := make([]string, 0) |
56 i := 0 | 85 i := 0 |
57 for mask := 1; mask < (2 << 16); mask *= 2 { | 86 for mask := 1; mask < (2 << 16); mask *= 2 { |
58 if int(f)&mask != 0 { | 87 if int(f)&mask != 0 { |
59 flags = append(flags, flagNames[i]) | 88 flags = append(flags, flagNames[i]) |
60 } | 89 } |
61 i++ | 90 i++ |
62 } | 91 } |
| 92 // Front end filtering logic will expect the flags to be in alphabetical
order. |
| 93 sort.Strings(flags) |
63 return flags | 94 return flags |
64 } | 95 } |
65 | 96 |
66 func (f FuzzFlag) String() string { | 97 func (f FuzzFlag) String() string { |
67 return fmt.Sprintf("FuzzFlag: %016b (%d) [%s]", f, f, strings.Join(f.ToH
umanReadableFlags(), " | ")) | 98 return fmt.Sprintf("FuzzFlag: %016b (%d) [%s]", f, f, strings.Join(f.ToH
umanReadableFlags(), " | ")) |
68 } | 99 } |
69 | 100 |
70 func ParseFuzzResult(debugDump, debugErr, releaseDump, releaseErr string) FuzzRe
sult { | 101 // ParseGCSPackage parses the results of analysis of a fuzz and creates a FuzzRe
sult with it. |
71 » result := FuzzResult{ | 102 // This includes parsing the stacktraces and computing the flags about the fuzz. |
72 » » DebugDump: debugDump, | 103 func ParseGCSPackage(g GCSPackage) FuzzResult { |
73 » » DebugStackTrace: ParseStackTrace(debugDump), | 104 » result := FuzzResult{} |
74 » » DebugStdErr: debugErr, | 105 » result.Debug.Asan = g.Debug.Asan |
75 » » ReleaseDump: releaseDump, | 106 » result.Debug.Dump = g.Debug.Dump |
76 » » ReleaseStackTrace: ParseStackTrace(releaseDump), | 107 » result.Debug.StdErr = g.Debug.StdErr |
77 » » ReleaseStdErr: releaseErr, | 108 » result.Debug.StackTrace = getStackTrace(g.Debug.Asan, g.Debug.Dump) |
78 » » Flags: 0, //dummy value, to be updated shortly | 109 » result.Release.Asan = g.Release.Asan |
79 » } | 110 » result.Release.Dump = g.Release.Dump |
80 » result.computeFlags() | 111 » result.Release.StdErr = g.Release.StdErr |
| 112 » result.Release.StackTrace = getStackTrace(g.Release.Asan, g.Release.Dump
) |
| 113 » result.computeFlags(g.FuzzCategory) |
81 | 114 |
82 return result | 115 return result |
83 } | 116 } |
84 | 117 |
85 func (r *FuzzResult) computeFlags() { | 118 // getStackTrace creates a StackTrace output from one of the two dumps given. I
t first tries to |
86 » flags := FuzzFlag(0) | 119 // use the AddressSanitizer dump, with the Clang dump as a fallback. |
| 120 func getStackTrace(asan, dump string) StackTrace { |
| 121 » if asanCrashed(asan) { |
| 122 » » return parseASANStackTrace(asan) |
| 123 » } |
| 124 » return parseCatchsegvStackTrace(dump) |
| 125 } |
87 | 126 |
88 » if r.DebugDump != "" { | 127 // computeFlags parses the raw data to set both the Debug and Release flags. |
89 » » flags |= DebugCrashed | 128 func (r *FuzzResult) computeFlags(category string) { |
90 » » if r.DebugStackTrace.IsEmpty() { | 129 » r.Debug.Flags = parseAll(category, &r.Debug) |
91 » » » flags |= DebugNoStackTrace | 130 » r.Release.Flags = parseAll(category, &r.Release) |
| 131 } |
| 132 |
| 133 // parseAll looks at the three input files and parses the results, based on the
category. The |
| 134 // category allows for specialized flags, like SKPICTURE_DuringRendering. |
| 135 func parseAll(category string, data *BuildData) FuzzFlag { |
| 136 » f := FuzzFlag(0) |
| 137 » // Check for SKAbort message |
| 138 » if strings.Contains(data.Asan, "fatal error") { |
| 139 » » f |= ASANCrashed |
| 140 » » f |= SKAbortHit |
| 141 » » if data.StackTrace.IsEmpty() { |
| 142 » » » data.StackTrace = extractSkAbortTrace(data.StdErr) |
92 } | 143 } |
93 } | 144 } |
94 | 145 » if strings.Contains(data.StdErr, "fatal error") { |
95 » if r.ReleaseDump != "" { | 146 » » f |= ClangCrashed |
96 » » flags |= ReleaseCrashed | 147 » » f |= SKAbortHit |
97 » » if r.ReleaseStackTrace.IsEmpty() { | 148 » » if data.StackTrace.IsEmpty() { |
98 » » » flags |= ReleaseNoStackTrace | 149 » » » data.StackTrace = extractSkAbortTrace(data.StdErr) |
99 } | 150 } |
100 } | 151 } |
101 | 152 » // If no sk abort message and no evidence of crashes, we either terminat
ed gracefully or |
102 » if r.DebugStdErr == "" && r.DebugDump == "" { | 153 » // timed out. |
103 » » flags |= DebugTimedOut | 154 » if f == 0 && !asanCrashed(data.Asan) && !clangDumped(data.Dump) { |
104 » } else if strings.Contains(r.DebugStdErr, "failed assertion") { | 155 » » if strings.Contains(data.Asan, "[terminated]") && strings.Contai
ns(data.StdErr, "[terminated]") { |
105 » » flags |= DebugAssertionViolated | 156 » » » return TerminatedGracefully |
106 » } else if strings.Contains(r.DebugStdErr, `terminate called after throwi
ng an instance of 'std::bad_alloc'`) { | 157 » » } |
107 » » flags |= DebugCrashed | DebugBadAlloc | 158 » » return TimedOut |
108 » } else if strings.Contains(r.DebugStdErr, `Success`) { | |
109 » » flags |= DebugFailedGracefully | |
110 » } else if r.DebugStdErr != "" { | |
111 » » flags |= DebugOther | |
112 } | 159 } |
113 | 160 |
114 » if r.ReleaseStdErr == "" && r.ReleaseDump == "" { | 161 » // Look for clues from the various dumps. |
115 » » flags |= ReleaseTimedOut | 162 » f |= parseAsan(category, data.Asan) |
116 » } else if strings.Contains(r.ReleaseStdErr, `terminate called after thro
wing an instance of 'std::bad_alloc'`) { | 163 » f |= parseCatchsegv(category, data.Dump, data.StdErr) |
117 » » flags |= ReleaseCrashed | ReleaseBadAlloc | 164 » if f == 0 { |
118 » } else if strings.Contains(r.ReleaseStdErr, `Success`) { | 165 » » // I don't know what this means (yet). |
119 » » flags |= ReleaseFailedGracefully | 166 » » return Other |
120 » } else if r.ReleaseStdErr != "" { | 167 » } |
121 » » flags |= ReleaseOther | 168 » if data.StackTrace.IsEmpty() { |
| 169 » » f |= NoStackTrace |
| 170 » } |
| 171 » return f |
| 172 } |
| 173 |
| 174 // parseAsan returns the flags discovered while looking through the AddressSanit
izer output. This |
| 175 // includes things like ASAN_GlobalBufferOverflow. |
| 176 func parseAsan(category, asan string) FuzzFlag { |
| 177 » f := FuzzFlag(0) |
| 178 » if !asanCrashed(asan) { |
| 179 » » return f |
| 180 » } |
| 181 » f |= ASANCrashed |
| 182 » if strings.Contains(asan, "failed assertion") { |
| 183 » » f |= AssertionViolated |
| 184 » } |
| 185 » if strings.Contains(asan, "global-buffer-overflow") { |
| 186 » » f |= ASAN_GlobalBufferOverflow |
| 187 » } |
| 188 » if strings.Contains(asan, "heap-buffer-overflow") { |
| 189 » » f |= ASAN_HeapBufferOverflow |
| 190 » } |
| 191 » if strings.Contains(asan, "stack-buffer-overflow") { |
| 192 » » f |= ASAN_StackBufferOverflow |
| 193 » } |
| 194 » if strings.Contains(asan, "heap-use-after-free") { |
| 195 » » f |= ASAN_HeapUseAfterFree |
| 196 » } |
| 197 » if strings.Contains(asan, "AddressSanitizer failed to allocate") { |
| 198 » » f |= BadAlloc |
122 } | 199 } |
123 | 200 |
124 » r.Flags = flags | 201 » // Split off the stderr that happened before the crash. |
| 202 » errs := strings.Split(asan, "=================") |
| 203 » if len(errs) > 0 { |
| 204 » » err := errs[0] |
| 205 » » if category == "skpicture" && strings.Contains(err, "Rendering")
{ |
| 206 » » » f |= SKPICTURE_DuringRendering |
| 207 » » } |
| 208 » } |
| 209 » return f |
125 } | 210 } |
| 211 |
| 212 // asanCrashed returns true if the asan output is consistent with a crash. |
| 213 func asanCrashed(asan string) bool { |
| 214 return strings.Contains(asan, "ERROR: AddressSanitizer:") |
| 215 } |
| 216 |
| 217 // parseAsan returns the flags discovered while looking through the Clang dump a
nd standard error. |
| 218 // This includes things like |
| 219 func parseCatchsegv(category, dump, err string) FuzzFlag { |
| 220 f := FuzzFlag(0) |
| 221 if !clangDumped(dump) && strings.Contains(err, "[terminated]") { |
| 222 return f |
| 223 } |
| 224 f |= ClangCrashed |
| 225 if strings.Contains(err, "failed assertion") { |
| 226 f |= AssertionViolated |
| 227 } |
| 228 if category == "skpicture" && strings.Contains(err, "Rendering") { |
| 229 f |= SKPICTURE_DuringRendering |
| 230 } |
| 231 if strings.Contains(err, "std::bad_alloc") { |
| 232 f |= BadAlloc |
| 233 } |
| 234 return f |
| 235 } |
| 236 |
| 237 // clangDumped returns true if the clang output is consistent with a crash, that
is, non empty. |
| 238 func clangDumped(dump string) bool { |
| 239 return len(dump) != 0 |
| 240 } |
| 241 |
| 242 var skAbortStackTraceLine = regexp.MustCompile(`(?:\.\./)+(?P<package>(?:\w+/)+)
(?P<file>.+):(?P<line>\d+): fatal error`) |
| 243 |
| 244 // extractSkAbortTrace looks for the fatal error string indicative of the SKAbor
t termination |
| 245 // and tries to pull out the stacktrace frame on which it happened. |
| 246 func extractSkAbortTrace(err string) StackTrace { |
| 247 st := StackTrace{} |
| 248 if match := skAbortStackTraceLine.FindStringSubmatch(err); match != nil
{ |
| 249 st.Frames = append(st.Frames, FullStackFrame(match[1], match[2],
common.UNKNOWN_FUNCTION, safeParseInt(match[3]))) |
| 250 } |
| 251 return st |
| 252 } |
OLD | NEW |