| Index: third_party/crashpad/crashpad/client/crashpad_client_win.cc
|
| diff --git a/third_party/crashpad/crashpad/client/crashpad_client_win.cc b/third_party/crashpad/crashpad/client/crashpad_client_win.cc
|
| index 5e3d60c68c21c92077609aa835d90d09d1f24fad..537e18ab99088b55deadc48c63c54da6289c157e 100644
|
| --- a/third_party/crashpad/crashpad/client/crashpad_client_win.cc
|
| +++ b/third_party/crashpad/crashpad/client/crashpad_client_win.cc
|
| @@ -28,12 +28,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 {
|
|
|
| @@ -168,6 +173,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 {
|
| @@ -462,7 +480,255 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
|
|
|
| // static
|
| void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) {
|
| + if (g_signal_exception == INVALID_HANDLE_VALUE) {
|
| + LOG(ERROR) << "haven't called UseHandler()";
|
| + return;
|
| + }
|
| +
|
| 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
|
|
|