Chromium Code Reviews| 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. |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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) |
|
Martin Barbella
2014/08/22 02:24:14
This seems pretty hacky. What was the issue here?
jeun
2014/08/22 22:58:44
This is to bypass UBSan (and possibly some other b
| |
| 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 | 97 |
| 95 # If this callstack is crash stack, update the boolean. | 98 # If this callstack is crash stack, update the boolean. |
| 96 if not reached_new_callstack: | 99 if not reached_new_callstack: |
| 97 reached_new_callstack = True | 100 reached_new_callstack = True |
| 98 current_stack = CallStack(stack_priority) | 101 current_stack = CallStack(stack_priority) |
| 99 | 102 |
| 100 # If this is from freed or allocation, add the callstack we have | 103 # If this is from freed or allocation, add the callstack we have |
| 101 # to the list of callstacks, and increment the stack priority. | 104 # to the list of callstacks, and increment the stack priority. |
| 102 else: | 105 else: |
| 103 stack_frame_index = 0 | 106 stack_frame_index = 0 |
| 104 if current_stack and current_stack.frame_list: | 107 if current_stack and current_stack.frame_list: |
| 105 self.stack_list.append(current_stack) | 108 self.stack_list.append(current_stack) |
| 106 current_stack = CallStack(stack_priority) | 109 current_stack = CallStack(stack_priority) |
| 107 | 110 |
| 108 # Generate stack frame object from the line. | 111 # Generate stack frame object from the line. |
| 109 parsed_stack_frame = self.__GenerateStackFrame( | 112 parsed_stack_frame = self.__GenerateStackFrame( |
| 110 stack_frame_index, line, build_type) | 113 stack_frame_index, line, build_type, parsed_deps) |
| 111 | 114 |
| 112 # If the line does not represent the stack frame, ignore this line. | 115 # If the line does not represent the stack frame, ignore this line. |
| 113 if not parsed_stack_frame: | 116 if not (parsed_stack_frame and current_stack): |
|
Martin Barbella
2014/08/22 02:24:14
With the other change, would the "and current_stac
jeun
2014/08/22 22:58:44
No, removed.
| |
| 114 continue | 117 continue |
| 115 | 118 |
| 116 # Add the parsed stack frame object to the current stack. | 119 # Add the parsed stack frame object to the current stack. |
| 117 current_stack.Add(parsed_stack_frame) | 120 current_stack.Add(parsed_stack_frame) |
| 118 stack_frame_index += 1 | 121 stack_frame_index += 1 |
| 119 | 122 |
| 120 # Add the current callstack only if there are frames in it. | 123 # Add the current callstack only if there are frames in it. |
| 121 if current_stack and current_stack.frame_list: | 124 if current_stack and current_stack.frame_list: |
| 122 self.stack_list.append(current_stack) | 125 self.stack_list.append(current_stack) |
| 123 | 126 |
| 124 def __IsStartOfNewCallStack(self, line, build_type): | 127 def __IsStartOfNewCallStack(self, line, build_type): |
| 125 """Check if this line is the start of the new callstack. | 128 """Check if this line is the start of the new callstack. |
| 126 | 129 |
| 127 Since each builds have different format of stacktrace, the logic for | 130 Since each builds have different format of stacktrace, the logic for |
| 128 checking the line for all builds is handled in here. | 131 checking the line for all builds is handled in here. |
| 129 | 132 |
| 130 Args: | 133 Args: |
| 131 line: Line to check for. | 134 line: Line to check for. |
| 132 build_type: The name of the build. | 135 build_type: The name of the build. |
| 133 | 136 |
| 134 Returns: | 137 Returns: |
| 135 True if the line is the start of new callstack, False otherwise. If True, | 138 True if the line is the start of new callstack, False otherwise. If True, |
| 136 it also returns the priority of the line. | 139 it also returns the priority of the line. |
| 137 """ | 140 """ |
| 138 # Currently not supported. | 141 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:', | 142 # In syzyasan build, new stack starts with 'crash stack:', |
| 144 # 'freed stack:', etc. | 143 # 'freed stack:', etc. |
| 145 callstack_start_pattern = re.compile(r'^(.*) stack:$') | 144 callstack_start_pattern = re.compile(r'^(.*) stack:$') |
| 146 match = callstack_start_pattern.match(line) | 145 match = callstack_start_pattern.match(line) |
| 147 | 146 |
| 148 # If the line matches the callstack start pattern. | 147 # If the line matches the callstack start pattern. |
| 149 if match: | 148 if match: |
| 150 # Check the type of the new match. | 149 # Check the type of the new match. |
| 151 stack_type = match.group(1) | 150 stack_type = match.group(1) |
| 152 | 151 |
| 153 # Crash stack gets priority 0. | 152 # Crash stack gets priority 0. |
| 154 if stack_type == 'Crash': | 153 if stack_type == 'Crash': |
| 155 return (True, 0) | 154 return (True, 0) |
| 156 | 155 |
| 157 # Other callstacks all get priority 1. | 156 # Other callstacks all get priority 1. |
| 158 else: | 157 else: |
| 159 return (True, 1) | 158 return (True, 1) |
| 160 | 159 |
| 161 elif 'tsan' in build_type: | 160 elif 'tsan' in build_type: |
| 162 # Create patterns for each callstack type. | 161 # Create patterns for each callstack type. |
| 163 crash_callstack_start_pattern = re.compile( | 162 crash_callstack_start_pattern1 = re.compile( |
| 164 r'^(Read|Write) of size \d+') | 163 r'^(Read|Write) of size \d+') |
| 165 | 164 |
| 165 crash_callstack_start_pattern2 = re.compile( | |
| 166 r'^[A-Z]+: ThreadSanitizer') | |
| 167 | |
| 166 allocation_callstack_start_pattern = re.compile( | 168 allocation_callstack_start_pattern = re.compile( |
| 167 r'^Previous (write|read) of size \d+') | 169 r'^Previous (write|read) of size \d+') |
| 168 | 170 |
| 169 location_callstack_start_pattern = re.compile( | 171 location_callstack_start_pattern = re.compile( |
| 170 r'^Location is heap block of size \d+') | 172 r'^Location is heap block of size \d+') |
| 171 | 173 |
| 172 # Crash stack gets priority 0. | 174 # Crash stack gets priority 0. |
| 173 if crash_callstack_start_pattern.match(line): | 175 if (crash_callstack_start_pattern1.match(line) or |
| 176 crash_callstack_start_pattern2.match(line)): | |
| 174 return (True, 0) | 177 return (True, 0) |
| 175 | 178 |
| 176 # All other stacks get priority 1. | 179 # All other stacks get priority 1. |
| 177 if allocation_callstack_start_pattern.match(line): | 180 if allocation_callstack_start_pattern.match(line): |
| 178 return (True, 1) | 181 return (True, 1) |
| 179 | 182 |
| 180 if location_callstack_start_pattern.match(line): | 183 if location_callstack_start_pattern.match(line): |
| 181 return (True, 1) | 184 return (True, 1) |
| 182 | 185 |
| 183 else: | 186 else: |
| 184 # In asan and other build types, crash stack can start | 187 # In asan and other build types, crash stack can start |
| 185 # in two different ways. | 188 # in two different ways. |
| 186 crash_callstack_start_pattern1 = re.compile(r'^==\d+== ?ERROR:') | 189 crash_callstack_start_pattern1 = re.compile(r'^==\d+== ?[A-Z]+:') |
| 187 crash_callstack_start_pattern2 = re.compile( | 190 crash_callstack_start_pattern2 = re.compile( |
| 188 r'^(READ|WRITE) of size \d+ at') | 191 r'^(READ|WRITE) of size \d+ at') |
| 192 crash_callstack_start_pattern3 = re.compile(r'^backtrace:') | |
| 189 | 193 |
| 190 freed_callstack_start_pattern = re.compile( | 194 freed_callstack_start_pattern = re.compile( |
| 191 r'^freed by thread T\d+ (.* )?here:') | 195 r'^freed by thread T\d+ (.* )?here:') |
| 192 | 196 |
| 193 allocation_callstack_start_pattern = re.compile( | 197 allocation_callstack_start_pattern = re.compile( |
| 194 r'^previously allocated by thread T\d+ (.* )?here:') | 198 r'^previously allocated by thread T\d+ (.* )?here:') |
| 195 | 199 |
| 196 other_callstack_start_pattern = re.compile( | 200 other_callstack_start_pattern = re.compile( |
| 197 r'^Thread T\d+ (.* )?created by') | 201 r'^Thread T\d+ (.* )?created by') |
| 198 | 202 |
| 199 # Crash stack gets priority 0. | 203 # Crash stack gets priority 0. |
| 200 if (crash_callstack_start_pattern1.match(line) or | 204 if (crash_callstack_start_pattern1.match(line) or |
| 201 crash_callstack_start_pattern2.match(line)): | 205 crash_callstack_start_pattern2.match(line) or |
| 206 crash_callstack_start_pattern3.match(line)): | |
| 202 return (True, 0) | 207 return (True, 0) |
| 203 | 208 |
| 204 # All other callstack gets priority 1. | 209 # All other callstack gets priority 1. |
| 205 if freed_callstack_start_pattern.match(line): | 210 if freed_callstack_start_pattern.match(line): |
| 206 return (True, 1) | 211 return (True, 1) |
| 207 | 212 |
| 208 if allocation_callstack_start_pattern.match(line): | 213 if allocation_callstack_start_pattern.match(line): |
| 209 return (True, 1) | 214 return (True, 1) |
| 210 | 215 |
| 211 if other_callstack_start_pattern.match(line): | 216 if other_callstack_start_pattern.match(line): |
| 212 return (True, 1) | 217 return (True, 1) |
| 213 | 218 |
| 214 # If the line does not match any pattern, return false and a dummy for | 219 # If the line does not match any pattern, return false and a dummy for |
| 215 # stack priority. | 220 # stack priority. |
| 216 return (False, -1) | 221 return (False, -1) |
| 217 | 222 |
| 218 def __GenerateStackFrame(self, stack_frame_index, line, build_type): | 223 def __GenerateStackFrame(self, stack_frame_index, line, build_type, |
| 224 parsed_deps): | |
| 219 """Extracts information from a line in stacktrace. | 225 """Extracts information from a line in stacktrace. |
| 220 | 226 |
| 221 Args: | 227 Args: |
| 222 stack_frame_index: A stack frame index of this line. | 228 stack_frame_index: A stack frame index of this line. |
| 223 line: A stacktrace string to extract data from. | 229 line: A stacktrace string to extract data from. |
| 224 build_type: A string containing the build type | 230 build_type: A string containing the build type |
| 225 of this crash (e.g. linux_asan_chrome_mp). | 231 of this crash (e.g. linux_asan_chrome_mp). |
| 232 parsed_deps: A parsed DEPS file to normalize path with. | |
| 226 | 233 |
| 227 Returns: | 234 Returns: |
| 228 A triple containing the name of the function, the path of the file and | 235 A triple containing the name of the function, the path of the file and |
| 229 the crashed line number. | 236 the crashed line number. |
| 230 """ | 237 """ |
| 231 line_parts = line.split() | 238 line_parts = line.split() |
| 239 try: | |
| 232 | 240 |
| 233 try: | 241 if 'syzyasan' in build_type: |
| 234 # Filter out lines that are not stack frame. | 242 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 | 243 |
| 239 # Tsan has different stack frame style from other builds. | 244 if not stack_frame_match: |
| 240 if build_type.startswith('linux_tsan'): | 245 return None |
| 241 file_path_and_line = line_parts[-2] | 246 file_path = stack_frame_match.group(5) |
| 242 function = ' '.join(line_parts[1:-2]) | 247 crashed_line_number = [int(stack_frame_match.group(6))] |
|
Martin Barbella
2014/08/22 02:24:14
Maybe rename this here and in StackFrame to crashe
jeun
2014/08/22 22:58:44
Done.
| |
| 248 function = stack_frame_match.group(2) | |
| 243 | 249 |
| 244 else: | 250 else: |
| 245 file_path_and_line = line_parts[-1] | 251 if not line_parts[0].startswith('#'): |
| 246 function = ' '.join(line_parts[3:-1]) | 252 return None |
| 247 | 253 |
| 248 # Get file path and line info from the line. | 254 if 'tsan' in build_type: |
| 249 file_path_and_line = file_path_and_line.split(':') | 255 file_path_and_line = line_parts[-2] |
| 250 file_path = file_path_and_line[0] | 256 function = ' '.join(line_parts[1:-2]) |
| 251 crashed_line_number = int(file_path_and_line[1]) | 257 else: |
| 258 file_path_and_line = line_parts[-1] | |
| 259 function = ' '.join(line_parts[3:-1]) | |
| 260 | |
| 261 # Get file path and line info from the line. | |
| 262 file_path_and_line_match = FILE_PATH_AND_LINE_PATTERN.match( | |
| 263 file_path_and_line) | |
| 264 | |
| 265 # Return None if the file path information is not available | |
| 266 if not file_path_and_line_match: | |
| 267 return None | |
| 268 | |
| 269 file_path = file_path_and_line_match.group(1) | |
| 270 | |
| 271 # Get the crashed line range. For example, file_path:line_number:range. | |
| 272 crashed_line_range = file_path_and_line_match.group(3) | |
| 273 | |
| 274 if crashed_line_range: | |
| 275 # Strip ':' prefix. | |
| 276 crashed_line_range = int(crashed_line_range[1:]) | |
| 277 else: | |
| 278 crashed_line_range = 0 | |
| 279 | |
| 280 crashed_line_number = int(file_path_and_line_match.group(2)) | |
| 281 # For example, 655:1 has crashed lines 655 and 656. | |
|
stgao
2014/08/22 06:50:54
How did we know ":1" mean a range?
Is it a functio
jeun
2014/08/22 22:58:44
Done.
| |
| 282 crashed_line_number = \ | |
| 283 range(crashed_line_number, | |
| 284 crashed_line_number + crashed_line_range + 1) | |
| 252 | 285 |
| 253 # Return None if the line is malformed. | 286 # Return None if the line is malformed. |
| 254 except IndexError: | 287 except IndexError: |
| 255 return None | 288 return None |
| 256 except ValueError: | 289 except ValueError: |
| 257 return None | 290 return None |
| 258 | 291 |
| 259 # Normalize the file path so that it can be compared to repository path. | 292 # 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) = ( | 293 (component_path, component_name, file_path) = ( |
| 262 crash_utils.NormalizePathLinux(file_path, self.parsed_deps)) | 294 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 | 295 |
| 268 # Return a new stack frame object with the parsed information. | 296 # Return a new stack frame object with the parsed information. |
| 297 file_name = file_path.split('/')[-1] | |
|
stgao
2014/08/22 06:50:54
os.path.basename
jeun
2014/08/22 22:58:44
This changed to split('/')[-1] so that the code wi
| |
| 269 return StackFrame(stack_frame_index, component_path, component_name, | 298 return StackFrame(stack_frame_index, component_path, component_name, |
| 270 file_name, function, file_path, crashed_line_number) | 299 file_name, function, file_path, crashed_line_number) |
| 271 | 300 |
| 272 def __getitem__(self, index): | 301 def __getitem__(self, index): |
| 273 return self.stack_list[index] | 302 return self.stack_list[index] |
| 274 | 303 |
| 275 def GetCrashStack(self): | 304 def GetCrashStack(self): |
| 276 """Returns the callstack with the highest priority. | 305 """Returns the callstack with the highest priority. |
| 277 | 306 |
| 278 Crash stack has priority 0, and allocation/freed/other thread stacks | 307 Crash stack has priority 0, and allocation/freed/other thread stacks |
| 279 get priority 1. | 308 get priority 1. |
| 280 | 309 |
| 281 Returns: | 310 Returns: |
| 282 The highest priority callstack in the stacktrace. | 311 The highest priority callstack in the stacktrace. |
| 283 """ | 312 """ |
| 284 sorted_stacklist = sorted(self.stack_list, | 313 sorted_stacklist = sorted(self.stack_list, |
| 285 key=lambda callstack: callstack.priority) | 314 key=lambda callstack: callstack.priority) |
| 286 return sorted_stacklist[0] | 315 return sorted_stacklist[0] |
| OLD | NEW |