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 <vector>
#include <stdint.h>
#include "base/files/file_path.h"
#include "base/macros.h"
#include "build/build_config.h"
@ -152,6 +154,41 @@ class CrashpadClient {
//! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would generally
//! passed to an unhandled exception filter.
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
//! \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/synchronization/lock.h"
#include "util/file/file_io.h"
#include "util/win/address_types.h"
#include "util/win/command_line.h"
#include "util/win/critical_section_with_debug_info.h"
#include "util/win/get_function.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/scoped_handle.h"
#include "util/win/scoped_process_suspend.h"
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 crashpad {
@ -469,4 +487,247 @@ void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* 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

View File

@ -110,6 +110,40 @@
'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',
'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
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
crash handler. If pipe_name is set, crashpad_handler will be started first. If
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.
"""
test_database = MakeTempDir()
@ -111,11 +112,12 @@ def GetDumpFromProgram(out_dir, pipe_name, executable_name):
printed = True
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:
subprocess.call([os.path.join(out_dir, executable_name),
os.path.join(out_dir, 'crashpad_handler.exe'),
test_database])
test_database] + list(args))
out = subprocess.check_output([
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')
def GetDumpFromOtherProgram(out_dir, pipe_name, *args):
return GetDumpFromProgram(out_dir, pipe_name, 'crash_other_program.exe',
*args)
def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name):
return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe')
@ -182,6 +189,8 @@ def RunTests(cdb_path,
start_handler_dump_path,
destroyed_dump_path,
z7_dump_path,
other_program_path,
other_program_no_exception_path,
pipe_name):
"""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
@ -311,6 +320,18 @@ def RunTests(cdb_path,
out.Check(r'z7_test C \(codeview symbols\) z7_test.dll',
'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):
try:
@ -352,11 +373,22 @@ def main(args):
if not z7_dump_path:
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,
crashy_dump_path,
start_handler_dump_path,
destroyed_dump_path,
z7_dump_path,
other_program_path,
other_program_no_exception_path,
pipe_name)
return 0

View File

@ -14,6 +14,7 @@
#include "snapshot/win/exception_snapshot_win.h"
#include "client/crashpad_client.h"
#include "snapshot/capture_memory.h"
#include "snapshot/memory_snapshot.h"
#include "snapshot/win/cpu_context_win.h"
@ -25,6 +26,34 @@
namespace crashpad {
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()
: ExceptionSnapshot(),
context_union_(),
@ -40,9 +69,11 @@ ExceptionSnapshotWin::ExceptionSnapshotWin()
ExceptionSnapshotWin::~ExceptionSnapshotWin() {
}
bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
bool ExceptionSnapshotWin::Initialize(
ProcessReaderWin* process_reader,
DWORD thread_id,
WinVMAddress exception_pointers_address) {
WinVMAddress exception_pointers_address,
const PointerVector<internal::ThreadSnapshotWin>& threads) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
const ProcessReaderWin::Thread* thread = nullptr;
@ -62,32 +93,28 @@ bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
#if defined(ARCH_CPU_32_BITS)
const bool is_64_bit = false;
using Context32 = CONTEXT;
#elif defined(ARCH_CPU_64_BITS)
const bool is_64_bit = process_reader->Is64Bit();
using Context32 = WOW64_CONTEXT;
if (is_64_bit) {
CONTEXT context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD64,
process_types::EXCEPTION_POINTERS64>(
*process_reader, exception_pointers_address, &context_record)) {
*process_reader,
exception_pointers_address,
threads,
&NativeContextToCPUContext64)) {
return false;
}
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
InitializeX64Context(context_record, context_.x86_64);
}
#endif
if (!is_64_bit) {
Context32 context_record;
if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32,
process_types::EXCEPTION_POINTERS32>(
*process_reader, exception_pointers_address, &context_record)) {
*process_reader,
exception_pointers_address,
threads,
&NativeContextToCPUContext32)) {
return false;
}
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(context_record, context_.x86);
}
CaptureMemoryDelegateWin capture_memory_delegate(
@ -143,7 +170,10 @@ template <class ExceptionRecordType,
bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
const ProcessReaderWin& process_reader,
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;
if (!process_reader.ReadMemory(exception_pointers_address,
sizeof(exception_pointers),
@ -164,6 +194,40 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
LOG(ERROR) << "ExceptionRecord";
return false;
}
if (first_record.ExceptionCode == CrashpadClient::kTriggeredExceptionCode &&
first_record.NumberParameters == 2 &&
first_record.ExceptionInformation[0] != 0) {
// This special exception code indicates that the target was crashed by
// another client calling CrashpadClient::DumpAndCrashTargetProcess(). In
// 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;
@ -174,14 +238,18 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
LOG(WARNING) << "dropping chained ExceptionRecord";
}
ContextType context_record;
if (!process_reader.ReadMemory(
static_cast<WinVMAddress>(exception_pointers.ContextRecord),
sizeof(*context_record),
context_record)) {
sizeof(context_record),
&context_record)) {
LOG(ERROR) << "ContextRecord";
return false;
}
native_to_cpu_context(context_record, &context_, &context_union_);
}
return true;
}

View File

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

View File

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

View File

@ -21,6 +21,18 @@
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,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES* ObjectAttributes,
@ -30,6 +42,38 @@ void* NTAPI RtlGetUnloadEventTrace();
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(
SYSTEM_INFORMATION_CLASS system_information_class,
PVOID system_information,

View File

@ -19,6 +19,23 @@
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
// ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)