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

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

Issue 430943003: [Findit] Plain objects to represent and parse stack trace. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: reupload 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
« no previous file with comments | « tools/findit/crash_utils.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 # found in the LICENSE file.
4
5 import os
6 import re
7
8 import crash_utils
9
10
11 class StackFrame(object):
12 """Represents a frame in stacktrace.
13
14 Attributes:
15 index: An index of the stack frame.
16 component_path: The path of the component this line represents.
17 component_name: The name of the component this line represents.
18 file_name: The name of the file that crashed.
19 function: The function that caused the crash.
20 file_path: The path of the crashed file.
21 crashed_line_number: The line of the file that caused the crash.
22 """
23
24 def __init__(self, stack_frame_index, component_path, component_name,
25 file_name, function, file_path, crashed_line_number):
26 self.index = stack_frame_index
27 self.component_path = component_path
28 self.component_name = component_name
29 self.file_name = file_name
30 self.function = function
31 self.file_path = file_path
32 self.crashed_line_number = crashed_line_number
33
34
35 class CallStack(object):
36 """Represents a call stack within a stacktrace.
37
38 It is a list of StackFrame object, and the object keeps track of whether
39 the stack is crash stack, freed or previously-allocated.
40 """
41
42 def __init__(self, stack_priority):
43 self.frame_list = []
44 self.priority = stack_priority
45
46 def Add(self, stacktrace_line):
47 self.frame_list.append(stacktrace_line)
48
49 def GetTopNFrames(self, n):
50 return self.frame_list[:n]
51
52
53 class Stacktrace(object):
54 """Represents Stacktrace object.
55
56 Contains a list of callstacks, because one stacktrace might have more than
57 one callstacks.
58 """
59
60 def __init__(self, stacktrace, build_type, parsed_deps):
61 self.stack_list = None
62 self.parsed_deps = parsed_deps
63 self.ParseStacktrace(stacktrace, build_type)
64
65 def ParseStacktrace(self, stacktrace, build_type):
66 """Parses stacktrace and normalizes it.
67
68 If there are multiple callstacks within the stacktrace,
69 it will parse each of them separately, and store them in the stack_list
70 variable.
71
72 Args:
73 stacktrace: A string containing stacktrace.
74 build_type: A string containing the build type of the crash.
75 """
76 # If the passed in string is empty, the object does not represent anything.
77 if not stacktrace:
78 return
79
80 # Reset the stack list.
81 self.stack_list = []
82 reached_new_callstack = False
83 # 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
85 # index from a line is that some stack frames do not have index.
86 stack_frame_index = 0
87 current_stack = None
88
89 for line in stacktrace:
90 (is_new_callstack, stack_priority) = self.__IsStartOfNewCallStack(
91 line, build_type)
92
93 if is_new_callstack:
94
95 # If this callstack is crash stack, update the boolean.
96 if not reached_new_callstack:
97 reached_new_callstack = True
98 current_stack = CallStack(stack_priority)
99
100 # If this is from freed or allocation, add the callstack we have
101 # to the list of callstacks, and increment the stack priority.
102 else:
103 stack_frame_index = 0
104 if current_stack and current_stack.frame_list:
105 self.stack_list.append(current_stack)
106 current_stack = CallStack(stack_priority)
107
108 # Generate stack frame object from the line.
109 parsed_stack_frame = self.__GenerateStackFrame(
110 stack_frame_index, line, build_type)
111
112 # If the line does not represent the stack frame, ignore this line.
113 if not parsed_stack_frame:
114 continue
115
116 # Add the parsed stack frame object to the current stack.
117 current_stack.Add(parsed_stack_frame)
118 stack_frame_index += 1
119
120 # Add the current callstack only if there are frames in it.
121 if current_stack and current_stack.frame_list:
122 self.stack_list.append(current_stack)
123
124 def __IsStartOfNewCallStack(self, line, build_type):
125 """Check if this line is the start of the new callstack.
126
127 Since each builds have different format of stacktrace, the logic for
128 checking the line for all builds is handled in here.
129
130 Args:
131 line: Line to check for.
132 build_type: The name of the build.
133
134 Returns:
135 True if the line is the start of new callstack, False otherwise. If True,
136 it also returns the priority of the line.
137 """
138 # Currently not supported.
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:',
144 # 'freed stack:', etc.
145 callstack_start_pattern = re.compile(r'^(.*) stack:$')
146 match = callstack_start_pattern.match(line)
147
148 # If the line matches the callstack start pattern.
149 if match:
150 # Check the type of the new match.
151 stack_type = match.group(1)
152
153 # Crash stack gets priority 0.
154 if stack_type == 'Crash':
155 return (True, 0)
156
157 # Other callstacks all get priority 1.
158 else:
159 return (True, 1)
160
161 elif 'tsan' in build_type:
162 # Create patterns for each callstack type.
163 crash_callstack_start_pattern = re.compile(
164 r'^(Read|Write) of size \d+')
165
166 allocation_callstack_start_pattern = re.compile(
167 r'^Previous (write|read) of size \d+')
168
169 location_callstack_start_pattern = re.compile(
170 r'^Location is heap block of size \d+')
171
172 # Crash stack gets priority 0.
173 if crash_callstack_start_pattern.match(line):
174 return (True, 0)
175
176 # All other stacks get priority 1.
177 if allocation_callstack_start_pattern.match(line):
178 return (True, 1)
179
180 if location_callstack_start_pattern.match(line):
181 return (True, 1)
182
183 else:
184 # In asan and other build types, crash stack can start
185 # in two different ways.
186 crash_callstack_start_pattern1 = re.compile(r'^==\d+== ?ERROR:')
187 crash_callstack_start_pattern2 = re.compile(
188 r'^(READ|WRITE) of size \d+ at')
189
190 freed_callstack_start_pattern = re.compile(
191 r'^freed by thread T\d+ (.* )?here:')
192
193 allocation_callstack_start_pattern = re.compile(
194 r'^previously allocated by thread T\d+ (.* )?here:')
195
196 other_callstack_start_pattern = re.compile(
197 r'^Thread T\d+ (.* )?created by')
198
199 # Crash stack gets priority 0.
200 if (crash_callstack_start_pattern1.match(line) or
201 crash_callstack_start_pattern2.match(line)):
202 return (True, 0)
203
204 # All other callstack gets priority 1.
205 if freed_callstack_start_pattern.match(line):
206 return (True, 1)
207
208 if allocation_callstack_start_pattern.match(line):
209 return (True, 1)
210
211 if other_callstack_start_pattern.match(line):
212 return (True, 1)
213
214 # If the line does not match any pattern, return false and a dummy for
215 # stack priority.
216 return (False, -1)
217
218 def __GenerateStackFrame(self, stack_frame_index, line, build_type):
219 """Extracts information from a line in stacktrace.
220
221 Args:
222 stack_frame_index: A stack frame index of this line.
223 line: A stacktrace string to extract data from.
224 build_type: A string containing the build type
225 of this crash (e.g. linux_asan_chrome_mp).
226
227 Returns:
228 A triple containing the name of the function, the path of the file and
229 the crashed line number.
230 """
231 line_parts = line.split()
232
233 try:
234 # Filter out lines that are not stack frame.
235 stack_frame_index_pattern = re.compile(r'#(\d+)')
236 if not stack_frame_index_pattern.match(line_parts[0]):
237 return None
238
239 # Tsan has different stack frame style from other builds.
240 if build_type.startswith('linux_tsan'):
241 file_path_and_line = line_parts[-2]
242 function = ' '.join(line_parts[1:-2])
243
244 else:
245 file_path_and_line = line_parts[-1]
246 function = ' '.join(line_parts[3:-1])
247
248 # Get file path and line info from the line.
249 file_path_and_line = file_path_and_line.split(':')
250 file_path = file_path_and_line[0]
251 crashed_line_number = int(file_path_and_line[1])
252
253 # Return None if the line is malformed.
254 except IndexError:
255 return None
256 except ValueError:
257 return None
258
259 # 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) = (
262 crash_utils.NormalizePathLinux(file_path, self.parsed_deps))
263
264 # If this component is not supported, ignore this line.
265 if not component_path:
266 return None
267
268 # Return a new stack frame object with the parsed information.
269 return StackFrame(stack_frame_index, component_path, component_name,
270 file_name, function, file_path, crashed_line_number)
271
272 def __getitem__(self, index):
273 return self.stack_list[index]
274
275 def GetCrashStack(self):
276 """Returns the callstack with the lowest priority."""
277 sorted_stacklist = sorted(self.stack_list,
278 key=lambda callstack: callstack.priority)
279 return sorted_stacklist[0]
OLDNEW
« no previous file with comments | « tools/findit/crash_utils.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698