win: Support dumping another process by causing it to crash

Adds a new client API which allows causing an exception in another
process. This is accomplished by injecting a thread that calls
RaiseException(). A special exception code is used that indicates to the
handler that the exception arguments contain a thread id and exception
code, which are in turn used to fabricate an exception record. This is
so that the API can allow the client to "blame" a particular thread in
the target process.

The target process must also be a registered Crashpad client, as the
normal exception mechanism is used to handle the exception.

The injection of a thread is used instead of DebugBreakProcess() which
does not cause the UnhandledExceptionFilter() to be executed.
NtCreateThreadEx() is used in lieu of CreateRemoteThread() as it allows
passing of a flag which avoids calling DllMain()s. This is necessary to
allow thread creation to succeed even when the target process is
deadlocked on the loader lock.

BUG=crashpad:103

Change-Id: I797007bd2b1e3416afe3f37a6566c0cdb259b106
Reviewed-on: https://chromium-review.googlesource.com/339263
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Scott Graham 2016-04-22 10:03:59 -07:00
parent dbfcb5d032
commit 6a6a0c27ed
12 changed files with 782 additions and 44 deletions

View File

@ -19,6 +19,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <stdint.h>
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/macros.h" #include "base/macros.h"
#include "build/build_config.h" #include "build/build_config.h"
@ -152,6 +154,41 @@ class CrashpadClient {
//! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would generally //! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would generally
//! passed to an unhandled exception filter. //! passed to an unhandled exception filter.
static void DumpAndCrash(EXCEPTION_POINTERS* exception_pointers); static void DumpAndCrash(EXCEPTION_POINTERS* exception_pointers);
//! \brief Requests that the handler capture a dump of a different process.
//!
//! The target process must be an already-registered Crashpad client. An
//! exception will be triggered in the target process, and the regular dump
//! mechanism used. This function will block until the exception in the target
//! process has been handled by the Crashpad handler.
//!
//! This function is unavailable when running on Windows XP and will return
//! `false`.
//!
//! \param[in] process A `HANDLE` identifying the process to be dumped.
//! \param[in] blame_thread If non-null, a `HANDLE` valid in the caller's
//! process, referring to a thread in the target process. If this is
//! supplied, instead of the exception referring to the location where the
//! exception was injected, an exception record will be fabricated that
//! refers to the current location of the given thread.
//! \param[in] exception_code If \a blame_thread is non-null, this will be
//! used as the exception code in the exception record.
//!
//! \return `true` if the exception was triggered successfully.
bool DumpAndCrashTargetProcess(HANDLE process,
HANDLE blame_thread,
DWORD exception_code) const;
enum : uint32_t {
//! \brief The exception code (roughly "Client called") used when
//! DumpAndCrashTargetProcess() triggers an exception in a target
//! process.
//!
//! \note This value does not have any bits of the top nibble set, to avoid
//! confusion with real exception codes which tend to have those bits
//! set.
kTriggeredExceptionCode = 0xcca11ed,
};
#endif #endif
//! \brief Configures the process to direct its crashes to a Crashpad handler. //! \brief Configures the process to direct its crashes to a Crashpad handler.

View File

@ -27,12 +27,17 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "util/file/file_io.h" #include "util/file/file_io.h"
#include "util/win/address_types.h"
#include "util/win/command_line.h" #include "util/win/command_line.h"
#include "util/win/critical_section_with_debug_info.h" #include "util/win/critical_section_with_debug_info.h"
#include "util/win/get_function.h" #include "util/win/get_function.h"
#include "util/win/handle.h" #include "util/win/handle.h"
#include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h"
#include "util/win/process_info.h"
#include "util/win/registration_protocol_win.h" #include "util/win/registration_protocol_win.h"
#include "util/win/scoped_handle.h" #include "util/win/scoped_handle.h"
#include "util/win/scoped_process_suspend.h"
namespace { namespace {
@ -167,6 +172,19 @@ void AddHandleToListIfValidAndInheritable(std::vector<HANDLE>* handle_list,
} }
} }
void AddUint32(std::vector<unsigned char>* data_vector, uint32_t data) {
data_vector->push_back(static_cast<unsigned char>(data & 0xff));
data_vector->push_back(static_cast<unsigned char>((data & 0xff00) >> 8));
data_vector->push_back(static_cast<unsigned char>((data & 0xff0000) >> 16));
data_vector->push_back(static_cast<unsigned char>((data & 0xff000000) >> 24));
}
void AddUint64(std::vector<unsigned char>* data_vector, uint64_t data) {
AddUint32(data_vector, static_cast<uint32_t>(data & 0xffffffffULL));
AddUint32(data_vector,
static_cast<uint32_t>((data & 0xffffffff00000000ULL) >> 32));
}
} // namespace } // namespace
namespace crashpad { namespace crashpad {
@ -469,4 +487,247 @@ void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) {
UnhandledExceptionHandler(exception_pointers); UnhandledExceptionHandler(exception_pointers);
} }
bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process,
HANDLE blame_thread,
DWORD exception_code) const {
// Confirm we're on Vista or later.
const DWORD version = GetVersion();
const DWORD major_version = LOBYTE(LOWORD(version));
if (major_version < 6) {
LOG(ERROR) << "unavailable before Vista";
return false;
}
// Confirm that our bitness is the same as the process we're crashing.
ProcessInfo process_info;
if (!process_info.Initialize(process)) {
LOG(ERROR) << "ProcessInfo::Initialize";
return false;
}
#if defined(ARCH_CPU_64_BITS)
if (!process_info.Is64Bit()) {
LOG(ERROR) << "DumpAndCrashTargetProcess currently not supported x64->x86";
return false;
}
#endif // ARCH_CPU_64_BITS
ScopedProcessSuspend suspend(process);
// If no thread handle was provided, or the thread has already exited, we pass
// 0 to the handler, which indicates no fake exception record to be created.
DWORD thread_id = 0;
if (blame_thread) {
// Now that we've suspended the process, if our thread hasn't exited, we
// know we're relatively safe to pass the thread id through.
if (WaitForSingleObject(blame_thread, 0) == WAIT_TIMEOUT) {
static const auto get_thread_id =
GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetThreadId);
thread_id = get_thread_id(blame_thread);
}
}
const size_t kInjectBufferSize = 4 * 1024;
WinVMAddress inject_memory =
reinterpret_cast<WinVMAddress>(VirtualAllocEx(process,
nullptr,
kInjectBufferSize,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE));
if (!inject_memory) {
PLOG(ERROR) << "VirtualAllocEx";
return false;
}
// Because we're the same bitness as our target, we can rely kernel32 being
// loaded at the same address in our process as the target, and just look up
// its address here.
WinVMAddress raise_exception_address =
reinterpret_cast<WinVMAddress>(&RaiseException);
WinVMAddress code_entry_point = 0;
std::vector<unsigned char> data_to_write;
if (process_info.Is64Bit()) {
// Data written is first, the data for the 4th argument (lpArguments) to
// RaiseException(). A two element array:
//
// DWORD64: thread_id
// DWORD64: exception_code
//
// Following that, code which sets the arguments to RaiseException() and
// then calls it:
//
// mov r9, <data_array_address>
// mov r8d, 2 ; nNumberOfArguments
// mov edx, 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE
// mov ecx, 0xcca11ed ; dwExceptionCode, interpreted specially by the
// ; handler.
// jmp <address_of_RaiseException>
//
// Note that the first three arguments to RaiseException() are DWORDs even
// on x64, so only the 4th argument (a pointer) is a full-width register.
//
// We also don't need to set up a stack or use call, since the only
// registers modified are volatile ones, and we can just jmp straight to
// RaiseException().
// The data array.
AddUint64(&data_to_write, thread_id);
AddUint64(&data_to_write, exception_code);
// The thread entry point.
code_entry_point = inject_memory + data_to_write.size();
// r9 = pointer to data.
data_to_write.push_back(0x49);
data_to_write.push_back(0xb9);
AddUint64(&data_to_write, inject_memory);
// r8d = 2 for nNumberOfArguments.
data_to_write.push_back(0x41);
data_to_write.push_back(0xb8);
AddUint32(&data_to_write, 2);
// edx = 1 for dwExceptionFlags.
data_to_write.push_back(0xba);
AddUint32(&data_to_write, 1);
// ecx = kTriggeredExceptionCode for dwExceptionCode.
data_to_write.push_back(0xb9);
AddUint32(&data_to_write, kTriggeredExceptionCode);
// jmp to RaiseException() via rax.
data_to_write.push_back(0x48); // mov rax, imm.
data_to_write.push_back(0xb8);
AddUint64(&data_to_write, raise_exception_address);
data_to_write.push_back(0xff); // jmp rax.
data_to_write.push_back(0xe0);
} else {
// Data written is first, the data for the 4th argument (lpArguments) to
// RaiseException(). A two element array:
//
// DWORD: thread_id
// DWORD: exception_code
//
// Following that, code which pushes our arguments to RaiseException() and
// then calls it:
//
// push <data_array_address>
// push 2 ; nNumberOfArguments
// push 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE
// push 0xcca11ed ; dwExceptionCode, interpreted specially by the handler.
// call <address_of_RaiseException>
// ud2 ; Generate invalid opcode to make sure we still crash if we return
// ; for some reason.
//
// No need to clean up the stack, as RaiseException() is __stdcall.
// The data array.
AddUint32(&data_to_write, thread_id);
AddUint32(&data_to_write, exception_code);
// The thread entry point.
code_entry_point = inject_memory + data_to_write.size();
// Push data address.
data_to_write.push_back(0x68);
AddUint32(&data_to_write, static_cast<uint32_t>(inject_memory));
// Push 2 for nNumberOfArguments.
data_to_write.push_back(0x6a);
data_to_write.push_back(2);
// Push 1 for dwExceptionCode.
data_to_write.push_back(0x6a);
data_to_write.push_back(1);
// Push dwExceptionFlags.
data_to_write.push_back(0x68);
AddUint32(&data_to_write, kTriggeredExceptionCode);
// Relative call to RaiseException().
int64_t relative_address_to_raise_exception =
raise_exception_address - (inject_memory + data_to_write.size() + 5);
data_to_write.push_back(0xe8);
AddUint32(&data_to_write,
static_cast<uint32_t>(relative_address_to_raise_exception));
// ud2.
data_to_write.push_back(0x0f);
data_to_write.push_back(0x0b);
}
DCHECK_LT(data_to_write.size(), kInjectBufferSize);
SIZE_T bytes_written;
if (!WriteProcessMemory(process,
reinterpret_cast<void*>(inject_memory),
data_to_write.data(),
data_to_write.size(),
&bytes_written)) {
PLOG(ERROR) << "WriteProcessMemory";
return false;
}
if (bytes_written != data_to_write.size()) {
LOG(ERROR) << "WriteProcessMemory unexpected number of bytes";
return false;
}
if (!FlushInstructionCache(
process, reinterpret_cast<void*>(inject_memory), bytes_written)) {
PLOG(ERROR) << "FlushInstructionCache";
return false;
}
DWORD old_protect;
if (!VirtualProtectEx(process,
reinterpret_cast<void*>(inject_memory),
kInjectBufferSize,
PAGE_EXECUTE_READ,
&old_protect)) {
PLOG(ERROR) << "VirtualProtectEx";
return false;
}
// Cause an exception in the target process by creating a thread which calls
// RaiseException with our arguments above. Note that we cannot get away with
// using DebugBreakProcess() (nothing happens unless a debugger is attached)
// and we cannot get away with CreateRemoteThread() because it doesn't work if
// the target is hung waiting for the loader lock. We use NtCreateThreadEx()
// with the SKIP_THREAD_ATTACH flag, which skips various notifications,
// letting this cause an exception, even when the target is stuck in the
// loader lock.
HANDLE injected_thread;
const size_t kStackSize = 0x4000; // This is what DebugBreakProcess() uses.
NTSTATUS status = NtCreateThreadEx(&injected_thread,
STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL,
nullptr,
process,
reinterpret_cast<void*>(code_entry_point),
nullptr,
THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH,
0,
kStackSize,
0,
nullptr);
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status) << "NtCreateThreadEx";
return false;
}
bool result = true;
if (WaitForSingleObject(injected_thread, 60 * 1000) != WAIT_OBJECT_0) {
PLOG(ERROR) << "WaitForSingleObject";
result = false;
}
status = NtClose(injected_thread);
if (!NT_SUCCESS(status)) {
NTSTATUS_LOG(ERROR, status) << "NtClose";
result = false;
}
return result;
}
} // namespace crashpad } // namespace crashpad

View File

@ -110,6 +110,40 @@
'win/crashy_test_program.cc', 'win/crashy_test_program.cc',
], ],
}, },
{
'target_name': 'crash_other_program',
'type': 'executable',
'dependencies': [
'../client/client.gyp:crashpad_client',
'../test/test.gyp:crashpad_test',
'../third_party/mini_chromium/mini_chromium.gyp:base',
'../util/util.gyp:crashpad_util',
],
'sources': [
'win/crash_other_program.cc',
],
},
{
'target_name': 'hanging_program',
'type': 'executable',
'dependencies': [
'../client/client.gyp:crashpad_client',
'../third_party/mini_chromium/mini_chromium.gyp:base',
],
'sources': [
'win/hanging_program.cc',
],
},
{
'target_name': 'loader_lock_dll',
'type': 'loadable_module',
'sources': [
'win/loader_lock_dll.cc',
],
'msvs_settings': {
'NoImportLibrary': 'true',
},
},
{ {
'target_name': 'self_destroying_program', 'target_name': 'self_destroying_program',
'type': 'executable', 'type': 'executable',

View File

@ -0,0 +1,120 @@
// Copyright 2016 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.
#include <stdlib.h>
#include <windows.h>
#include <tlhelp32.h>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "client/crashpad_client.h"
#include "test/paths.h"
#include "test/win/child_launcher.h"
#include "util/file/file_io.h"
#include "util/win/scoped_handle.h"
#include "util/win/xp_compat.h"
namespace crashpad {
namespace test {
namespace {
bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) {
DWORD target_pid = GetProcessId(process);
HANDLE thread_snap_raw = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (thread_snap_raw == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "CreateToolhelp32Snapshot";
return false;
}
ScopedFileHANDLE thread_snap(thread_snap_raw);
THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);
if (!Thread32First(thread_snap.get(), &te32)) {
LOG(ERROR) << "Thread32First";
return false;
}
int thread_count = 0;
do {
if (te32.th32OwnerProcessID == target_pid) {
thread_count++;
if (thread_count == 2) {
// Nominate this lucky thread as our blamee, and dump it. This will be
// "Thread1" in the child.
ScopedKernelHANDLE thread(
OpenThread(kXPThreadAllAccess, false, te32.th32ThreadID));
if (!client.DumpAndCrashTargetProcess(
process, thread.get(), 0xdeadbea7)) {
LOG(ERROR) << "DumpAndCrashTargetProcess failed";
return false;
}
return true;
}
}
} while (Thread32Next(thread_snap.get(), &te32));
return false;
}
int CrashOtherProgram(int argc, wchar_t* argv[]) {
CrashpadClient client;
if (argc == 2 || argc == 3) {
if (!client.SetHandlerIPCPipe(argv[1])) {
LOG(ERROR) << "SetHandler";
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "Usage: %ls <server_pipe_name> [noexception]\n", argv[0]);
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
// Launch another process that hangs.
base::FilePath test_executable = Paths::Executable();
std::wstring child_test_executable =
test_executable.DirName().Append(L"hanging_program.exe").value();
ChildLauncher child(child_test_executable, argv[1]);
child.Start();
// Wait until it's ready.
char c;
if (!LoggingReadFile(child.stdout_read_handle(), &c, sizeof(c)) || c != ' ') {
LOG(ERROR) << "failed child communication";
return EXIT_FAILURE;
}
if (argc == 3 && wcscmp(argv[2], L"noexception") == 0) {
client.DumpAndCrashTargetProcess(child.process_handle(), 0, 0);
return EXIT_SUCCESS;
} else {
if (CrashAndDumpTarget(client, child.process_handle()))
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}
} // namespace
} // namespace test
} // namespace crashpad
int wmain(int argc, wchar_t* argv[]) {
return crashpad::test::CrashOtherProgram(argc, argv);
}

View File

@ -0,0 +1,86 @@
// Copyright 2016 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.
#include <stdio.h>
#include <windows.h>
#include "base/debug/alias.h"
#include "base/logging.h"
#include "base/macros.h"
#include "client/crashpad_client.h"
#include "client/crashpad_info.h"
DWORD WINAPI Thread1(LPVOID dummy) {
Sleep(INFINITE);
return 0;
}
DWORD WINAPI Thread2(LPVOID dummy) {
Sleep(INFINITE);
return 0;
}
DWORD WINAPI Thread3(LPVOID dummy) {
HMODULE dll = LoadLibrary(L"loader_lock_dll.dll");
if (!dll)
PLOG(ERROR) << "LoadLibrary";
// This call is not expected to return.
if (!FreeLibrary(dll))
PLOG(ERROR) << "FreeLibrary";
return 0;
}
int wmain(int argc, wchar_t* argv[]) {
crashpad::CrashpadClient client;
if (argc == 2) {
if (!client.SetHandlerIPCPipe(argv[1])) {
LOG(ERROR) << "SetHandler";
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "Usage: %ls <server_pipe_name>\n", argv[0]);
return EXIT_FAILURE;
}
if (!client.UseHandler()) {
LOG(ERROR) << "UseHandler";
return EXIT_FAILURE;
}
// Make sure this module has a CrashpadInfo structure.
crashpad::CrashpadInfo* crashpad_info =
crashpad::CrashpadInfo::GetCrashpadInfo();
base::debug::Alias(crashpad_info);
HANDLE threads[3];
threads[0] = CreateThread(nullptr, 0, Thread1, nullptr, 0, nullptr);
threads[1] = CreateThread(nullptr, 0, Thread2, nullptr, 0, nullptr);
threads[2] = CreateThread(nullptr, 0, Thread3, nullptr, 0, nullptr);
// Our whole process is going to hang when the loaded DLL hangs in its
// DllMain(), so we can't signal to our parent that we're "ready". So, use a
// hokey delay of 1s after we spawn the threads, and hope that we make it to
// the FreeLibrary call by then.
Sleep(1000);
fprintf(stdout, " ");
fflush(stdout);
WaitForMultipleObjects(ARRAYSIZE(threads), threads, true, INFINITE);
return 0;
}

View File

@ -0,0 +1,28 @@
// Copyright 2016 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.
#include <windows.h>
// This program intentionally blocks in DllMain which is executed with the
// loader lock locked. This allows us to test that
// CrashpadClient::DumpAndCrashTargetProcess() can still dump the target in this
// case.
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) {
switch (reason) {
case DLL_PROCESS_DETACH:
case DLL_THREAD_DETACH:
Sleep(INFINITE);
}
return TRUE;
}

View File

@ -79,11 +79,12 @@ def GetCdbPath():
return None return None
def GetDumpFromProgram(out_dir, pipe_name, executable_name): def GetDumpFromProgram(out_dir, pipe_name, executable_name, *args):
"""Initialize a crash database, and run |executable_name| connecting to a """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 crash handler. If pipe_name is set, crashpad_handler will be started first. If
pipe_name is empty, the executable is responsible for starting pipe_name is empty, the executable is responsible for starting
crashpad_handler. Returns the minidump generated by crashpad_handler for crashpad_handler. *args will be passed after other arguments to
executable_name. Returns the minidump generated by crashpad_handler for
further testing. further testing.
""" """
test_database = MakeTempDir() test_database = MakeTempDir()
@ -111,11 +112,12 @@ def GetDumpFromProgram(out_dir, pipe_name, executable_name):
printed = True printed = True
time.sleep(0.1) time.sleep(0.1)
subprocess.call([os.path.join(out_dir, executable_name), pipe_name]) subprocess.call([os.path.join(out_dir, executable_name), pipe_name] +
list(args))
else: else:
subprocess.call([os.path.join(out_dir, executable_name), subprocess.call([os.path.join(out_dir, executable_name),
os.path.join(out_dir, 'crashpad_handler.exe'), os.path.join(out_dir, 'crashpad_handler.exe'),
test_database]) test_database] + list(args))
out = subprocess.check_output([ out = subprocess.check_output([
os.path.join(out_dir, 'crashpad_database_util.exe'), os.path.join(out_dir, 'crashpad_database_util.exe'),
@ -135,6 +137,11 @@ def GetDumpFromCrashyProgram(out_dir, pipe_name):
return GetDumpFromProgram(out_dir, pipe_name, 'crashy_program.exe') return GetDumpFromProgram(out_dir, pipe_name, 'crashy_program.exe')
def GetDumpFromOtherProgram(out_dir, pipe_name, *args):
return GetDumpFromProgram(out_dir, pipe_name, 'crash_other_program.exe',
*args)
def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name): def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name):
return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe') return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe')
@ -182,6 +189,8 @@ def RunTests(cdb_path,
start_handler_dump_path, start_handler_dump_path,
destroyed_dump_path, destroyed_dump_path,
z7_dump_path, z7_dump_path,
other_program_path,
other_program_no_exception_path,
pipe_name): pipe_name):
"""Runs various tests in sequence. Runs a new cdb instance on the dump for """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 each block of tests to reduce the chances that output from one command is
@ -311,6 +320,18 @@ def RunTests(cdb_path,
out.Check(r'z7_test C \(codeview symbols\) z7_test.dll', out.Check(r'z7_test C \(codeview symbols\) z7_test.dll',
'expected non-pdb symbol format') 'expected non-pdb symbol format')
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')
out.Check('hanging_program!Thread1', 'other program dump right thread')
out.Check('\. 1 Id', 'other program exception on correct thread')
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()')
def main(args): def main(args):
try: try:
@ -352,11 +373,22 @@ def main(args):
if not z7_dump_path: if not z7_dump_path:
return 1 return 1
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
RunTests(cdb_path, RunTests(cdb_path,
crashy_dump_path, crashy_dump_path,
start_handler_dump_path, start_handler_dump_path,
destroyed_dump_path, destroyed_dump_path,
z7_dump_path, z7_dump_path,
other_program_path,
other_program_no_exception_path,
pipe_name) pipe_name)
return 0 return 0

View File

@ -14,6 +14,7 @@
#include "snapshot/win/exception_snapshot_win.h" #include "snapshot/win/exception_snapshot_win.h"
#include "client/crashpad_client.h"
#include "snapshot/capture_memory.h" #include "snapshot/capture_memory.h"
#include "snapshot/memory_snapshot.h" #include "snapshot/memory_snapshot.h"
#include "snapshot/win/cpu_context_win.h" #include "snapshot/win/cpu_context_win.h"
@ -25,6 +26,34 @@
namespace crashpad { namespace crashpad {
namespace internal { namespace internal {
namespace {
#if defined(ARCH_CPU_32_BITS)
using Context32 = CONTEXT;
#elif defined(ARCH_CPU_64_BITS)
using Context32 = WOW64_CONTEXT;
#endif
#if defined(ARCH_CPU_64_BITS)
void NativeContextToCPUContext64(const CONTEXT& context_record,
CPUContext* context,
CPUContextUnion* context_union) {
context->architecture = kCPUArchitectureX86_64;
context->x86_64 = &context_union->x86_64;
InitializeX64Context(context_record, context->x86_64);
}
#endif
void NativeContextToCPUContext32(const Context32& context_record,
CPUContext* context,
CPUContextUnion* context_union) {
context->architecture = kCPUArchitectureX86;
context->x86 = &context_union->x86;
InitializeX86Context(context_record, context->x86);
}
} // namespace
ExceptionSnapshotWin::ExceptionSnapshotWin() ExceptionSnapshotWin::ExceptionSnapshotWin()
: ExceptionSnapshot(), : ExceptionSnapshot(),
context_union_(), context_union_(),
@ -40,9 +69,11 @@ ExceptionSnapshotWin::ExceptionSnapshotWin()
ExceptionSnapshotWin::~ExceptionSnapshotWin() { ExceptionSnapshotWin::~ExceptionSnapshotWin() {
} }
bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader, bool ExceptionSnapshotWin::Initialize(
DWORD thread_id, ProcessReaderWin* process_reader,
WinVMAddress exception_pointers_address) { DWORD thread_id,
WinVMAddress exception_pointers_address,
const PointerVector<internal::ThreadSnapshotWin>& threads) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_); INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
const ProcessReaderWin::Thread* thread = nullptr; const ProcessReaderWin::Thread* thread = nullptr;
@ -62,32 +93,28 @@ bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
#if defined(ARCH_CPU_32_BITS) #if defined(ARCH_CPU_32_BITS)
const bool is_64_bit = false; const bool is_64_bit = false;
using Context32 = CONTEXT;
#elif defined(ARCH_CPU_64_BITS) #elif defined(ARCH_CPU_64_BITS)
const bool is_64_bit = process_reader->Is64Bit(); const bool is_64_bit = process_reader->Is64Bit();
using Context32 = WOW64_CONTEXT;
if (is_64_bit) { if (is_64_bit) {
CONTEXT context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD64, if (!InitializeFromExceptionPointers<EXCEPTION_RECORD64,
process_types::EXCEPTION_POINTERS64>( process_types::EXCEPTION_POINTERS64>(
*process_reader, exception_pointers_address, &context_record)) { *process_reader,
exception_pointers_address,
threads,
&NativeContextToCPUContext64)) {
return false; return false;
} }
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
InitializeX64Context(context_record, context_.x86_64);
} }
#endif #endif
if (!is_64_bit) { if (!is_64_bit) {
Context32 context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32, if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32,
process_types::EXCEPTION_POINTERS32>( process_types::EXCEPTION_POINTERS32>(
*process_reader, exception_pointers_address, &context_record)) { *process_reader,
exception_pointers_address,
threads,
&NativeContextToCPUContext32)) {
return false; return false;
} }
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(context_record, context_.x86);
} }
CaptureMemoryDelegateWin capture_memory_delegate( CaptureMemoryDelegateWin capture_memory_delegate(
@ -143,7 +170,10 @@ template <class ExceptionRecordType,
bool ExceptionSnapshotWin::InitializeFromExceptionPointers( bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
const ProcessReaderWin& process_reader, const ProcessReaderWin& process_reader,
WinVMAddress exception_pointers_address, WinVMAddress exception_pointers_address,
ContextType* context_record) { const PointerVector<internal::ThreadSnapshotWin>& threads,
void (*native_to_cpu_context)(const ContextType& context_record,
CPUContext* context,
CPUContextUnion* context_union)) {
ExceptionPointersType exception_pointers; ExceptionPointersType exception_pointers;
if (!process_reader.ReadMemory(exception_pointers_address, if (!process_reader.ReadMemory(exception_pointers_address,
sizeof(exception_pointers), sizeof(exception_pointers),
@ -164,22 +194,60 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
LOG(ERROR) << "ExceptionRecord"; LOG(ERROR) << "ExceptionRecord";
return false; return false;
} }
exception_code_ = first_record.ExceptionCode;
exception_flags_ = first_record.ExceptionFlags;
exception_address_ = first_record.ExceptionAddress;
for (DWORD i = 0; i < first_record.NumberParameters; ++i)
codes_.push_back(first_record.ExceptionInformation[i]);
if (first_record.ExceptionRecord) {
// https://crashpad.chromium.org/bug/43
LOG(WARNING) << "dropping chained ExceptionRecord";
}
if (!process_reader.ReadMemory( if (first_record.ExceptionCode == CrashpadClient::kTriggeredExceptionCode &&
static_cast<WinVMAddress>(exception_pointers.ContextRecord), first_record.NumberParameters == 2 &&
sizeof(*context_record), first_record.ExceptionInformation[0] != 0) {
context_record)) { // This special exception code indicates that the target was crashed by
LOG(ERROR) << "ContextRecord"; // another client calling CrashpadClient::DumpAndCrashTargetProcess(). In
return false; // this case the parameters are a thread id and an exception code which we
// use to fabricate a new exception record.
using ArgumentType = decltype(first_record.ExceptionInformation[0]);
const ArgumentType thread_id = first_record.ExceptionInformation[0];
exception_code_ = static_cast<DWORD>(first_record.ExceptionInformation[1]);
exception_flags_ = EXCEPTION_NONCONTINUABLE;
for (const auto* thread : threads) {
if (thread->ThreadID() == thread_id) {
thread_id_ = thread_id;
exception_address_ = thread->Context()->InstructionPointer();
context_.architecture = thread->Context()->architecture;
if (context_.architecture == kCPUArchitectureX86_64) {
context_union_.x86_64 = *thread->Context()->x86_64;
context_.x86_64 = &context_union_.x86_64;
} else {
context_union_.x86 = *thread->Context()->x86;
context_.x86 = &context_union_.x86;
}
break;
}
}
if (exception_address_ == 0) {
LOG(WARNING) << "thread " << thread_id << " not found";
return false;
}
} else {
// Normal case.
exception_code_ = first_record.ExceptionCode;
exception_flags_ = first_record.ExceptionFlags;
exception_address_ = first_record.ExceptionAddress;
for (DWORD i = 0; i < first_record.NumberParameters; ++i)
codes_.push_back(first_record.ExceptionInformation[i]);
if (first_record.ExceptionRecord) {
// https://crashpad.chromium.org/bug/43
LOG(WARNING) << "dropping chained ExceptionRecord";
}
ContextType context_record;
if (!process_reader.ReadMemory(
static_cast<WinVMAddress>(exception_pointers.ContextRecord),
sizeof(context_record),
&context_record)) {
LOG(ERROR) << "ContextRecord";
return false;
}
native_to_cpu_context(context_record, &context_, &context_union_);
} }
return true; return true;

View File

@ -22,6 +22,7 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "snapshot/cpu_context.h" #include "snapshot/cpu_context.h"
#include "snapshot/exception_snapshot.h" #include "snapshot/exception_snapshot.h"
#include "snapshot/win/thread_snapshot_win.h"
#include "util/misc/initialization_state_dcheck.h" #include "util/misc/initialization_state_dcheck.h"
#include "util/stdlib/pointer_container.h" #include "util/stdlib/pointer_container.h"
#include "util/win/address_types.h" #include "util/win/address_types.h"
@ -35,6 +36,13 @@ namespace internal {
class MemorySnapshotWin; class MemorySnapshotWin;
#if defined(ARCH_CPU_X86_FAMILY)
union CPUContextUnion {
CPUContextX86 x86;
CPUContextX86_64 x86_64;
};
#endif
class ExceptionSnapshotWin final : public ExceptionSnapshot { class ExceptionSnapshotWin final : public ExceptionSnapshot {
public: public:
ExceptionSnapshotWin(); ExceptionSnapshotWin();
@ -53,7 +61,8 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot {
//! an appropriate message logged. //! an appropriate message logged.
bool Initialize(ProcessReaderWin* process_reader, bool Initialize(ProcessReaderWin* process_reader,
DWORD thread_id, DWORD thread_id,
WinVMAddress exception_pointers); WinVMAddress exception_pointers,
const PointerVector<internal::ThreadSnapshotWin>& threads);
// ExceptionSnapshot: // ExceptionSnapshot:
@ -69,15 +78,16 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot {
template <class ExceptionRecordType, template <class ExceptionRecordType,
class ExceptionPointersType, class ExceptionPointersType,
class ContextType> class ContextType>
bool InitializeFromExceptionPointers(const ProcessReaderWin& process_reader, bool InitializeFromExceptionPointers(
WinVMAddress exception_pointers_address, const ProcessReaderWin& process_reader,
ContextType* context_record); WinVMAddress exception_pointers_address,
const PointerVector<internal::ThreadSnapshotWin>& threads,
void (*native_to_cpu_context)(const ContextType& context_record,
CPUContext* context,
CPUContextUnion* context_union));
#if defined(ARCH_CPU_X86_FAMILY) #if defined(ARCH_CPU_X86_FAMILY)
union { CPUContextUnion context_union_;
CPUContextX86 x86;
CPUContextX86_64 x86_64;
} context_union_;
#endif #endif
CPUContext context_; CPUContext context_;
std::vector<uint64_t> codes_; std::vector<uint64_t> codes_;

View File

@ -107,7 +107,8 @@ bool ProcessSnapshotWin::InitializeException(
exception_.reset(new internal::ExceptionSnapshotWin()); exception_.reset(new internal::ExceptionSnapshotWin());
if (!exception_->Initialize(&process_reader_, if (!exception_->Initialize(&process_reader_,
exception_information.thread_id, exception_information.thread_id,
exception_information.exception_pointers)) { exception_information.exception_pointers,
threads_)) {
exception_.reset(); exception_.reset();
return false; return false;
} }

View File

@ -21,6 +21,18 @@
struct CLIENT_ID; struct CLIENT_ID;
NTSTATUS NTAPI NtCreateThreadEx(PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PVOID StartRoutine,
PVOID Argument,
ULONG CreateFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
PVOID /*PPS_ATTRIBUTE_LIST*/ AttributeList);
NTSTATUS NTAPI NtOpenThread(HANDLE* ThreadHandle, NTSTATUS NTAPI NtOpenThread(HANDLE* ThreadHandle,
ACCESS_MASK DesiredAccess, ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES* ObjectAttributes, OBJECT_ATTRIBUTES* ObjectAttributes,
@ -30,6 +42,38 @@ void* NTAPI RtlGetUnloadEventTrace();
namespace crashpad { namespace crashpad {
NTSTATUS NtClose(HANDLE handle) {
static const auto nt_close = GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtClose);
return nt_close(handle);
}
NTSTATUS
NtCreateThreadEx(PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
HANDLE process_handle,
PVOID start_routine,
PVOID argument,
ULONG create_flags,
SIZE_T zero_bits,
SIZE_T stack_size,
SIZE_T maximum_stack_size,
PVOID attribute_list) {
static const auto nt_create_thread_ex =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtCreateThreadEx);
return nt_create_thread_ex(thread_handle,
desired_access,
object_attributes,
process_handle,
start_routine,
argument,
create_flags,
zero_bits,
stack_size,
maximum_stack_size,
attribute_list);
}
NTSTATUS NtQuerySystemInformation( NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS system_information_class, SYSTEM_INFORMATION_CLASS system_information_class,
PVOID system_information, PVOID system_information,

View File

@ -19,6 +19,23 @@
namespace crashpad { namespace crashpad {
NTSTATUS NtClose(HANDLE handle);
// http://processhacker.sourceforge.net/doc/ntpsapi_8h_source.html
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
NTSTATUS
NtCreateThreadEx(PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
HANDLE process_handle,
PVOID start_routine,
PVOID argument,
ULONG create_flags,
SIZE_T zero_bits,
SIZE_T stack_size,
SIZE_T maximum_stack_size,
PVOID /*PPS_ATTRIBUTE_LIST*/ attribute_list);
// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of // Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of
// ntstatus.h. // ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)