OLD | NEW |
1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium OS 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 grp, logging, os, pwd, re, stat, subprocess | 5 import grp, logging, os, pwd, re, stat, subprocess |
6 from signal import SIGSEGV | 6 from signal import SIGSEGV |
7 from autotest_lib.client.bin import site_crash_test, site_utils, test | 7 from autotest_lib.client.bin import site_crash_test, site_utils, test |
8 from autotest_lib.client.common_lib import error, utils | 8 from autotest_lib.client.common_lib import error, utils |
9 | 9 |
10 _CORE_PATTERN = '/proc/sys/kernel/core_pattern' | 10 _CORE_PATTERN = '/proc/sys/kernel/core_pattern' |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
114 # Should identify recursion line which is on the stack | 114 # Should identify recursion line which is on the stack |
115 # for 15 levels | 115 # for 15 levels |
116 if not ('15 %s!recbomb(int) [bomb.cc : 12 ' % basename) in stack: | 116 if not ('15 %s!recbomb(int) [bomb.cc : 12 ' % basename) in stack: |
117 raise error.TestFail('Did not show recursion line on stack') | 117 raise error.TestFail('Did not show recursion line on stack') |
118 | 118 |
119 # Should identify main line | 119 # Should identify main line |
120 if not ('16 %s!main [crasher.cc : 21 ' % basename) in stack: | 120 if not ('16 %s!main [crasher.cc : 21 ' % basename) in stack: |
121 raise error.TestFail('Did not show main on stack') | 121 raise error.TestFail('Did not show main on stack') |
122 | 122 |
123 | 123 |
124 def _run_crasher_process(self, username, cause_crash=True): | 124 def _run_crasher_process(self, username, cause_crash=True, consent=True): |
125 """Runs the crasher process. | 125 """Runs the crasher process. |
126 | 126 |
127 Args: | 127 Args: |
128 username: runs as given user | 128 username: runs as given user |
129 extra_args: additional parameters to pass to crasher process | 129 extra_args: additional parameters to pass to crasher process |
130 | 130 |
131 Returns: | 131 Returns: |
132 A dictionary with keys: | 132 A dictionary with keys: |
133 returncode: return code of the crasher | 133 returncode: return code of the crasher |
134 crashed: did the crasher return segv error code | 134 crashed: did the crasher return segv error code |
135 crash_reporter_caught: did crash_reporter catch a segv | 135 crash_reporter_caught: did crash_reporter catch a segv |
136 output: stderr/stdout output of the crasher process | 136 output: stderr/stdout output of the crasher process |
137 """ | 137 """ |
138 self._prepare_crasher() | 138 self._prepare_crasher() |
139 self._populate_symbols() | 139 self._populate_symbols() |
140 | 140 |
141 if username != 'root': | 141 if username != 'root': |
142 crasher_command = ['su', username, '-c'] | 142 crasher_command = ['su', username, '-c'] |
143 expected_result = 128 + SIGSEGV | 143 expected_result = 128 + SIGSEGV |
144 else: | 144 else: |
145 crasher_command = [] | 145 crasher_command = [] |
146 expected_result = -SIGSEGV | 146 expected_result = -SIGSEGV |
147 | 147 |
148 crasher_command.append(self._crasher_path) | 148 crasher_command.append(self._crasher_path) |
149 basename = os.path.basename(self._crasher_path) | 149 basename = os.path.basename(self._crasher_path) |
150 if not cause_crash: | 150 if not cause_crash: |
151 crasher_command.append('--nocrash') | 151 crasher_command.append('--nocrash') |
| 152 self._set_consent(consent) |
152 crasher = subprocess.Popen(crasher_command, | 153 crasher = subprocess.Popen(crasher_command, |
153 stdout=subprocess.PIPE, | 154 stdout=subprocess.PIPE, |
154 stderr=subprocess.PIPE) | 155 stderr=subprocess.PIPE) |
155 output = crasher.communicate()[1] | 156 output = crasher.communicate()[1] |
156 logging.debug('Output from %s: %s' % | 157 logging.debug('Output from %s: %s' % |
157 (self._crasher_path, output)) | 158 (self._crasher_path, output)) |
158 | 159 |
159 # Grab the pid from the process output. We can't just use | 160 # Grab the pid from the process output. We can't just use |
160 # crasher.pid unfortunately because that may be the PID of su. | 161 # crasher.pid unfortunately because that may be the PID of su. |
161 match = re.search(r'pid=(\d+)', output) | 162 match = re.search(r'pid=(\d+)', output) |
162 if not match: | 163 if not match: |
163 raise error.TestFail('Could not find pid output from crasher: %s' % | 164 raise error.TestFail('Could not find pid output from crasher: %s' % |
164 output) | 165 output) |
165 pid = int(match.group(1)) | 166 pid = int(match.group(1)) |
166 | 167 |
167 expected_message = ('Received crash notification for ' | 168 if consent: |
168 '%s[%d] sig 11' % (basename, pid)) | 169 handled_string = 'handling' |
| 170 else: |
| 171 handled_string = 'ignoring' |
| 172 expected_message = ( |
| 173 'Received crash notification for %s[%d] sig 11 (%s)' % |
| 174 (basename, pid, handled_string)) |
169 | 175 |
170 # Wait until no crash_reporter is running. | 176 # Wait until no crash_reporter is running. |
171 site_utils.poll_for_condition( | 177 site_utils.poll_for_condition( |
172 lambda: utils.system('pgrep crash_reporter', | 178 lambda: utils.system('pgrep crash_reporter', |
173 ignore_status=True) != 0, | 179 ignore_status=True) != 0, |
174 timeout=10, | 180 timeout=10, |
175 exception=error.TestError( | 181 exception=error.TestError( |
176 'Timeout waiting for crash_reporter to finish: ' + | 182 'Timeout waiting for crash_reporter to finish: ' + |
177 self._log_reader.get_logs())) | 183 self._log_reader.get_logs())) |
178 | 184 |
179 logging.debug('crash_reporter_caught message: ' + expected_message) | 185 logging.debug('crash_reporter_caught message: ' + expected_message) |
180 crash_reporter_caught = self._log_reader.can_find(expected_message) | 186 is_caught = self._log_reader.can_find(expected_message) |
181 | 187 |
182 result = {'crashed': crasher.returncode == expected_result, | 188 result = {'crashed': crasher.returncode == expected_result, |
183 'crash_reporter_caught': crash_reporter_caught, | 189 'crash_reporter_caught': is_caught, |
184 'output': output, | 190 'output': output, |
185 'returncode': crasher.returncode} | 191 'returncode': crasher.returncode} |
186 logging.debug('Crasher process result: %s' % result) | 192 logging.debug('Crasher process result: %s' % result) |
187 return result | 193 return result |
188 | 194 |
189 | 195 |
190 def _check_crash_directory_permissions(self, crash_dir): | 196 def _check_crash_directory_permissions(self, crash_dir): |
191 stat_info = os.stat(crash_dir) | 197 stat_info = os.stat(crash_dir) |
192 user = pwd.getpwuid(stat_info.st_uid)[0] | 198 user = pwd.getpwuid(stat_info.st_uid)[0] |
193 group = grp.getgrgid(stat_info.st_gid)[0] | 199 group = grp.getgrgid(stat_info.st_gid)[0] |
(...skipping 19 matching lines...) Expand all Loading... |
213 | 219 |
214 | 220 |
215 def _check_minidump_stackwalk(self, minidump_path, basename, | 221 def _check_minidump_stackwalk(self, minidump_path, basename, |
216 from_crash_reporter): | 222 from_crash_reporter): |
217 # Now stackwalk the minidump | 223 # Now stackwalk the minidump |
218 stack = utils.system_output('/usr/bin/minidump_stackwalk %s %s' % | 224 stack = utils.system_output('/usr/bin/minidump_stackwalk %s %s' % |
219 (minidump_path, self._symbol_dir)) | 225 (minidump_path, self._symbol_dir)) |
220 self._verify_stack(stack, basename, from_crash_reporter) | 226 self._verify_stack(stack, basename, from_crash_reporter) |
221 | 227 |
222 | 228 |
223 def _check_generated_minidump_sending(self, minidump_path, | 229 def _check_generated_minidump_sending(self, meta_path, minidump_path, |
224 username, crasher_basename, | 230 username, crasher_basename, |
225 will_syslog_give_name): | 231 will_syslog_give_name): |
226 # Now check that the sending works | 232 # Now check that the sending works |
227 self._set_sending(True) | 233 self._set_sending(True) |
228 result = self._call_sender_one_crash( | 234 result = self._call_sender_one_crash( |
229 username=username, | 235 username=username, |
230 report=os.path.basename(minidump_path)) | 236 report=os.path.basename(minidump_path)) |
231 if (not result['send_attempt'] or not result['send_success'] or | 237 if (not result['send_attempt'] or not result['send_success'] or |
232 result['report_exists']): | 238 result['report_exists']): |
233 raise error.TestFail('Minidump not sent properly') | 239 raise error.TestFail('Minidump not sent properly') |
234 if will_syslog_give_name: | 240 if will_syslog_give_name: |
235 if result['exec_name'] != crasher_basename: | 241 if result['exec_name'] != crasher_basename: |
236 raise error.TestFail('Executable name incorrect') | 242 raise error.TestFail('Executable name incorrect') |
237 if result['report_kind'] != 'minidump': | 243 if result['report_kind'] != 'minidump': |
238 raise error.TestFail('Expected a minidump report') | 244 raise error.TestFail('Expected a minidump report') |
239 if result['report_name'] != minidump_path: | 245 if result['report_payload'] != minidump_path: |
240 raise error.TestFail('Sent the wrong minidump report') | 246 raise error.TestFail('Sent the wrong minidump payload') |
| 247 if result['meta_path'] != meta_path: |
| 248 raise error.TestFail('Used the wrong meta file') |
| 249 |
| 250 # Check version matches. |
| 251 lsb_release = utils.read_file('/etc/lsb-release') |
| 252 version_match = re.search(r'CHROMEOS_RELEASE_VERSION=(.*)', lsb_release) |
| 253 if not ('Version: %s' % version_match.group(1)) in result['output']: |
| 254 raise error.TestFail('Did not find version %s in log output' % |
| 255 version_match.group(1)) |
241 | 256 |
242 | 257 |
243 def _check_crashing_process(self, username): | 258 def _check_crashing_process(self, username, consent=True): |
244 self._log_reader.set_start_by_current() | 259 self._log_reader.set_start_by_current() |
245 | 260 |
246 result = self._run_crasher_process(username) | 261 result = self._run_crasher_process(username, consent=consent) |
247 | 262 |
248 if not result['crashed']: | 263 if not result['crashed']: |
249 raise error.TestFail('crasher did not do its job of crashing: %d' % | 264 raise error.TestFail('crasher did not do its job of crashing: %d' % |
250 result['returncode']) | 265 result['returncode']) |
251 | 266 |
252 if not result['crash_reporter_caught']: | 267 if not result['crash_reporter_caught']: |
253 logging.debug('Messages that should have included segv: %s' % | 268 logging.debug('Messages that should have included segv: %s' % |
254 self._log_reader.get_logs()) | 269 self._log_reader.get_logs()) |
255 raise error.TestFail('Did not find segv message') | 270 raise error.TestFail('Did not find segv message') |
256 | 271 |
257 crash_dir = self._get_crash_dir(username) | 272 crash_dir = self._get_crash_dir(username) |
| 273 |
| 274 if not consent: |
| 275 if os.path.exists(crash_dir): |
| 276 raise error.TestFail('Crash directory should not exist') |
| 277 return |
| 278 |
258 crash_contents = os.listdir(crash_dir) | 279 crash_contents = os.listdir(crash_dir) |
259 basename = os.path.basename(self._crasher_path) | 280 basename = os.path.basename(self._crasher_path) |
260 | 281 |
261 breakpad_minidump = None | 282 breakpad_minidump = None |
262 crash_reporter_minidump = None | 283 crash_reporter_minidump = None |
| 284 crash_reporter_meta = None |
263 | 285 |
264 self._check_crash_directory_permissions(crash_dir) | 286 self._check_crash_directory_permissions(crash_dir) |
265 | 287 |
266 logging.debug('Contents in %s: %s' % (crash_dir, crash_contents)) | 288 logging.debug('Contents in %s: %s' % (crash_dir, crash_contents)) |
267 | 289 |
268 for filename in crash_contents: | 290 for filename in crash_contents: |
269 if filename.endswith('.core'): | 291 if filename.endswith('.core'): |
270 # Ignore core files. We'll test them later. | 292 # Ignore core files. We'll test them later. |
271 pass | 293 pass |
272 elif filename.startswith(basename): | 294 elif (filename.startswith(basename) and |
| 295 filename.endswith('.dmp')): |
273 # This appears to be a minidump created by the crash reporter. | 296 # This appears to be a minidump created by the crash reporter. |
274 if not crash_reporter_minidump is None: | 297 if not crash_reporter_minidump is None: |
275 raise error.TestFail('Crash reporter wrote multiple ' | 298 raise error.TestFail('Crash reporter wrote multiple ' |
276 'minidumps') | 299 'minidumps') |
277 crash_reporter_minidump = os.path.join(crash_dir, filename) | 300 crash_reporter_minidump = os.path.join(crash_dir, filename) |
| 301 elif (filename.startswith(basename) and |
| 302 filename.endswith('.meta')): |
| 303 if not crash_reporter_meta is None: |
| 304 raise error.TestFail('Crash reported wrote multiple ' |
| 305 'meta files') |
| 306 crash_reporter_meta = os.path.join(crash_dir, filename) |
278 else: | 307 else: |
279 # This appears to be a breakpad created minidump. | 308 # This appears to be a breakpad created minidump. |
280 if not breakpad_minidump is None: | 309 if not breakpad_minidump is None: |
281 raise error.TestFail('Breakpad wrote multimpe minidumps') | 310 raise error.TestFail('Breakpad wrote multimpe minidumps') |
282 breakpad_minidump = os.path.join(crash_dir, filename) | 311 breakpad_minidump = os.path.join(crash_dir, filename) |
283 | 312 |
284 if breakpad_minidump: | 313 if breakpad_minidump: |
285 raise error.TestFail('%s did generate breakpad minidump' % basename) | 314 raise error.TestFail('%s did generate breakpad minidump' % basename) |
286 | 315 |
287 if not crash_reporter_minidump: | 316 if not crash_reporter_minidump: |
288 raise error.TestFail('crash reporter did not generate minidump') | 317 raise error.TestFail('crash reporter did not generate minidump') |
289 | 318 |
| 319 if not crash_reporter_meta: |
| 320 raise error.TestFail('crash reporter did not generate meta') |
| 321 |
290 if not self._log_reader.can_find('Stored minidump to ' + | 322 if not self._log_reader.can_find('Stored minidump to ' + |
291 crash_reporter_minidump): | 323 crash_reporter_minidump): |
292 raise error.TestFail('crash reporter did not announce minidump') | 324 raise error.TestFail('crash reporter did not announce minidump') |
293 | 325 |
294 # By default test sending the crash_reporter minidump unless there | |
295 # is a breakpad minidump, and then we test sending it instead. | |
296 send_minidump = crash_reporter_minidump | |
297 | |
298 if crash_reporter_minidump: | 326 if crash_reporter_minidump: |
299 self._check_minidump_stackwalk(crash_reporter_minidump, | 327 self._check_minidump_stackwalk(crash_reporter_minidump, |
300 basename, | 328 basename, |
301 from_crash_reporter=True) | 329 from_crash_reporter=True) |
302 will_syslog_give_name = True | 330 will_syslog_give_name = True |
303 | 331 |
304 if breakpad_minidump: | 332 self._check_generated_minidump_sending(crash_reporter_meta, |
305 self._check_minidump_stackwalk(breakpad_minidump, | 333 crash_reporter_minidump, |
306 basename, | |
307 from_crash_reporter=False) | |
308 send_minidump = breakpad_minidump | |
309 os.unlink(crash_reporter_minidump) | |
310 # If you link against -lcrash, upon sending the syslog will | |
311 # just say exec_name: <very-long-guid>, where that GUID is the | |
312 # GUID generated during breakpad. Since -lcrash is going away, | |
313 # it seems ok to have the syslog be a little more opaque for | |
314 # these crashes. They'll be next to sends for the real crash | |
315 # anyway. | |
316 will_syslog_give_name = False | |
317 | |
318 self._check_generated_minidump_sending(send_minidump, | |
319 username, | 334 username, |
320 basename, | 335 basename, |
321 will_syslog_give_name) | 336 will_syslog_give_name) |
322 | 337 |
323 def _test_no_crash(self): | 338 def _test_no_crash(self): |
324 """Test a program linked against libcrash_dumper can exit normally.""" | 339 """Test a program linked against libcrash_dumper can exit normally.""" |
325 self._log_reader.set_start_by_current() | 340 self._log_reader.set_start_by_current() |
326 result = self._run_crasher_process(username='root', | 341 result = self._run_crasher_process(username='root', |
327 cause_crash=False) | 342 cause_crash=False) |
328 if (result['crashed'] or | 343 if (result['crashed'] or |
329 result['crash_reporter_caught'] or | 344 result['crash_reporter_caught'] or |
330 result['returncode'] != 0): | 345 result['returncode'] != 0): |
331 raise error.TestFail('Normal exit of program with dumper failed') | 346 raise error.TestFail('Normal exit of program with dumper failed') |
332 | 347 |
333 | 348 |
334 def _test_chronos_nobreakpad_crasher(self): | 349 def _test_chronos_crasher(self): |
335 """Test a user space crash when running as chronos is handled.""" | 350 """Test a user space crash when running as chronos is handled.""" |
336 self._check_crashing_process('chronos') | 351 self._check_crashing_process('chronos') |
337 | 352 |
338 | 353 |
339 def _test_root_nobreakpad_crasher(self): | 354 def _test_chronos_crasher_no_consent(self): |
| 355 """Test that without consent no files are stored.""" |
| 356 results = self._check_crashing_process('chronos', consent=False) |
| 357 |
| 358 |
| 359 def _test_root_crasher(self): |
340 """Test a user space crash when running as root is handled.""" | 360 """Test a user space crash when running as root is handled.""" |
341 self._check_crashing_process('root') | 361 self._check_crashing_process('root') |
342 | 362 |
343 | 363 |
| 364 def _test_root_crasher_no_consent(self): |
| 365 """Test that without consent no files are stored.""" |
| 366 results = self._check_crashing_process('root', consent=False) |
| 367 |
| 368 |
344 def _test_max_enqueued_crashes(self): | 369 def _test_max_enqueued_crashes(self): |
345 """Test that _MAX_CRASH_DIRECTORY_SIZE is enforced.""" | 370 """Test that _MAX_CRASH_DIRECTORY_SIZE is enforced.""" |
346 self._log_reader.set_start_by_current() | 371 self._log_reader.set_start_by_current() |
347 username = 'root' | 372 username = 'root' |
348 | 373 |
349 crash_dir = self._get_crash_dir(username) | 374 crash_dir = self._get_crash_dir(username) |
350 full_message = ('Crash directory %s already full with %d pending ' | 375 full_message = ('Crash directory %s already full with %d pending ' |
351 'reports' % (crash_dir, _MAX_CRASH_DIRECTORY_SIZE)) | 376 'reports' % (crash_dir, _MAX_CRASH_DIRECTORY_SIZE)) |
352 | 377 |
353 # Fill up the queue. | 378 # Fill up the queue. |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
443 os.system('umount /root') | 468 os.system('umount /root') |
444 | 469 |
445 | 470 |
446 # TODO(kmixter): Test crashing a process as ntp or some other | 471 # TODO(kmixter): Test crashing a process as ntp or some other |
447 # non-root, non-chronos user. | 472 # non-root, non-chronos user. |
448 | 473 |
449 def run_once(self): | 474 def run_once(self): |
450 self.run_crash_tests(['reporter_startup', | 475 self.run_crash_tests(['reporter_startup', |
451 'reporter_shutdown', | 476 'reporter_shutdown', |
452 'no_crash', | 477 'no_crash', |
453 'chronos_nobreakpad_crasher', | 478 'chronos_crasher', |
454 'root_nobreakpad_crasher', | 479 'chronos_crasher_no_consent', |
| 480 'root_crasher', |
| 481 'root_crasher_no_consent', |
455 'max_enqueued_crashes', | 482 'max_enqueued_crashes', |
456 'core_file_persists_in_debug', | 483 'core_file_persists_in_debug', |
457 'core_file_removed_in_production'], | 484 'core_file_removed_in_production'], |
458 initialize_crash_reporter = True) | 485 initialize_crash_reporter = True) |
OLD | NEW |