2015-10-08 21:09:40 -07:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# Copyright 2015 The Crashpad Authors. All rights reserved.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2017-11-29 13:26:55 -05:00
|
|
|
from __future__ import print_function
|
|
|
|
|
2015-10-08 21:09:40 -07:00
|
|
|
import os
|
2015-10-16 14:55:14 -07:00
|
|
|
import platform
|
2017-10-05 15:57:19 -04:00
|
|
|
import pywintypes
|
2015-10-09 13:39:39 -07:00
|
|
|
import random
|
|
|
|
import re
|
|
|
|
import subprocess
|
2015-10-08 21:09:40 -07:00
|
|
|
import sys
|
2015-10-09 13:39:39 -07:00
|
|
|
import tempfile
|
2015-11-06 15:54:48 -08:00
|
|
|
import time
|
2017-03-24 18:06:08 -04:00
|
|
|
import win32con
|
2017-10-05 15:57:19 -04:00
|
|
|
import win32pipe
|
|
|
|
import winerror
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2016-02-26 14:42:47 -08:00
|
|
|
|
2015-10-09 13:39:39 -07:00
|
|
|
g_temp_dirs = []
|
2016-10-21 13:08:18 -07:00
|
|
|
g_had_failures = False
|
2015-10-09 13:39:39 -07:00
|
|
|
|
|
|
|
|
|
|
|
def MakeTempDir():
|
|
|
|
global g_temp_dirs
|
|
|
|
new_dir = tempfile.mkdtemp()
|
|
|
|
g_temp_dirs.append(new_dir)
|
|
|
|
return new_dir
|
|
|
|
|
|
|
|
|
|
|
|
def CleanUpTempDirs():
|
|
|
|
global g_temp_dirs
|
|
|
|
for d in g_temp_dirs:
|
|
|
|
subprocess.call(['rmdir', '/s', '/q', d], shell=True)
|
2015-10-08 21:09:40 -07:00
|
|
|
|
|
|
|
|
|
|
|
def FindInstalledWindowsApplication(app_path):
|
|
|
|
search_paths = [os.getenv('PROGRAMFILES(X86)'),
|
|
|
|
os.getenv('PROGRAMFILES'),
|
2015-11-02 09:35:08 -08:00
|
|
|
os.getenv('PROGRAMW6432'),
|
2015-10-08 21:09:40 -07:00
|
|
|
os.getenv('LOCALAPPDATA')]
|
|
|
|
search_paths += os.getenv('PATH', '').split(os.pathsep)
|
|
|
|
|
|
|
|
for search_path in search_paths:
|
|
|
|
if not search_path:
|
|
|
|
continue
|
|
|
|
path = os.path.join(search_path, app_path)
|
|
|
|
if os.path.isfile(path):
|
|
|
|
return path
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def GetCdbPath():
|
2015-10-09 13:39:39 -07:00
|
|
|
"""Search in some reasonable places to find cdb.exe. Searches x64 before x86
|
|
|
|
and newer versions before older versions.
|
|
|
|
"""
|
2015-10-08 21:09:40 -07:00
|
|
|
possible_paths = (
|
|
|
|
os.path.join('Windows Kits', '10', 'Debuggers', 'x64'),
|
|
|
|
os.path.join('Windows Kits', '10', 'Debuggers', 'x86'),
|
|
|
|
os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'),
|
|
|
|
os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'),
|
|
|
|
os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'),
|
|
|
|
os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'),
|
|
|
|
'Debugging Tools For Windows (x64)',
|
|
|
|
'Debugging Tools For Windows (x86)',
|
2015-10-09 13:39:39 -07:00
|
|
|
'Debugging Tools For Windows',)
|
2015-10-08 21:09:40 -07:00
|
|
|
for possible_path in possible_paths:
|
|
|
|
app_path = os.path.join(possible_path, 'cdb.exe')
|
|
|
|
app_path = FindInstalledWindowsApplication(app_path)
|
|
|
|
if app_path:
|
|
|
|
return app_path
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2017-10-05 15:57:19 -04:00
|
|
|
def NamedPipeExistsAndReady(pipe_name):
|
|
|
|
"""Returns False if pipe_name does not exist. If pipe_name does exist, blocks
|
|
|
|
until the pipe is ready to service clients, and then returns True.
|
|
|
|
|
|
|
|
This is used as a drop-in replacement for os.path.exists() and os.access() to
|
|
|
|
test for the pipe's existence. Both of those calls tickle the pipe in a way
|
|
|
|
that appears to the server to be a client connecting, triggering error
|
|
|
|
messages when no data is received.
|
|
|
|
|
|
|
|
Although this function only needs to test pipe existence (waiting for
|
|
|
|
CreateNamedPipe()), it actually winds up testing pipe readiness
|
|
|
|
(waiting for ConnectNamedPipe()). This is unnecessary but harmless.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
win32pipe.WaitNamedPipe(pipe_name, win32pipe.NMPWAIT_WAIT_FOREVER)
|
2017-11-29 13:26:55 -05:00
|
|
|
except pywintypes.error as e:
|
2017-10-05 15:57:19 -04:00
|
|
|
if e[0] == winerror.ERROR_FILE_NOT_FOUND:
|
|
|
|
return False
|
|
|
|
raise
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2017-03-24 18:06:08 -04:00
|
|
|
def GetDumpFromProgram(
|
|
|
|
out_dir, pipe_name, executable_name, expect_exit_code, *args):
|
2015-11-02 13:59:36 -05:00
|
|
|
"""Initialize a crash database, and run |executable_name| connecting to a
|
|
|
|
crash handler. If pipe_name is set, crashpad_handler will be started first. If
|
|
|
|
pipe_name is empty, the executable is responsible for starting
|
2016-04-22 10:03:59 -07:00
|
|
|
crashpad_handler. *args will be passed after other arguments to
|
2017-03-24 18:06:08 -04:00
|
|
|
executable_name. If the child process does not exit with |expect_exit_code|,
|
|
|
|
an exception will be raised. Returns the path to the minidump generated by
|
|
|
|
crashpad_handler for further testing.
|
2015-10-09 13:39:39 -07:00
|
|
|
"""
|
|
|
|
test_database = MakeTempDir()
|
2015-10-09 13:59:35 -07:00
|
|
|
handler = None
|
2015-10-09 13:39:39 -07:00
|
|
|
|
|
|
|
try:
|
2017-03-24 18:06:08 -04:00
|
|
|
subprocess.check_call(
|
2015-10-09 13:39:39 -07:00
|
|
|
[os.path.join(out_dir, 'crashpad_database_util.exe'), '--create',
|
2017-03-24 18:06:08 -04:00
|
|
|
'--database=' + test_database])
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2015-11-02 13:59:36 -05:00
|
|
|
if pipe_name is not None:
|
|
|
|
handler = subprocess.Popen([
|
2016-08-16 16:30:31 -07:00
|
|
|
os.path.join(out_dir, 'crashpad_handler.com'),
|
2015-11-02 13:59:36 -05:00
|
|
|
'--pipe-name=' + pipe_name,
|
|
|
|
'--database=' + test_database
|
|
|
|
])
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2015-11-06 15:54:48 -08:00
|
|
|
# Wait until the server is ready.
|
|
|
|
printed = False
|
2017-10-05 15:57:19 -04:00
|
|
|
while not NamedPipeExistsAndReady(pipe_name):
|
2015-11-06 15:54:48 -08:00
|
|
|
if not printed:
|
2017-11-29 13:26:55 -05:00
|
|
|
print('Waiting for crashpad_handler to be ready...')
|
2015-11-06 15:54:48 -08:00
|
|
|
printed = True
|
2017-10-05 15:57:19 -04:00
|
|
|
time.sleep(0.001)
|
2015-11-06 15:54:48 -08:00
|
|
|
|
2017-10-05 15:57:19 -04:00
|
|
|
command = [os.path.join(out_dir, executable_name), pipe_name] + list(args)
|
2015-11-02 13:59:36 -05:00
|
|
|
else:
|
2017-10-05 15:57:19 -04:00
|
|
|
command = ([os.path.join(out_dir, executable_name),
|
|
|
|
os.path.join(out_dir, 'crashpad_handler.com'),
|
|
|
|
test_database] +
|
|
|
|
list(args))
|
2017-11-29 13:26:55 -05:00
|
|
|
print('Running %s' % os.path.basename(command[0]))
|
2017-10-05 15:57:19 -04:00
|
|
|
exit_code = subprocess.call(command)
|
2017-03-24 18:06:08 -04:00
|
|
|
if exit_code != expect_exit_code:
|
2017-10-04 11:40:23 -04:00
|
|
|
raise subprocess.CalledProcessError(exit_code, executable_name)
|
2015-10-09 13:39:39 -07:00
|
|
|
|
|
|
|
out = subprocess.check_output([
|
|
|
|
os.path.join(out_dir, 'crashpad_database_util.exe'),
|
|
|
|
'--database=' + test_database,
|
2018-04-12 19:37:06 -07:00
|
|
|
'--show-pending-reports',
|
2015-10-09 13:39:39 -07:00
|
|
|
'--show-all-report-info',
|
|
|
|
])
|
|
|
|
for line in out.splitlines():
|
|
|
|
if line.strip().startswith('Path:'):
|
|
|
|
return line.partition(':')[2].strip()
|
|
|
|
finally:
|
|
|
|
if handler:
|
|
|
|
handler.kill()
|
|
|
|
|
|
|
|
|
2015-10-21 16:07:03 -07:00
|
|
|
def GetDumpFromCrashyProgram(out_dir, pipe_name):
|
2017-03-24 18:06:08 -04:00
|
|
|
return GetDumpFromProgram(out_dir,
|
|
|
|
pipe_name,
|
|
|
|
'crashy_program.exe',
|
|
|
|
win32con.EXCEPTION_ACCESS_VIOLATION)
|
2015-10-21 16:07:03 -07:00
|
|
|
|
|
|
|
|
2016-04-22 10:03:59 -07:00
|
|
|
def GetDumpFromOtherProgram(out_dir, pipe_name, *args):
|
2017-03-24 18:06:08 -04:00
|
|
|
return GetDumpFromProgram(
|
|
|
|
out_dir, pipe_name, 'crash_other_program.exe', 0, *args)
|
2016-04-22 10:03:59 -07:00
|
|
|
|
|
|
|
|
2016-11-17 14:00:21 -08:00
|
|
|
def GetDumpFromSignal(out_dir, pipe_name, *args):
|
2017-03-24 18:06:08 -04:00
|
|
|
STATUS_FATAL_APP_EXIT = 0x40000015 # Not known by win32con.
|
|
|
|
return GetDumpFromProgram(out_dir,
|
|
|
|
pipe_name,
|
|
|
|
'crashy_signal.exe',
|
|
|
|
STATUS_FATAL_APP_EXIT,
|
|
|
|
*args)
|
2016-11-17 14:00:21 -08:00
|
|
|
|
|
|
|
|
2015-10-21 16:07:03 -07:00
|
|
|
def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name):
|
2017-03-24 18:06:08 -04:00
|
|
|
return GetDumpFromProgram(out_dir,
|
|
|
|
pipe_name,
|
|
|
|
'self_destroying_program.exe',
|
|
|
|
win32con.EXCEPTION_BREAKPOINT)
|
2015-10-21 16:07:03 -07:00
|
|
|
|
|
|
|
|
2015-10-31 11:45:39 -07:00
|
|
|
def GetDumpFromZ7Program(out_dir, pipe_name):
|
2017-03-24 18:06:08 -04:00
|
|
|
return GetDumpFromProgram(out_dir,
|
|
|
|
pipe_name,
|
|
|
|
'crashy_z7_loader.exe',
|
|
|
|
win32con.EXCEPTION_ACCESS_VIOLATION)
|
2015-10-31 11:45:39 -07:00
|
|
|
|
|
|
|
|
2015-10-09 13:39:39 -07:00
|
|
|
class CdbRun(object):
|
|
|
|
"""Run cdb.exe passing it a cdb command and capturing the output.
|
|
|
|
`Check()` searches for regex patterns in sequence allowing verification of
|
|
|
|
expected output.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, cdb_path, dump_path, command):
|
|
|
|
# Run a command line that loads the dump, runs the specified cdb command,
|
|
|
|
# and then quits, and capturing stdout.
|
|
|
|
self.out = subprocess.check_output([
|
|
|
|
cdb_path,
|
|
|
|
'-z', dump_path,
|
|
|
|
'-c', command + ';q'
|
|
|
|
])
|
|
|
|
|
2015-10-09 16:50:14 -07:00
|
|
|
def Check(self, pattern, message, re_flags=0):
|
|
|
|
match_obj = re.search(pattern, self.out, re_flags)
|
2015-10-09 13:39:39 -07:00
|
|
|
if match_obj:
|
|
|
|
# Matched. Consume up to end of match.
|
|
|
|
self.out = self.out[match_obj.end(0):]
|
2017-11-29 13:26:55 -05:00
|
|
|
print('ok - %s' % message)
|
2015-10-15 15:03:18 -07:00
|
|
|
sys.stdout.flush()
|
2015-10-09 13:39:39 -07:00
|
|
|
else:
|
2017-11-29 13:26:55 -05:00
|
|
|
print('-' * 80, file=sys.stderr)
|
|
|
|
print('FAILED - %s' % message, file=sys.stderr)
|
|
|
|
print('-' * 80, file=sys.stderr)
|
|
|
|
print('did not match:\n %s' % pattern, file=sys.stderr)
|
|
|
|
print('-' * 80, file=sys.stderr)
|
|
|
|
print('remaining output was:\n %s' % self.out, file=sys.stderr)
|
|
|
|
print('-' * 80, file=sys.stderr)
|
2015-10-15 15:03:18 -07:00
|
|
|
sys.stderr.flush()
|
2016-10-21 13:08:18 -07:00
|
|
|
global g_had_failures
|
|
|
|
g_had_failures = True
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2016-11-03 11:35:19 -07:00
|
|
|
def Find(self, pattern, re_flags=0):
|
|
|
|
match_obj = re.search(pattern, self.out, re_flags)
|
|
|
|
if match_obj:
|
|
|
|
# Matched. Consume up to end of match.
|
|
|
|
self.out = self.out[match_obj.end(0):]
|
|
|
|
return match_obj
|
|
|
|
return None
|
|
|
|
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2015-11-02 13:59:36 -05:00
|
|
|
def RunTests(cdb_path,
|
|
|
|
dump_path,
|
|
|
|
start_handler_dump_path,
|
|
|
|
destroyed_dump_path,
|
|
|
|
z7_dump_path,
|
2016-04-22 10:03:59 -07:00
|
|
|
other_program_path,
|
|
|
|
other_program_no_exception_path,
|
2016-11-17 14:00:21 -08:00
|
|
|
sigabrt_main_path,
|
|
|
|
sigabrt_background_path,
|
2015-11-02 13:59:36 -05:00
|
|
|
pipe_name):
|
2015-10-09 13:39:39 -07:00
|
|
|
"""Runs various tests in sequence. Runs a new cdb instance on the dump for
|
|
|
|
each block of tests to reduce the chances that output from one command is
|
|
|
|
confused for output from another.
|
|
|
|
"""
|
|
|
|
out = CdbRun(cdb_path, dump_path, '.ecxr')
|
|
|
|
out.Check('This dump file has an exception of interest stored in it',
|
|
|
|
'captured exception')
|
2016-08-12 13:24:01 -04:00
|
|
|
|
|
|
|
# When SomeCrashyFunction is inlined, cdb doesn't demangle its namespace as
|
|
|
|
# "`anonymous namespace'" and instead gives the decorated form.
|
|
|
|
out.Check('crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::'
|
|
|
|
'SomeCrashyFunction',
|
|
|
|
'exception at correct location')
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2015-11-02 13:59:36 -05:00
|
|
|
out = CdbRun(cdb_path, start_handler_dump_path, '.ecxr')
|
|
|
|
out.Check('This dump file has an exception of interest stored in it',
|
|
|
|
'captured exception (using StartHandler())')
|
2016-08-12 13:24:01 -04:00
|
|
|
out.Check('crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::'
|
|
|
|
'SomeCrashyFunction',
|
|
|
|
'exception at correct location (using StartHandler())')
|
2015-11-02 13:59:36 -05:00
|
|
|
|
2015-10-09 13:39:39 -07:00
|
|
|
out = CdbRun(cdb_path, dump_path, '!peb')
|
|
|
|
out.Check(r'PEB at', 'found the PEB')
|
|
|
|
out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved')
|
|
|
|
out.Check(r'Base TimeStamp Module', 'module list present')
|
|
|
|
pipe_name_escaped = pipe_name.replace('\\', '\\\\')
|
win: Fix process_structs.h definition of RTL_USER_PROCESS_PARAMETERS
In the 64-bit version of the structure, padding is needed between
ShowWindowFlags and WindowTitle.
The CurrentDirectores (yes, that’s how it’s spelled) members would have
been interpreted incorrectly because STRING was defined incorrectly. The
length fields are USHORT, not DWORD. In the 64-bit version of the
structure, a padding member ensured that the structure was at least the
correct size. In the 32-bit version of the structure, this caused the
structure size to be inflated, so all but the first CurrentDirectores
element and any struct member that followed would appear at incorrect
offsets, and the overall struct size being read was larger than
appropriate.
This resolves crashpad_handler logging (usually) three errors while
handling a 64-bit process crash, such as:
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at
0x780f24de00000000, size 0x275 fully unreadable
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at
0x780f24fe00000000, size 0x275 fully unreadable
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at 0x0,
size 0x275 fully unreadable
Bug: crashpad:198
Test: end_to_end_test.py
Change-Id: I1655101de01cf46b4b50eda45a11f8d0f3bca8b3
Reviewed-on: https://chromium-review.googlesource.com/701736
Reviewed-by: Leonard Mosescu <mosescu@chromium.org>
2017-10-05 00:11:45 -04:00
|
|
|
out.Check(r'CommandLine: *\'.*crashy_program\.exe *' + pipe_name_escaped,
|
2015-10-09 13:39:39 -07:00
|
|
|
'some PEB data is correct')
|
2015-10-09 16:50:14 -07:00
|
|
|
out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured',
|
|
|
|
re.IGNORECASE)
|
2015-10-09 13:39:39 -07:00
|
|
|
|
win: Fix process_structs.h definition of RTL_USER_PROCESS_PARAMETERS
In the 64-bit version of the structure, padding is needed between
ShowWindowFlags and WindowTitle.
The CurrentDirectores (yes, that’s how it’s spelled) members would have
been interpreted incorrectly because STRING was defined incorrectly. The
length fields are USHORT, not DWORD. In the 64-bit version of the
structure, a padding member ensured that the structure was at least the
correct size. In the 32-bit version of the structure, this caused the
structure size to be inflated, so all but the first CurrentDirectores
element and any struct member that followed would appear at incorrect
offsets, and the overall struct size being read was larger than
appropriate.
This resolves crashpad_handler logging (usually) three errors while
handling a 64-bit process crash, such as:
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at
0x780f24de00000000, size 0x275 fully unreadable
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at
0x780f24fe00000000, size 0x275 fully unreadable
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at 0x0,
size 0x275 fully unreadable
Bug: crashpad:198
Test: end_to_end_test.py
Change-Id: I1655101de01cf46b4b50eda45a11f8d0f3bca8b3
Reviewed-on: https://chromium-review.googlesource.com/701736
Reviewed-by: Leonard Mosescu <mosescu@chromium.org>
2017-10-05 00:11:45 -04:00
|
|
|
out = CdbRun(cdb_path, dump_path, '?? @$peb->ProcessParameters')
|
|
|
|
out.Check(r' ImagePathName *: _UNICODE_STRING ".*\\crashy_program\.exe"',
|
|
|
|
'PEB->ProcessParameters.ImagePathName string captured')
|
|
|
|
out.Check(' DesktopInfo *: '
|
|
|
|
'_UNICODE_STRING "(?!--- memory read error at address ).*"',
|
|
|
|
'PEB->ProcessParameters.DesktopInfo string captured')
|
|
|
|
|
2015-10-09 13:39:39 -07:00
|
|
|
out = CdbRun(cdb_path, dump_path, '!teb')
|
|
|
|
out.Check(r'TEB at', 'found the TEB')
|
|
|
|
out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data')
|
|
|
|
out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue')
|
|
|
|
|
|
|
|
out = CdbRun(cdb_path, dump_path, '!gle')
|
|
|
|
out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the '
|
|
|
|
'file specified.', '!gle gets last error')
|
|
|
|
out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found} The '
|
|
|
|
'file %hs does not exist.', '!gle gets last ntstatus')
|
|
|
|
|
2015-11-30 12:29:21 -08:00
|
|
|
if False:
|
|
|
|
# TODO(scottmg): Re-enable when we grab ntdll!RtlCriticalSectionList.
|
|
|
|
out = CdbRun(cdb_path, dump_path, '!locks')
|
|
|
|
out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::'
|
|
|
|
r'g_test_critical_section', 'lock was captured')
|
|
|
|
if platform.win32_ver()[0] != '7':
|
|
|
|
# We can't allocate CRITICAL_SECTIONs with .DebugInfo on Win 7.
|
|
|
|
out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked')
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2015-10-21 10:43:42 -07:00
|
|
|
out = CdbRun(cdb_path, dump_path, '!handle')
|
|
|
|
out.Check(r'\d+ Handles', 'captured handles')
|
|
|
|
out.Check(r'Event\s+\d+', 'capture some event handles')
|
|
|
|
out.Check(r'File\s+\d+', 'capture some file handles')
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2016-02-11 17:19:30 -08:00
|
|
|
out = CdbRun(cdb_path, dump_path, 'lm')
|
|
|
|
out.Check(r'Unloaded modules:', 'captured some unloaded modules')
|
|
|
|
out.Check(r'lz32\.dll', 'found expected unloaded module lz32')
|
|
|
|
out.Check(r'wmerror\.dll', 'found expected unloaded module wmerror')
|
|
|
|
|
2015-10-21 16:07:03 -07:00
|
|
|
out = CdbRun(cdb_path, destroyed_dump_path, '.ecxr;!peb;k 2')
|
|
|
|
out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved')
|
|
|
|
out.Check(r'ntdll\.dll', 'ntdll present', re.IGNORECASE)
|
|
|
|
|
|
|
|
# Check that there is no stack trace in the self-destroyed process. Confirm
|
|
|
|
# that the top is where we expect it (that's based only on IP), but subsequent
|
|
|
|
# stack entries will not be available. This confirms that we have a mostly
|
|
|
|
# valid dump, but that the stack was omitted.
|
|
|
|
out.Check(r'self_destroying_program!crashpad::`anonymous namespace\'::'
|
|
|
|
r'FreeOwnStackAndBreak.*\nquit:',
|
|
|
|
'at correct location, no additional stack entries')
|
|
|
|
|
2016-11-03 11:35:19 -07:00
|
|
|
# Dump memory pointed to be EDI on the background suspended thread. We don't
|
|
|
|
# know the index of the thread because the system may have started other
|
|
|
|
# threads, so first do a run to extract the thread index that's suspended, and
|
|
|
|
# then another run to dump the data pointed to by EDI for that thread.
|
|
|
|
out = CdbRun(cdb_path, dump_path, '.ecxr;~')
|
|
|
|
match_obj = out.Find(r'(\d+)\s+Id: [0-9a-f.]+ Suspend: 1 Teb:')
|
|
|
|
if match_obj:
|
|
|
|
thread = match_obj.group(1)
|
|
|
|
out = CdbRun(cdb_path, dump_path, '.ecxr;~' + thread + 's;db /c14 edi')
|
2016-01-08 17:24:04 -08:00
|
|
|
out.Check(r'63 62 61 60 5f 5e 5d 5c-5b 5a 59 58 57 56 55 54 53 52 51 50',
|
|
|
|
'data pointed to by registers captured')
|
|
|
|
|
2016-01-14 12:50:22 -08:00
|
|
|
# Move up one stack frame after jumping to the exception, and examine memory.
|
|
|
|
out = CdbRun(cdb_path, dump_path,
|
|
|
|
'.ecxr; .f+; dd /c100 poi(offset_pointer)-20')
|
|
|
|
out.Check(r'80000078 00000079 8000007a 0000007b 8000007c 0000007d 8000007e '
|
|
|
|
r'0000007f 80000080 00000081 80000082 00000083 80000084 00000085 '
|
|
|
|
r'80000086 00000087 80000088 00000089 8000008a 0000008b 8000008c '
|
|
|
|
r'0000008d 8000008e 0000008f 80000090 00000091 80000092 00000093 '
|
|
|
|
r'80000094 00000095 80000096 00000097',
|
|
|
|
'data pointed to by stack captured')
|
|
|
|
|
2016-02-26 14:42:47 -08:00
|
|
|
# Attempt to retrieve the value of g_extra_memory_pointer (by name), and then
|
|
|
|
# examine the memory at which it points. Both should have been saved.
|
|
|
|
out = CdbRun(cdb_path, dump_path,
|
|
|
|
'dd poi(crashy_program!crashpad::g_extra_memory_pointer)+0x1f30 '
|
|
|
|
'L8')
|
|
|
|
out.Check(r'0000655e 0000656b 00006578 00006585',
|
|
|
|
'extra memory range captured')
|
|
|
|
|
2016-02-29 13:28:05 -08:00
|
|
|
out = CdbRun(cdb_path, dump_path, '.dumpdebug')
|
|
|
|
out.Check(r'type \?\?\? \(333333\), size 00001000',
|
|
|
|
'first user stream')
|
|
|
|
out.Check(r'type \?\?\? \(222222\), size 00000080',
|
|
|
|
'second user stream')
|
|
|
|
|
2015-10-31 11:45:39 -07:00
|
|
|
if z7_dump_path:
|
|
|
|
out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm')
|
|
|
|
out.Check('This dump file has an exception of interest stored in it',
|
|
|
|
'captured exception in z7 module')
|
2015-11-02 10:28:01 -08:00
|
|
|
# Older versions of cdb display relative to exports for /Z7 modules, newer
|
|
|
|
# ones just display the offset.
|
|
|
|
out.Check(r'z7_test(!CrashMe\+0xe|\+0x100e):',
|
|
|
|
'exception in z7 at correct location')
|
win: Fix process_structs.h definition of RTL_USER_PROCESS_PARAMETERS
In the 64-bit version of the structure, padding is needed between
ShowWindowFlags and WindowTitle.
The CurrentDirectores (yes, that’s how it’s spelled) members would have
been interpreted incorrectly because STRING was defined incorrectly. The
length fields are USHORT, not DWORD. In the 64-bit version of the
structure, a padding member ensured that the structure was at least the
correct size. In the 32-bit version of the structure, this caused the
structure size to be inflated, so all but the first CurrentDirectores
element and any struct member that followed would appear at incorrect
offsets, and the overall struct size being read was larger than
appropriate.
This resolves crashpad_handler logging (usually) three errors while
handling a 64-bit process crash, such as:
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at
0x780f24de00000000, size 0x275 fully unreadable
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at
0x780f24fe00000000, size 0x275 fully unreadable
[pid:tid:yyyymmdd,hhmmss.mmm:ERROR process_info.cc:632] range at 0x0,
size 0x275 fully unreadable
Bug: crashpad:198
Test: end_to_end_test.py
Change-Id: I1655101de01cf46b4b50eda45a11f8d0f3bca8b3
Reviewed-on: https://chromium-review.googlesource.com/701736
Reviewed-by: Leonard Mosescu <mosescu@chromium.org>
2017-10-05 00:11:45 -04:00
|
|
|
out.Check(r'z7_test C \(codeview symbols\) z7_test\.dll',
|
2015-10-31 11:45:39 -07:00
|
|
|
'expected non-pdb symbol format')
|
|
|
|
|
2016-04-22 10:03:59 -07:00
|
|
|
out = CdbRun(cdb_path, other_program_path, '.ecxr;k;~')
|
|
|
|
out.Check('Unknown exception - code deadbea7',
|
|
|
|
'other program dump exception code')
|
|
|
|
out.Check('!Sleep', 'other program reasonable location')
|
2017-10-04 11:40:23 -04:00
|
|
|
out.Check("hanging_program!`anonymous namespace'::Thread1",
|
|
|
|
'other program dump right thread')
|
2016-11-03 11:35:19 -07:00
|
|
|
count = 0
|
|
|
|
while True:
|
|
|
|
match_obj = out.Find(r'Id.*Suspend: (\d+) ')
|
|
|
|
if match_obj:
|
|
|
|
if match_obj.group(1) != '0':
|
|
|
|
out.Check(r'FAILED', 'all suspend counts should be 0')
|
|
|
|
else:
|
|
|
|
count += 1
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
assert count > 2
|
2016-04-22 10:03:59 -07:00
|
|
|
|
|
|
|
out = CdbRun(cdb_path, other_program_no_exception_path, '.ecxr;k')
|
|
|
|
out.Check('Unknown exception - code 0cca11ed',
|
|
|
|
'other program with no exception given')
|
|
|
|
out.Check('!RaiseException', 'other program in RaiseException()')
|
|
|
|
|
2016-11-17 14:00:21 -08:00
|
|
|
out = CdbRun(cdb_path, sigabrt_main_path, '.ecxr')
|
|
|
|
out.Check('code 40000015', 'got sigabrt signal')
|
|
|
|
out.Check('::HandleAbortSignal', ' stack in expected location')
|
|
|
|
|
|
|
|
out = CdbRun(cdb_path, sigabrt_background_path, '.ecxr')
|
|
|
|
out.Check('code 40000015', 'got sigabrt signal from background thread')
|
|
|
|
|
2015-10-31 11:45:39 -07:00
|
|
|
|
2015-10-08 21:09:40 -07:00
|
|
|
def main(args):
|
2015-10-09 13:39:39 -07:00
|
|
|
try:
|
|
|
|
if len(args) != 1:
|
2017-11-29 13:26:55 -05:00
|
|
|
print('must supply binary dir', file=sys.stderr)
|
2015-10-09 13:39:39 -07:00
|
|
|
return 1
|
|
|
|
|
|
|
|
cdb_path = GetCdbPath()
|
|
|
|
if not cdb_path:
|
2017-11-29 13:26:55 -05:00
|
|
|
print('could not find cdb', file=sys.stderr)
|
2015-10-09 13:39:39 -07:00
|
|
|
return 1
|
|
|
|
|
2015-10-09 16:28:19 -07:00
|
|
|
# Make sure we can download Windows symbols.
|
|
|
|
if not os.environ.get('_NT_SYMBOL_PATH'):
|
|
|
|
symbol_dir = MakeTempDir()
|
2015-10-21 10:43:42 -07:00
|
|
|
protocol = 'https' if platform.win32_ver()[0] != 'XP' else 'http'
|
2015-10-09 16:28:19 -07:00
|
|
|
os.environ['_NT_SYMBOL_PATH'] = (
|
2015-10-21 10:43:42 -07:00
|
|
|
'SRV*' + symbol_dir + '*' +
|
|
|
|
protocol + '://msdl.microsoft.com/download/symbols')
|
2015-10-09 16:28:19 -07:00
|
|
|
|
2015-10-09 13:39:39 -07:00
|
|
|
pipe_name = r'\\.\pipe\end-to-end_%s_%s' % (
|
|
|
|
os.getpid(), str(random.getrandbits(64)))
|
|
|
|
|
2015-10-21 16:07:03 -07:00
|
|
|
crashy_dump_path = GetDumpFromCrashyProgram(args[0], pipe_name)
|
|
|
|
if not crashy_dump_path:
|
|
|
|
return 1
|
|
|
|
|
2015-11-02 13:59:36 -05:00
|
|
|
start_handler_dump_path = GetDumpFromCrashyProgram(args[0], None)
|
|
|
|
if not start_handler_dump_path:
|
|
|
|
return 1
|
|
|
|
|
2015-10-21 16:07:03 -07:00
|
|
|
destroyed_dump_path = GetDumpFromSelfDestroyingProgram(args[0], pipe_name)
|
|
|
|
if not destroyed_dump_path:
|
2015-10-09 13:39:39 -07:00
|
|
|
return 1
|
|
|
|
|
2015-10-31 11:45:39 -07:00
|
|
|
z7_dump_path = None
|
win: Dynamically disable WoW64 tests absent explicit 32-bit build output
Rather than having the 64-bit build assume that it lives in
out\{Debug,Release}_x64 and that it can find 32-bit build output in
out\{Debug,Release}, require the location of 32-bit build output to be
provided explicitly via the CRASHPAD_TEST_32_BIT_OUTPUT environment
variable. If this variable is not set, 64-bit tests that require 32-bit
test build output will dynamically disable themselves at runtime.
In order for this to work, a new DISABLED_TEST() macro is added to
support dynamically disabled tests. gtest does not have its own
first-class support for this
(https://groups.google.com/d/topic/googletestframework/Nwh3u7YFuN4,
https://github.com/google/googletest/issues/490) so this local solution
is used instead.
For tests via Crashpad’s own build\run_tests.py, which is how Crashpad’s
own buildbots and trybots invoke tests, CRASHPAD_TEST_32_BIT_OUTPUT is
set to a locaton compatible with the paths expected for the GYP-based
build. No test coverage is lost on Crashpad’s own buildbots and trybots.
For Crashpad tests in Chromium’s buildbots and trybots, this environment
variable will not be set, causing these tests to be dynamically
disabled.
Bug: crashpad:203, chromium:743139, chromium:777924
Change-Id: I3c0de2bf4f835e13ed5a4adda5760d6fed508126
Reviewed-on: https://chromium-review.googlesource.com/739795
Commit-Queue: Mark Mentovai <mark@chromium.org>
Reviewed-by: Scott Graham <scottmg@chromium.org>
2017-10-26 13:48:01 -04:00
|
|
|
if not args[0].endswith('_x64'):
|
2015-10-31 11:45:39 -07:00
|
|
|
z7_dump_path = GetDumpFromZ7Program(args[0], pipe_name)
|
|
|
|
if not z7_dump_path:
|
|
|
|
return 1
|
|
|
|
|
2016-04-22 10:03:59 -07:00
|
|
|
other_program_path = GetDumpFromOtherProgram(args[0], pipe_name)
|
|
|
|
if not other_program_path:
|
|
|
|
return 1
|
|
|
|
|
|
|
|
other_program_no_exception_path = GetDumpFromOtherProgram(
|
|
|
|
args[0], pipe_name, 'noexception')
|
|
|
|
if not other_program_no_exception_path:
|
|
|
|
return 1
|
|
|
|
|
2016-11-17 14:00:21 -08:00
|
|
|
sigabrt_main_path = GetDumpFromSignal(args[0], pipe_name, 'main')
|
|
|
|
if not sigabrt_main_path:
|
|
|
|
return 1
|
|
|
|
|
|
|
|
sigabrt_background_path = GetDumpFromSignal(
|
|
|
|
args[0], pipe_name, 'background')
|
|
|
|
if not sigabrt_background_path:
|
|
|
|
return 1
|
|
|
|
|
2015-11-02 13:59:36 -05:00
|
|
|
RunTests(cdb_path,
|
|
|
|
crashy_dump_path,
|
|
|
|
start_handler_dump_path,
|
|
|
|
destroyed_dump_path,
|
|
|
|
z7_dump_path,
|
2016-04-22 10:03:59 -07:00
|
|
|
other_program_path,
|
|
|
|
other_program_no_exception_path,
|
2016-11-17 14:00:21 -08:00
|
|
|
sigabrt_main_path,
|
|
|
|
sigabrt_background_path,
|
2015-10-31 11:45:39 -07:00
|
|
|
pipe_name)
|
2015-10-09 13:39:39 -07:00
|
|
|
|
2016-10-21 13:08:18 -07:00
|
|
|
return 1 if g_had_failures else 0
|
2015-10-09 13:39:39 -07:00
|
|
|
finally:
|
|
|
|
CleanUpTempDirs()
|
2015-10-08 21:09:40 -07:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
sys.exit(main(sys.argv[1:]))
|