OLD | NEW |
1 # Copyright (c) 2014 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import os | |
6 import re | 5 import re |
7 | 6 |
8 import crash_utils | 7 import crash_utils |
9 | 8 |
10 | 9 |
| 10 SYZYASAN_STACK_FRAME_PATTERN = re.compile( |
| 11 r'(CF: )?(.*?)( \(FPO: .*\) )?( \(CONV: .*\) )?\[(.*) @ (\d+)\]') |
| 12 FILE_PATH_AND_LINE_PATTERN = re.compile(r'(.*?):(\d+)(:\d+)?') |
| 13 |
| 14 |
11 class StackFrame(object): | 15 class StackFrame(object): |
12 """Represents a frame in stacktrace. | 16 """Represents a frame in stacktrace. |
13 | 17 |
14 Attributes: | 18 Attributes: |
15 index: An index of the stack frame. | 19 index: An index of the stack frame. |
16 component_path: The path of the component this frame represents. | 20 component_path: The path of the component this frame represents. |
17 component_name: The name of the component this frame represents. | 21 component_name: The name of the component this frame represents. |
18 file_name: The name of the file that crashed. | 22 file_name: The name of the file that crashed. |
19 function: The function that caused the crash. | 23 function: The function that caused the crash. |
20 file_path: The path of the crashed file. | 24 file_path: The path of the crashed file. |
21 crashed_line_number: The line of the file that caused the crash. | 25 crashed_line_range: The line of the file that caused the crash. |
22 """ | 26 """ |
23 | 27 |
24 def __init__(self, stack_frame_index, component_path, component_name, | 28 def __init__(self, stack_frame_index, component_path, component_name, |
25 file_name, function, file_path, crashed_line_number): | 29 file_name, function, file_path, crashed_line_range): |
26 self.index = stack_frame_index | 30 self.index = stack_frame_index |
27 self.component_path = component_path | 31 self.component_path = component_path |
28 self.component_name = component_name | 32 self.component_name = component_name |
29 self.file_name = file_name | 33 self.file_name = file_name |
30 self.function = function | 34 self.function = function |
31 self.file_path = file_path | 35 self.file_path = file_path |
32 self.crashed_line_number = crashed_line_number | 36 self.crashed_line_range = crashed_line_range |
33 | 37 |
34 | 38 |
35 class CallStack(object): | 39 class CallStack(object): |
36 """Represents a call stack within a stacktrace. | 40 """Represents a call stack within a stacktrace. |
37 | 41 |
38 It is a list of StackFrame object, and the object keeps track of whether | 42 It is a list of StackFrame object, and the object keeps track of whether |
39 the stack is crash stack, freed or previously-allocated. | 43 the stack is crash stack, freed or previously-allocated. |
40 """ | 44 """ |
41 | 45 |
42 def __init__(self, stack_priority): | 46 def __init__(self, stack_priority): |
43 self.frame_list = [] | 47 self.frame_list = [] |
44 self.priority = stack_priority | 48 self.priority = stack_priority |
45 | 49 |
46 def Add(self, stacktrace_line): | 50 def Add(self, stacktrace_line): |
47 self.frame_list.append(stacktrace_line) | 51 self.frame_list.append(stacktrace_line) |
48 | 52 |
49 def GetTopNFrames(self, n): | 53 def GetTopNFrames(self, n): |
50 return self.frame_list[:n] | 54 return self.frame_list[:n] |
51 | 55 |
52 | 56 |
53 class Stacktrace(object): | 57 class Stacktrace(object): |
54 """Represents Stacktrace object. | 58 """Represents Stacktrace object. |
55 | 59 |
56 Contains a list of callstacks, because one stacktrace might have more than | 60 Contains a list of callstacks, because one stacktrace might have more than |
57 one callstacks. | 61 one callstacks. |
58 """ | 62 """ |
59 | 63 |
60 def __init__(self, stacktrace, build_type, parsed_deps): | 64 def __init__(self, stacktrace, build_type, parsed_deps): |
61 self.stack_list = None | 65 self.stack_list = None |
62 self.parsed_deps = parsed_deps | 66 self.ParseStacktrace(stacktrace, build_type, parsed_deps) |
63 self.ParseStacktrace(stacktrace, build_type) | |
64 | 67 |
65 def ParseStacktrace(self, stacktrace, build_type): | 68 def ParseStacktrace(self, stacktrace, build_type, parsed_deps): |
66 """Parses stacktrace and normalizes it. | 69 """Parses stacktrace and normalizes it. |
67 | 70 |
68 If there are multiple callstacks within the stacktrace, | 71 If there are multiple callstacks within the stacktrace, |
69 it will parse each of them separately, and store them in the stack_list | 72 it will parse each of them separately, and store them in the stack_list |
70 variable. | 73 variable. |
71 | 74 |
72 Args: | 75 Args: |
73 stacktrace: A string containing stacktrace. | 76 stacktrace: A string containing stacktrace. |
74 build_type: A string containing the build type of the crash. | 77 build_type: A string containing the build type of the crash. |
| 78 parsed_deps: A parsed DEPS file to normalize path with. |
75 """ | 79 """ |
76 # If the passed in string is empty, the object does not represent anything. | 80 # If the passed in string is empty, the object does not represent anything. |
77 if not stacktrace: | 81 if not stacktrace: |
78 return | 82 return |
79 | |
80 # Reset the stack list. | 83 # Reset the stack list. |
81 self.stack_list = [] | 84 self.stack_list = [] |
82 reached_new_callstack = False | 85 reached_new_callstack = False |
83 # Note that we do not need exact stack frame index, we only need relative | 86 # Note that we do not need exact stack frame index, we only need relative |
84 # position of a frame within a callstack. The reason for not extracting | 87 # position of a frame within a callstack. The reason for not extracting |
85 # index from a line is that some stack frames do not have index. | 88 # index from a line is that some stack frames do not have index. |
86 stack_frame_index = 0 | 89 stack_frame_index = 0 |
87 current_stack = None | 90 current_stack = CallStack(-1) |
88 | 91 |
89 for line in stacktrace: | 92 for line in stacktrace: |
| 93 line = line.strip() |
90 (is_new_callstack, stack_priority) = self.__IsStartOfNewCallStack( | 94 (is_new_callstack, stack_priority) = self.__IsStartOfNewCallStack( |
91 line, build_type) | 95 line, build_type) |
92 | |
93 if is_new_callstack: | 96 if is_new_callstack: |
94 | |
95 # If this callstack is crash stack, update the boolean. | 97 # If this callstack is crash stack, update the boolean. |
96 if not reached_new_callstack: | 98 if not reached_new_callstack: |
97 reached_new_callstack = True | 99 reached_new_callstack = True |
98 current_stack = CallStack(stack_priority) | 100 current_stack = CallStack(stack_priority) |
99 | 101 |
100 # If this is from freed or allocation, add the callstack we have | 102 # If this is from freed or allocation, add the callstack we have |
101 # to the list of callstacks, and increment the stack priority. | 103 # to the list of callstacks, and increment the stack priority. |
102 else: | 104 else: |
103 stack_frame_index = 0 | 105 stack_frame_index = 0 |
104 if current_stack and current_stack.frame_list: | 106 if current_stack and current_stack.frame_list: |
105 self.stack_list.append(current_stack) | 107 self.stack_list.append(current_stack) |
106 current_stack = CallStack(stack_priority) | 108 current_stack = CallStack(stack_priority) |
107 | 109 |
108 # Generate stack frame object from the line. | 110 # Generate stack frame object from the line. |
109 parsed_stack_frame = self.__GenerateStackFrame( | 111 parsed_stack_frame = self.__GenerateStackFrame( |
110 stack_frame_index, line, build_type) | 112 stack_frame_index, line, build_type, parsed_deps) |
111 | 113 |
112 # If the line does not represent the stack frame, ignore this line. | 114 # If the line does not represent the stack frame, ignore this line. |
113 if not parsed_stack_frame: | 115 if not parsed_stack_frame: |
114 continue | 116 continue |
115 | 117 |
116 # Add the parsed stack frame object to the current stack. | 118 # Add the parsed stack frame object to the current stack. |
117 current_stack.Add(parsed_stack_frame) | 119 current_stack.Add(parsed_stack_frame) |
118 stack_frame_index += 1 | 120 stack_frame_index += 1 |
119 | 121 |
120 # Add the current callstack only if there are frames in it. | 122 # Add the current callstack only if there are frames in it. |
121 if current_stack and current_stack.frame_list: | 123 if current_stack and current_stack.frame_list: |
122 self.stack_list.append(current_stack) | 124 self.stack_list.append(current_stack) |
123 | 125 |
124 def __IsStartOfNewCallStack(self, line, build_type): | 126 def __IsStartOfNewCallStack(self, line, build_type): |
125 """Check if this line is the start of the new callstack. | 127 """Check if this line is the start of the new callstack. |
126 | 128 |
127 Since each builds have different format of stacktrace, the logic for | 129 Since each builds have different format of stacktrace, the logic for |
128 checking the line for all builds is handled in here. | 130 checking the line for all builds is handled in here. |
129 | 131 |
130 Args: | 132 Args: |
131 line: Line to check for. | 133 line: Line to check for. |
132 build_type: The name of the build. | 134 build_type: The name of the build. |
133 | 135 |
134 Returns: | 136 Returns: |
135 True if the line is the start of new callstack, False otherwise. If True, | 137 True if the line is the start of new callstack, False otherwise. If True, |
136 it also returns the priority of the line. | 138 it also returns the priority of the line. |
137 """ | 139 """ |
138 # Currently not supported. | 140 if 'syzyasan' in build_type: |
139 if 'android' in build_type: | |
140 pass | |
141 | |
142 elif 'syzyasan' in build_type: | |
143 # In syzyasan build, new stack starts with 'crash stack:', | 141 # In syzyasan build, new stack starts with 'crash stack:', |
144 # 'freed stack:', etc. | 142 # 'freed stack:', etc. |
145 callstack_start_pattern = re.compile(r'^(.*) stack:$') | 143 callstack_start_pattern = re.compile(r'^(.*) stack:$') |
146 match = callstack_start_pattern.match(line) | 144 match = callstack_start_pattern.match(line) |
147 | 145 |
148 # If the line matches the callstack start pattern. | 146 # If the line matches the callstack start pattern. |
149 if match: | 147 if match: |
150 # Check the type of the new match. | 148 # Check the type of the new match. |
151 stack_type = match.group(1) | 149 stack_type = match.group(1) |
152 | 150 |
153 # Crash stack gets priority 0. | 151 # Crash stack gets priority 0. |
154 if stack_type == 'Crash': | 152 if stack_type == 'Crash': |
155 return (True, 0) | 153 return (True, 0) |
156 | 154 |
157 # Other callstacks all get priority 1. | 155 # Other callstacks all get priority 1. |
158 else: | 156 else: |
159 return (True, 1) | 157 return (True, 1) |
160 | 158 |
161 elif 'tsan' in build_type: | 159 elif 'tsan' in build_type: |
162 # Create patterns for each callstack type. | 160 # Create patterns for each callstack type. |
163 crash_callstack_start_pattern = re.compile( | 161 crash_callstack_start_pattern1 = re.compile( |
164 r'^(Read|Write) of size \d+') | 162 r'^(Read|Write) of size \d+') |
165 | 163 |
| 164 crash_callstack_start_pattern2 = re.compile( |
| 165 r'^[A-Z]+: ThreadSanitizer') |
| 166 |
166 allocation_callstack_start_pattern = re.compile( | 167 allocation_callstack_start_pattern = re.compile( |
167 r'^Previous (write|read) of size \d+') | 168 r'^Previous (write|read) of size \d+') |
168 | 169 |
169 location_callstack_start_pattern = re.compile( | 170 location_callstack_start_pattern = re.compile( |
170 r'^Location is heap block of size \d+') | 171 r'^Location is heap block of size \d+') |
171 | 172 |
172 # Crash stack gets priority 0. | 173 # Crash stack gets priority 0. |
173 if crash_callstack_start_pattern.match(line): | 174 if (crash_callstack_start_pattern1.match(line) or |
| 175 crash_callstack_start_pattern2.match(line)): |
174 return (True, 0) | 176 return (True, 0) |
175 | 177 |
176 # All other stacks get priority 1. | 178 # All other stacks get priority 1. |
177 if allocation_callstack_start_pattern.match(line): | 179 if allocation_callstack_start_pattern.match(line): |
178 return (True, 1) | 180 return (True, 1) |
179 | 181 |
180 if location_callstack_start_pattern.match(line): | 182 if location_callstack_start_pattern.match(line): |
181 return (True, 1) | 183 return (True, 1) |
182 | 184 |
183 else: | 185 else: |
184 # In asan and other build types, crash stack can start | 186 # In asan and other build types, crash stack can start |
185 # in two different ways. | 187 # in two different ways. |
186 crash_callstack_start_pattern1 = re.compile(r'^==\d+== ?ERROR:') | 188 crash_callstack_start_pattern1 = re.compile(r'^==\d+== ?[A-Z]+:') |
187 crash_callstack_start_pattern2 = re.compile( | 189 crash_callstack_start_pattern2 = re.compile( |
188 r'^(READ|WRITE) of size \d+ at') | 190 r'^(READ|WRITE) of size \d+ at') |
| 191 crash_callstack_start_pattern3 = re.compile(r'^backtrace:') |
189 | 192 |
190 freed_callstack_start_pattern = re.compile( | 193 freed_callstack_start_pattern = re.compile( |
191 r'^freed by thread T\d+ (.* )?here:') | 194 r'^freed by thread T\d+ (.* )?here:') |
192 | 195 |
193 allocation_callstack_start_pattern = re.compile( | 196 allocation_callstack_start_pattern = re.compile( |
194 r'^previously allocated by thread T\d+ (.* )?here:') | 197 r'^previously allocated by thread T\d+ (.* )?here:') |
195 | 198 |
196 other_callstack_start_pattern = re.compile( | 199 other_callstack_start_pattern = re.compile( |
197 r'^Thread T\d+ (.* )?created by') | 200 r'^Thread T\d+ (.* )?created by') |
198 | 201 |
199 # Crash stack gets priority 0. | 202 # Crash stack gets priority 0. |
200 if (crash_callstack_start_pattern1.match(line) or | 203 if (crash_callstack_start_pattern1.match(line) or |
201 crash_callstack_start_pattern2.match(line)): | 204 crash_callstack_start_pattern2.match(line) or |
| 205 crash_callstack_start_pattern3.match(line)): |
202 return (True, 0) | 206 return (True, 0) |
203 | 207 |
204 # All other callstack gets priority 1. | 208 # All other callstack gets priority 1. |
205 if freed_callstack_start_pattern.match(line): | 209 if freed_callstack_start_pattern.match(line): |
206 return (True, 1) | 210 return (True, 1) |
207 | 211 |
208 if allocation_callstack_start_pattern.match(line): | 212 if allocation_callstack_start_pattern.match(line): |
209 return (True, 1) | 213 return (True, 1) |
210 | 214 |
211 if other_callstack_start_pattern.match(line): | 215 if other_callstack_start_pattern.match(line): |
212 return (True, 1) | 216 return (True, 1) |
213 | 217 |
214 # If the line does not match any pattern, return false and a dummy for | 218 # If the line does not match any pattern, return false and a dummy for |
215 # stack priority. | 219 # stack priority. |
216 return (False, -1) | 220 return (False, -1) |
217 | 221 |
218 def __GenerateStackFrame(self, stack_frame_index, line, build_type): | 222 def __GenerateStackFrame(self, stack_frame_index, line, build_type, |
| 223 parsed_deps): |
219 """Extracts information from a line in stacktrace. | 224 """Extracts information from a line in stacktrace. |
220 | 225 |
221 Args: | 226 Args: |
222 stack_frame_index: A stack frame index of this line. | 227 stack_frame_index: A stack frame index of this line. |
223 line: A stacktrace string to extract data from. | 228 line: A stacktrace string to extract data from. |
224 build_type: A string containing the build type | 229 build_type: A string containing the build type |
225 of this crash (e.g. linux_asan_chrome_mp). | 230 of this crash (e.g. linux_asan_chrome_mp). |
| 231 parsed_deps: A parsed DEPS file to normalize path with. |
226 | 232 |
227 Returns: | 233 Returns: |
228 A triple containing the name of the function, the path of the file and | 234 A triple containing the name of the function, the path of the file and |
229 the crashed line number. | 235 the crashed line number. |
230 """ | 236 """ |
231 line_parts = line.split() | 237 line_parts = line.split() |
| 238 try: |
232 | 239 |
233 try: | 240 if 'syzyasan' in build_type: |
234 # Filter out lines that are not stack frame. | 241 stack_frame_match = SYZYASAN_STACK_FRAME_PATTERN.match(line) |
235 stack_frame_index_pattern = re.compile(r'#(\d+)') | |
236 if not stack_frame_index_pattern.match(line_parts[0]): | |
237 return None | |
238 | 242 |
239 # Tsan has different stack frame style from other builds. | 243 if not stack_frame_match: |
240 if build_type.startswith('linux_tsan'): | 244 return None |
241 file_path_and_line = line_parts[-2] | 245 file_path = stack_frame_match.group(5) |
242 function = ' '.join(line_parts[1:-2]) | 246 crashed_line_range = [int(stack_frame_match.group(6))] |
| 247 function = stack_frame_match.group(2) |
243 | 248 |
244 else: | 249 else: |
245 file_path_and_line = line_parts[-1] | 250 if not line_parts[0].startswith('#'): |
246 function = ' '.join(line_parts[3:-1]) | 251 return None |
247 | 252 |
248 # Get file path and line info from the line. | 253 if 'tsan' in build_type: |
249 file_path_and_line = file_path_and_line.split(':') | 254 file_path_and_line = line_parts[-2] |
250 file_path = file_path_and_line[0] | 255 function = ' '.join(line_parts[1:-2]) |
251 crashed_line_number = int(file_path_and_line[1]) | 256 else: |
| 257 file_path_and_line = line_parts[-1] |
| 258 function = ' '.join(line_parts[3:-1]) |
| 259 |
| 260 # Get file path and line info from the line. |
| 261 file_path_and_line_match = FILE_PATH_AND_LINE_PATTERN.match( |
| 262 file_path_and_line) |
| 263 |
| 264 # Return None if the file path information is not available |
| 265 if not file_path_and_line_match: |
| 266 return None |
| 267 |
| 268 file_path = file_path_and_line_match.group(1) |
| 269 |
| 270 # Get the crashed line range. For example, file_path:line_number:range. |
| 271 crashed_line_range_num = file_path_and_line_match.group(3) |
| 272 |
| 273 if crashed_line_range_num: |
| 274 # Strip ':' prefix. |
| 275 crashed_line_range_num = int(crashed_line_range_num[1:]) |
| 276 else: |
| 277 crashed_line_range_num = 0 |
| 278 |
| 279 crashed_line_number = int(file_path_and_line_match.group(2)) |
| 280 # For example, 655:1 has crashed lines 655 and 656. |
| 281 crashed_line_range = \ |
| 282 range(crashed_line_number, |
| 283 crashed_line_number + crashed_line_range_num + 1) |
252 | 284 |
253 # Return None if the line is malformed. | 285 # Return None if the line is malformed. |
254 except IndexError: | 286 except IndexError: |
255 return None | 287 return None |
256 except ValueError: | 288 except ValueError: |
257 return None | 289 return None |
258 | 290 |
259 # Normalize the file path so that it can be compared to repository path. | 291 # Normalize the file path so that it can be compared to repository path. |
260 file_name = os.path.basename(file_path) | |
261 (component_path, component_name, file_path) = ( | 292 (component_path, component_name, file_path) = ( |
262 crash_utils.NormalizePathLinux(file_path, self.parsed_deps)) | 293 crash_utils.NormalizePath(file_path, parsed_deps)) |
263 | |
264 # If this component is not supported, ignore this line. | |
265 if not component_path: | |
266 return None | |
267 | 294 |
268 # Return a new stack frame object with the parsed information. | 295 # Return a new stack frame object with the parsed information. |
| 296 file_name = file_path.split('/')[-1] |
269 return StackFrame(stack_frame_index, component_path, component_name, | 297 return StackFrame(stack_frame_index, component_path, component_name, |
270 file_name, function, file_path, crashed_line_number) | 298 file_name, function, file_path, crashed_line_range) |
271 | 299 |
272 def __getitem__(self, index): | 300 def __getitem__(self, index): |
273 return self.stack_list[index] | 301 return self.stack_list[index] |
274 | 302 |
275 def GetCrashStack(self): | 303 def GetCrashStack(self): |
276 """Returns the callstack with the highest priority. | 304 """Returns the callstack with the highest priority. |
277 | 305 |
278 Crash stack has priority 0, and allocation/freed/other thread stacks | 306 Crash stack has priority 0, and allocation/freed/other thread stacks |
279 get priority 1. | 307 get priority 1. |
280 | 308 |
281 Returns: | 309 Returns: |
282 The highest priority callstack in the stacktrace. | 310 The highest priority callstack in the stacktrace. |
283 """ | 311 """ |
284 sorted_stacklist = sorted(self.stack_list, | 312 sorted_stacklist = sorted(self.stack_list, |
285 key=lambda callstack: callstack.priority) | 313 key=lambda callstack: callstack.priority) |
286 return sorted_stacklist[0] | 314 return sorted_stacklist[0] |
OLD | NEW |