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