Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: tools/findit/stacktrace.py

Issue 478763003: [Findit] Bug fixing and implemented some feature requests. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed codereview and removed all references to logging Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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]
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698