OLD | NEW |
1 package data | 1 package data |
2 | 2 |
3 import ( | 3 import ( |
4 "bufio" | 4 "bufio" |
5 "bytes" | 5 "bytes" |
6 "fmt" | 6 "fmt" |
7 "regexp" | 7 "regexp" |
8 "strconv" | 8 "strconv" |
9 "strings" | 9 "strings" |
10 | 10 |
11 "github.com/skia-dev/glog" | 11 "github.com/skia-dev/glog" |
12 "go.skia.org/infra/fuzzer/go/common" | 12 "go.skia.org/infra/fuzzer/go/common" |
13 ) | 13 ) |
14 | 14 |
15 type StackTrace struct { | 15 type StackTrace struct { |
16 Frames []StackTraceFrame `json:"frames"` | 16 Frames []StackTraceFrame `json:"frames"` |
17 } | 17 } |
18 | 18 |
19 type StackTraceFrame struct { | 19 type StackTraceFrame struct { |
20 PackageName string `json:"packageName"` | 20 PackageName string `json:"packageName"` |
21 FileName string `json:"fileName"` | 21 FileName string `json:"fileName"` |
22 LineNumber int `json:"lineNumber"` | 22 LineNumber int `json:"lineNumber"` |
23 FunctionName string `json:"functionName"` | 23 FunctionName string `json:"functionName"` |
24 } | 24 } |
25 | 25 |
26 // The `?:` at the beginning of the groups prevent them from being captured | 26 // The `?:` at the beginning of the groups prevent them from being captured |
27 // \1 is the "package", \2 is the file name, \3 is the line number, \4 is the fu
nction symbol string | 27 // \1 is the "package", \2 is the file name, \3 is the line number, \4 is the fu
nction symbol string |
28 var stackTraceLine = regexp.MustCompile(`(?:\.\./)+(?P<package>(?:\w+/)+)(?P<fil
e>.+):(?P<line>\d+).*\(_(?P<symbol>.*)\)`) | 28 var segvStackTraceLine = regexp.MustCompile(`(?:\.\./)+(?P<package>(?:\w+/)+)(?P
<file>.+):(?P<line>\d+).*\(_(?P<symbol>.*)\)`) |
29 | 29 |
30 // Given the files of a dump file (created through get_stack_trace [which uses c
atchsegv]), return the stack trace | 30 // parseCatchsegvStackTrace takes the contents of a dump file of a catchsegv run
, and returns the |
31 func ParseStackTrace(contents string) StackTrace { | 31 // parsed stacktrace |
| 32 func parseCatchsegvStackTrace(contents string) StackTrace { |
32 r := bytes.NewBufferString(contents) | 33 r := bytes.NewBufferString(contents) |
33 scan := bufio.NewScanner(r) | 34 scan := bufio.NewScanner(r) |
34 | 35 |
35 hasBegun := false | 36 hasBegun := false |
36 | 37 |
37 frames := make([]StackTraceFrame, 0, 5) | 38 frames := make([]StackTraceFrame, 0, 5) |
38 | 39 |
39 for scan.Scan() { | 40 for scan.Scan() { |
40 line := scan.Text() | 41 line := scan.Text() |
41 if strings.Contains(line, "Backtrace") { | 42 if strings.Contains(line, "Backtrace") { |
42 hasBegun = true | 43 hasBegun = true |
43 } | 44 } |
44 if strings.Contains(line, "Memory map") { | 45 if strings.Contains(line, "Memory map") { |
45 break | 46 break |
46 } | 47 } |
47 if !hasBegun { | 48 if !hasBegun { |
48 continue | 49 continue |
49 } | 50 } |
50 | 51 |
51 » » if match := stackTraceLine.FindStringSubmatch(line); match != ni
l { | 52 » » if match := segvStackTraceLine.FindStringSubmatch(line); match !
= nil { |
52 // match[0] is the entire matched portion, [1] is the "p
ackage", [2] is the file name, | 53 // match[0] is the entire matched portion, [1] is the "p
ackage", [2] is the file name, |
53 // [3] is the line number and [4] is the encoded functio
n symbol string. | 54 // [3] is the line number and [4] is the encoded functio
n symbol string. |
54 » » » newFrame := FullStackFrame(match[1], match[2], decodeFun
ctionName(match[4]), safeParseInt(match[3])) | 55 » » » newFrame := FullStackFrame(match[1], match[2], catchsegv
FunctionName(match[4]), safeParseInt(match[3])) |
55 frames = append(frames, newFrame) | 56 frames = append(frames, newFrame) |
56 } | 57 } |
57 } | 58 } |
58 return StackTrace{Frames: frames} | 59 return StackTrace{Frames: frames} |
59 } | 60 } |
60 | 61 |
61 // safeParseInt parses a string that is known to contain digits into an int. | 62 // safeParseInt parses a string that is known to contain digits into an int. |
62 // It may fail if the number is larger than MAX_INT, but it is unlikely those | 63 // It may fail if the number is larger than MAX_INT, but it is unlikely those |
63 // numbers would come up in the stacktraces. | 64 // numbers would come up in the stacktraces. |
64 func safeParseInt(n string) int { | 65 func safeParseInt(n string) int { |
65 if i, err := strconv.Atoi(n); err != nil { | 66 if i, err := strconv.Atoi(n); err != nil { |
66 glog.Errorf("Could not parse number from known digits %q: %v", n
, err) | 67 glog.Errorf("Could not parse number from known digits %q: %v", n
, err) |
67 return 0 | 68 return 0 |
68 } else { | 69 } else { |
69 return i | 70 return i |
70 } | 71 } |
71 } | 72 } |
72 | 73 |
73 var staticStart = regexp.MustCompile(`^ZL(\d+)`) | 74 var staticStart = regexp.MustCompile(`^ZL(\d+)`) |
74 var nonstaticStart = regexp.MustCompile(`^Z(\d+)`) | 75 var nonstaticStart = regexp.MustCompile(`^Z(\d+)`) |
75 | 76 |
76 var methodStart = regexp.MustCompile(`^(ZNK?)(\d+)`) | 77 var methodStart = regexp.MustCompile(`^(ZNK?)(\d+)`) |
77 var methodName = regexp.MustCompile(`^(\d+)`) | 78 var methodName = regexp.MustCompile(`^(\d+)`) |
78 | 79 |
79 // decodeFunctionName parses the symbol string from a stacktrace to | 80 // catchsegvFunctionName parses the symbol string from a stacktrace to |
80 // get the name of the related function. | 81 // get the name of the related function. |
81 //TODO(kjlubick) parse arguments if that helps the readability of the stacktrace
s | 82 //TODO(kjlubick) parse arguments if that helps the readability of the stacktrace
s |
82 // Here are some examples of symbol strings and what the various chars mean: | 83 // Here are some examples of symbol strings and what the various chars mean: |
83 // (Parameters are after the names, but are unparsed as of yet) | 84 // (Parameters are after the names, but are unparsed as of yet) |
84 // ZL12convert_to_8 -> ZL12 -> static function 12 long "convert_to_8" | 85 // ZL12convert_to_8 -> ZL12 -> static function 12 long "convert_to_8" |
85 // Z9tool_mainiPPc -> non-static function, 9 long "tool_main" | 86 // Z9tool_mainiPPc -> non-static function, 9 long "tool_main" |
86 // ZN14SkBmpMaskCodec10decodeRows -> ZN14 -> type is 14 long, method name is 10
long "decodeRows" | 87 // ZN14SkBmpMaskCodec10decodeRows -> ZN14 -> type is 14 long, method name is 10
long "decodeRows" |
87 // ZNK2DM6SKPSrc4drawEP8SkCanvas -> ZNK2 -> type is 2 long (method is konstant)
"DM" -> | 88 // ZNK2DM6SKPSrc4drawEP8SkCanvas -> ZNK2 -> type is 2 long (method is konstant)
"DM" -> |
88 // 6 -> Sub type is 6 long "SKPSrc" -> 4 -> method is 4 long "draw" | 89 // 6 -> Sub type is 6 long "SKPSrc" -> 4 -> method is 4 long "draw" |
89 // (since there is no number directly after the 3rd step, we have found the p
aram boundary). | 90 // (since there is no number directly after the 3rd step, we have found the p
aram boundary). |
90 func decodeFunctionName(s string) string { | 91 func catchsegvFunctionName(s string) string { |
91 if match := methodStart.FindStringSubmatch(s); match != nil { | 92 if match := methodStart.FindStringSubmatch(s); match != nil { |
92 length := safeParseInt(match[2]) | 93 length := safeParseInt(match[2]) |
93 // ZNK? is 2-3 chars, so slice (num letters + num digits + numbe
r of spaces) chars off | 94 // ZNK? is 2-3 chars, so slice (num letters + num digits + numbe
r of spaces) chars off |
94 // the beginning. | 95 // the beginning. |
95 s = s[len(match[1])+len(match[2])+length:] | 96 s = s[len(match[1])+len(match[2])+length:] |
96 f := "" | 97 f := "" |
97 // We look at the beginning of our trimmed string for numbers. | 98 // We look at the beginning of our trimmed string for numbers. |
98 // if there are numbers, we pull off a piece of the name and sca
n again. | 99 // if there are numbers, we pull off a piece of the name and sca
n again. |
99 // If there is more than one piece, we separate it with ::, beca
use it is a nested type | 100 // If there is more than one piece, we separate it with ::, beca
use it is a nested type |
100 // or enum thing. | 101 // or enum thing. |
(...skipping 16 matching lines...) Expand all Loading... |
117 } | 118 } |
118 if match := nonstaticStart.FindStringSubmatch(s); match != nil { | 119 if match := nonstaticStart.FindStringSubmatch(s); match != nil { |
119 length := safeParseInt(match[1]) | 120 length := safeParseInt(match[1]) |
120 // Z is 1 char, so advance 1 space + how many digits there are | 121 // Z is 1 char, so advance 1 space + how many digits there are |
121 start := 1 + len(match[1]) | 122 start := 1 + len(match[1]) |
122 return s[start : start+length] | 123 return s[start : start+length] |
123 } | 124 } |
124 return common.UNKNOWN_FUNCTION | 125 return common.UNKNOWN_FUNCTION |
125 } | 126 } |
126 | 127 |
| 128 // The `?:` at the beginning of the groups prevent them from being captured |
| 129 // \1 is the (hopefully symbolized) function name, \2 is the "package", \3 is th
e file name, |
| 130 // \4 is the line number |
| 131 var asanStackTraceLine = regexp.MustCompile(`in (?P<function>[a-zA-Z0-9_:]+).*(?
:\.\./)+(?P<package>(?:\w+/)+)(?P<file>.+?):(?P<line>\d+)`) |
| 132 |
| 133 // parseCatchsegvStackTrace takes the output of an AddressSanitizer crash, and r
eturns the parsed |
| 134 // StackTrace, if it is able to find one. If the result is not symbolized, this
will return |
| 135 // an empty StackTrace. |
| 136 func parseASANStackTrace(contents string) StackTrace { |
| 137 r := bytes.NewBufferString(contents) |
| 138 scan := bufio.NewScanner(r) |
| 139 |
| 140 hasBegun := false |
| 141 |
| 142 frames := make([]StackTraceFrame, 0, 5) |
| 143 |
| 144 for scan.Scan() { |
| 145 line := scan.Text() |
| 146 if strings.Contains(line, "ERROR: AddressSanitizer:") { |
| 147 hasBegun = true |
| 148 continue |
| 149 } |
| 150 if hasBegun && line == "" { |
| 151 break |
| 152 } |
| 153 if !hasBegun { |
| 154 continue |
| 155 } |
| 156 |
| 157 if match := asanStackTraceLine.FindStringSubmatch(line); match !
= nil { |
| 158 // match[0] is the entire matched portion, [1] is the fu
nction name [2] is the |
| 159 // "package", [3] is the file name, [4] is the line numb
er |
| 160 newFrame := FullStackFrame(match[2], match[3], match[1],
safeParseInt(match[4])) |
| 161 frames = append(frames, newFrame) |
| 162 } |
| 163 } |
| 164 return StackTrace{Frames: frames} |
| 165 } |
| 166 |
127 // FullStackFrame creates a StackTraceFrame with all components | 167 // FullStackFrame creates a StackTraceFrame with all components |
128 func FullStackFrame(packageName, fileName, functionName string, lineNumber int)
StackTraceFrame { | 168 func FullStackFrame(packageName, fileName, functionName string, lineNumber int)
StackTraceFrame { |
129 return StackTraceFrame{ | 169 return StackTraceFrame{ |
130 PackageName: packageName, | 170 PackageName: packageName, |
131 FileName: fileName, | 171 FileName: fileName, |
132 LineNumber: lineNumber, | 172 LineNumber: lineNumber, |
133 FunctionName: functionName, | 173 FunctionName: functionName, |
134 } | 174 } |
135 } | 175 } |
136 | 176 |
137 func (f *StackTraceFrame) String() string { | 177 func (f *StackTraceFrame) String() string { |
138 return fmt.Sprintf("%s%s:%d %s", f.PackageName, f.FileName, f.LineNumber
, f.FunctionName) | 178 return fmt.Sprintf("%s%s:%d %s", f.PackageName, f.FileName, f.LineNumber
, f.FunctionName) |
139 } | 179 } |
140 | 180 |
141 func (st *StackTrace) String() string { | 181 func (st *StackTrace) String() string { |
142 s := fmt.Sprintf("StackTrace with %d frames:\n", len(st.Frames)) | 182 s := fmt.Sprintf("StackTrace with %d frames:\n", len(st.Frames)) |
143 for _, f := range st.Frames { | 183 for _, f := range st.Frames { |
144 s += fmt.Sprintf("\t%s\n", f.String()) | 184 s += fmt.Sprintf("\t%s\n", f.String()) |
145 } | 185 } |
146 return s | 186 return s |
147 } | 187 } |
148 | 188 |
149 func (st *StackTrace) IsEmpty() bool { | 189 func (st *StackTrace) IsEmpty() bool { |
150 return len(st.Frames) == 0 | 190 return len(st.Frames) == 0 |
151 } | 191 } |
OLD | NEW |