mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
win: Implement CRASHPAD_SIMULATE_CRASH()
Windows requires the connection to the handler to do anything, so it can't really be implemented or tested without CrashpadClient and the connection machinery. R=mark@chromium.org BUG=crashpad:53 Review URL: https://codereview.chromium.org/1356383002 .
This commit is contained in:
parent
9bc0a99681
commit
475ac81cce
@ -47,6 +47,7 @@
|
||||
'simulate_crash.h',
|
||||
'simulate_crash_mac.cc',
|
||||
'simulate_crash_mac.h',
|
||||
'simulate_crash_win.h',
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include "base/mac/scoped_mach_port.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
@ -87,6 +89,13 @@ class CrashpadClient {
|
||||
//!
|
||||
//! \return `true` on success and `false` on failure.
|
||||
bool SetHandler(const std::string& ipc_port);
|
||||
|
||||
//! \brief Requests that the handler capture a dump even though there hasn't
|
||||
//! been a crash.
|
||||
//!
|
||||
//! \param[in] context A CONTEXT, generally captured by `RtlCaptureContext()`
|
||||
//! or similar.
|
||||
static void DumpWithoutCrash(const CONTEXT& context);
|
||||
#endif
|
||||
|
||||
//! \brief Configures the process to direct its crashes to a Crashpad handler.
|
||||
|
@ -21,17 +21,32 @@
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/win/registration_protocol_win.h"
|
||||
#include "util/win/scoped_handle.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// This handle is never closed.
|
||||
// This handle is never closed. This is used to signal to the server that a dump
|
||||
// should be taken in the event of a crash.
|
||||
HANDLE g_signal_exception = INVALID_HANDLE_VALUE;
|
||||
|
||||
// Where we store the exception information that the crash handler reads.
|
||||
crashpad::ExceptionInformation g_exception_information;
|
||||
crashpad::ExceptionInformation g_crash_exception_information;
|
||||
|
||||
// These handles are never closed. g_signal_non_crash_dump is used to signal to
|
||||
// the server to take a dump (not due to an exception), and the server will
|
||||
// signal g_non_crash_dump_done when the dump is completed.
|
||||
HANDLE g_signal_non_crash_dump = INVALID_HANDLE_VALUE;
|
||||
HANDLE g_non_crash_dump_done = INVALID_HANDLE_VALUE;
|
||||
|
||||
// Guards multiple simultaneous calls to DumpWithoutCrash(). This is leaked.
|
||||
base::Lock* g_non_crash_dump_lock;
|
||||
|
||||
// Where we store a pointer to the context information when taking a non-crash
|
||||
// dump.
|
||||
crashpad::ExceptionInformation g_non_crash_exception_information;
|
||||
|
||||
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
||||
// Tracks whether a thread has already entered UnhandledExceptionHandler.
|
||||
@ -55,8 +70,8 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
||||
|
||||
// Otherwise, we're the first thread, so record the exception pointer and
|
||||
// signal the crash handler.
|
||||
g_exception_information.thread_id = GetCurrentThreadId();
|
||||
g_exception_information.exception_pointers =
|
||||
g_crash_exception_information.thread_id = GetCurrentThreadId();
|
||||
g_crash_exception_information.exception_pointers =
|
||||
reinterpret_cast<crashpad::WinVMAddress>(exception_pointers);
|
||||
|
||||
// Now signal the crash server, which will take a dump and then terminate us
|
||||
@ -69,8 +84,6 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
||||
// Sleep for a while to allow it to process us. Eventually, we terminate
|
||||
// ourselves in case the crash server is gone, so that we don't leave zombies
|
||||
// around. This would ideally never happen.
|
||||
// TODO(scottmg): Re-add the "reply" event here, for implementing
|
||||
// DumpWithoutCrashing.
|
||||
Sleep(kMillisecondsUntilTerminate);
|
||||
|
||||
LOG(ERROR) << "crash server did not respond, self-terminating";
|
||||
@ -102,13 +115,19 @@ bool CrashpadClient::StartHandler(
|
||||
}
|
||||
|
||||
bool CrashpadClient::SetHandler(const std::string& ipc_port) {
|
||||
DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE);
|
||||
DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE);
|
||||
DCHECK_EQ(g_non_crash_dump_done, INVALID_HANDLE_VALUE);
|
||||
|
||||
ClientToServerMessage message;
|
||||
memset(&message, 0, sizeof(message));
|
||||
message.type = ClientToServerMessage::kRegister;
|
||||
message.registration.version = RegistrationRequest::kMessageVersion;
|
||||
message.registration.client_process_id = GetCurrentProcessId();
|
||||
message.registration.exception_information =
|
||||
reinterpret_cast<WinVMAddress>(&g_exception_information);
|
||||
message.registration.crash_exception_information =
|
||||
reinterpret_cast<WinVMAddress>(&g_crash_exception_information);
|
||||
message.registration.non_crash_exception_information =
|
||||
reinterpret_cast<WinVMAddress>(&g_non_crash_exception_information);
|
||||
|
||||
ServerToClientMessage response = {0};
|
||||
|
||||
@ -119,17 +138,80 @@ bool CrashpadClient::SetHandler(const std::string& ipc_port) {
|
||||
|
||||
// The server returns these already duplicated to be valid in this process.
|
||||
g_signal_exception = reinterpret_cast<HANDLE>(
|
||||
static_cast<uintptr_t>(response.registration.request_report_event));
|
||||
static_cast<uintptr_t>(response.registration.request_crash_dump_event));
|
||||
g_signal_non_crash_dump = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(
|
||||
response.registration.request_non_crash_dump_event));
|
||||
g_non_crash_dump_done = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(
|
||||
response.registration.non_crash_dump_completed_event));
|
||||
|
||||
g_non_crash_dump_lock = new base::Lock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrashpadClient::UseHandler() {
|
||||
if (g_signal_exception == INVALID_HANDLE_VALUE)
|
||||
if (g_signal_exception == INVALID_HANDLE_VALUE ||
|
||||
g_signal_non_crash_dump == INVALID_HANDLE_VALUE ||
|
||||
g_non_crash_dump_done == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In theory we could store the previous handler but it is not clear what
|
||||
// use we have for it.
|
||||
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) {
|
||||
if (g_signal_non_crash_dump == INVALID_HANDLE_VALUE ||
|
||||
g_non_crash_dump_done == INVALID_HANDLE_VALUE) {
|
||||
LOG(ERROR) << "haven't called SetHandler()";
|
||||
return;
|
||||
}
|
||||
|
||||
// In the non-crashing case, we aren't concerned about avoiding calls into
|
||||
// Win32 APIs, so just use regular locking here in case of multiple threads
|
||||
// calling this function. If a crash occurs while we're in here, the worst
|
||||
// that can happen is that the server captures a partial dump for this path
|
||||
// because on the other thread gathering a crash dump, it TerminateProcess()d,
|
||||
// causing this one to abort.
|
||||
base::AutoLock lock(*g_non_crash_dump_lock);
|
||||
|
||||
// Create a fake EXCEPTION_POINTERS to give the handler something to work
|
||||
// with.
|
||||
EXCEPTION_POINTERS exception_pointers = {0};
|
||||
|
||||
// This is logically const, but EXCEPTION_POINTERS does not declare it as
|
||||
// const, so we have to cast that away from the argument.
|
||||
exception_pointers.ContextRecord = const_cast<CONTEXT*>(&context);
|
||||
|
||||
// We include a fake exception and use a code of '0x517a7ed' (something like
|
||||
// "simulated") so that it's relatively obvious in windbg that it's not
|
||||
// actually an exception. Most values in
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082.aspx have
|
||||
// some of the top nibble set, so we make sure to pick a value that doesn't,
|
||||
// so as to be unlikely to conflict.
|
||||
const uint32_t kSimulatedExceptionCode = 0x517a7ed;
|
||||
EXCEPTION_RECORD record = {0};
|
||||
record.ExceptionCode = kSimulatedExceptionCode;
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
record.ExceptionAddress = reinterpret_cast<void*>(context.Rip);
|
||||
#else
|
||||
record.ExceptionAddress = reinterpret_cast<void*>(context.Eip);
|
||||
#endif // ARCH_CPU_64_BITS
|
||||
|
||||
exception_pointers.ExceptionRecord = &record;
|
||||
|
||||
g_non_crash_exception_information.thread_id = GetCurrentThreadId();
|
||||
g_non_crash_exception_information.exception_pointers =
|
||||
reinterpret_cast<crashpad::WinVMAddress>(&exception_pointers);
|
||||
|
||||
bool set_event_result = SetEvent(g_signal_non_crash_dump);
|
||||
PLOG_IF(ERROR, !set_event_result) << "SetEvent";
|
||||
|
||||
DWORD wfso_result = WaitForSingleObject(g_non_crash_dump_done, INFINITE);
|
||||
PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject";
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include "client/simulate_crash_mac.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include "client/simulate_crash_win.h"
|
||||
#endif
|
||||
|
||||
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_H_
|
||||
|
30
client/simulate_crash_win.h
Normal file
30
client/simulate_crash_win.h
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_
|
||||
#define CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "client/crashpad_client.h"
|
||||
|
||||
//! \brief Captures the CPU context and captures a dump without an exception.
|
||||
#define CRASHPAD_SIMULATE_CRASH() \
|
||||
do { \
|
||||
CONTEXT context; \
|
||||
RtlCaptureContext(&context); \
|
||||
crashpad::CrashpadClient::DumpWithoutCrash(context); \
|
||||
} while (false)
|
||||
|
||||
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_
|
@ -97,6 +97,7 @@
|
||||
['OS=="win"', {
|
||||
'dependencies': [
|
||||
'crashpad_snapshot_test_crashing_child',
|
||||
'crashpad_snapshot_test_dump_without_crashing',
|
||||
'crashpad_snapshot_test_image_reader',
|
||||
'crashpad_snapshot_test_image_reader_module',
|
||||
],
|
||||
@ -152,6 +153,19 @@
|
||||
'win/crashpad_snapshot_test_crashing_child.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'crashpad_snapshot_test_dump_without_crashing',
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'../client/client.gyp:crashpad_client',
|
||||
'../compat/compat.gyp:crashpad_compat',
|
||||
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
||||
'../util/util.gyp:crashpad_util',
|
||||
],
|
||||
'sources': [
|
||||
'win/crashpad_snapshot_test_dump_without_crashing.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'crashpad_snapshot_test_image_reader',
|
||||
'type': 'executable',
|
||||
|
42
snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc
Normal file
42
snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc
Normal file
@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "client/crashpad_client.h"
|
||||
#include "client/simulate_crash.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/win/address_types.h"
|
||||
|
||||
__declspec(noinline) crashpad::WinVMAddress CurrentAddress() {
|
||||
return reinterpret_cast<crashpad::WinVMAddress>(_ReturnAddress());
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CHECK_EQ(argc, 2);
|
||||
|
||||
crashpad::CrashpadClient client;
|
||||
CHECK(client.SetHandler(argv[1]));
|
||||
CHECK(client.UseHandler());
|
||||
|
||||
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
PCHECK(out != INVALID_HANDLE_VALUE) << "GetStdHandle";
|
||||
crashpad::WinVMAddress current_address = CurrentAddress();
|
||||
crashpad::CheckedWriteFile(out, ¤t_address, sizeof(current_address));
|
||||
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
|
||||
return 0;
|
||||
}
|
@ -36,65 +36,6 @@ namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
|
||||
HANDLE handle;
|
||||
if (DuplicateHandle(GetCurrentProcess(),
|
||||
event,
|
||||
process,
|
||||
&handle,
|
||||
SYNCHRONIZE | EVENT_MODIFY_STATE,
|
||||
false,
|
||||
0)) {
|
||||
return handle;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class Delegate : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
Delegate(HANDLE server_ready, HANDLE completed_test_event)
|
||||
: server_ready_(server_ready),
|
||||
completed_test_event_(completed_test_event),
|
||||
break_near_(0) {}
|
||||
~Delegate() override {}
|
||||
|
||||
void set_break_near(WinVMAddress break_near) { break_near_ = break_near; }
|
||||
|
||||
void ExceptionHandlerServerStarted() override { SetEvent(server_ready_); }
|
||||
|
||||
unsigned int ExceptionHandlerServerException(
|
||||
HANDLE process,
|
||||
WinVMAddress exception_information_address) override {
|
||||
ScopedProcessSuspend suspend(process);
|
||||
ProcessSnapshotWin snapshot;
|
||||
snapshot.Initialize(process, ProcessSuspensionState::kSuspended);
|
||||
snapshot.InitializeException(exception_information_address);
|
||||
|
||||
// Confirm the exception record was read correctly.
|
||||
EXPECT_NE(snapshot.Exception()->ThreadID(), 0u);
|
||||
EXPECT_EQ(snapshot.Exception()->Exception(), EXCEPTION_BREAKPOINT);
|
||||
|
||||
// Verify the exception happened at the expected location with a bit of
|
||||
// slop space to allow for reading the current PC before the exception
|
||||
// happens. See CrashingChildProcess::Run().
|
||||
const uint64_t kAllowedOffset = 64;
|
||||
EXPECT_GT(snapshot.Exception()->ExceptionAddress(), break_near_);
|
||||
EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
|
||||
break_near_ + kAllowedOffset);
|
||||
|
||||
SetEvent(completed_test_event_);
|
||||
|
||||
return snapshot.Exception()->Exception();
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE server_ready_; // weak
|
||||
HANDLE completed_test_event_; // weak
|
||||
WinVMAddress break_near_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Delegate);
|
||||
};
|
||||
|
||||
// Runs the ExceptionHandlerServer on a background thread.
|
||||
class RunServerThread : public Thread {
|
||||
public:
|
||||
@ -133,13 +74,58 @@ class ScopedStopServerAndJoinThread {
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
|
||||
};
|
||||
|
||||
class CrashingDelegate : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
CrashingDelegate(HANDLE server_ready, HANDLE completed_test_event)
|
||||
: server_ready_(server_ready),
|
||||
completed_test_event_(completed_test_event),
|
||||
break_near_(0) {}
|
||||
~CrashingDelegate() override {}
|
||||
|
||||
void set_break_near(WinVMAddress break_near) { break_near_ = break_near; }
|
||||
|
||||
void ExceptionHandlerServerStarted() override { SetEvent(server_ready_); }
|
||||
|
||||
unsigned int ExceptionHandlerServerException(
|
||||
HANDLE process,
|
||||
WinVMAddress exception_information_address) override {
|
||||
ScopedProcessSuspend suspend(process);
|
||||
ProcessSnapshotWin snapshot;
|
||||
snapshot.Initialize(process, ProcessSuspensionState::kSuspended);
|
||||
snapshot.InitializeException(exception_information_address);
|
||||
|
||||
// Confirm the exception record was read correctly.
|
||||
EXPECT_NE(snapshot.Exception()->ThreadID(), 0u);
|
||||
EXPECT_EQ(snapshot.Exception()->Exception(), EXCEPTION_BREAKPOINT);
|
||||
|
||||
// Verify the exception happened at the expected location with a bit of
|
||||
// slop space to allow for reading the current PC before the exception
|
||||
// happens. See TestCrashingChild().
|
||||
const uint64_t kAllowedOffset = 64;
|
||||
EXPECT_GT(snapshot.Exception()->ExceptionAddress(), break_near_);
|
||||
EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
|
||||
break_near_ + kAllowedOffset);
|
||||
|
||||
SetEvent(completed_test_event_);
|
||||
|
||||
return snapshot.Exception()->Exception();
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE server_ready_; // weak
|
||||
HANDLE completed_test_event_; // weak
|
||||
WinVMAddress break_near_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashingDelegate);
|
||||
};
|
||||
|
||||
void TestCrashingChild(const base::string16& directory_modification) {
|
||||
// Set up the registration server on a background thread.
|
||||
std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
|
||||
base::StringPrintf("%08x", GetCurrentProcessId());
|
||||
ScopedKernelHANDLE server_ready(CreateEvent(nullptr, false, false, nullptr));
|
||||
ScopedKernelHANDLE completed(CreateEvent(nullptr, false, false, nullptr));
|
||||
Delegate delegate(server_ready.get(), completed.get());
|
||||
CrashingDelegate delegate(server_ready.get(), completed.get());
|
||||
|
||||
ExceptionHandlerServer exception_handler_server;
|
||||
RunServerThread server_thread(
|
||||
@ -186,6 +172,104 @@ TEST(ExceptionSnapshotWinTest, ChildCrashWOW64) {
|
||||
}
|
||||
#endif // ARCH_CPU_64_BITS
|
||||
|
||||
class SimulateDelegate : public ExceptionHandlerServer::Delegate {
|
||||
public:
|
||||
SimulateDelegate(HANDLE server_ready, HANDLE completed_test_event)
|
||||
: server_ready_(server_ready),
|
||||
completed_test_event_(completed_test_event),
|
||||
dump_near_(0) {}
|
||||
~SimulateDelegate() override {}
|
||||
|
||||
void set_dump_near(WinVMAddress dump_near) { dump_near_ = dump_near; }
|
||||
|
||||
void ExceptionHandlerServerStarted() override { SetEvent(server_ready_); }
|
||||
|
||||
unsigned int ExceptionHandlerServerException(
|
||||
HANDLE process,
|
||||
WinVMAddress exception_information_address) override {
|
||||
ScopedProcessSuspend suspend(process);
|
||||
ProcessSnapshotWin snapshot;
|
||||
snapshot.Initialize(process, ProcessSuspensionState::kSuspended);
|
||||
snapshot.InitializeException(exception_information_address);
|
||||
EXPECT_TRUE(snapshot.Exception());
|
||||
EXPECT_EQ(0, snapshot.Exception()->Exception());
|
||||
EXPECT_EQ(0, snapshot.Exception()->ExceptionAddress());
|
||||
|
||||
// Verify the dump was captured at the expected location with some slop
|
||||
// space.
|
||||
const uint64_t kAllowedOffset = 64;
|
||||
EXPECT_GT(snapshot.Exception()->Context()->InstructionPointer(),
|
||||
dump_near_);
|
||||
EXPECT_LT(snapshot.Exception()->Context()->InstructionPointer(),
|
||||
dump_near_ + kAllowedOffset);
|
||||
|
||||
SetEvent(completed_test_event_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE server_ready_; // weak
|
||||
HANDLE completed_test_event_; // weak
|
||||
WinVMAddress dump_near_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SimulateDelegate);
|
||||
};
|
||||
|
||||
void TestDumpWithoutCrashingChild(
|
||||
const base::string16& directory_modification) {
|
||||
// Set up the registration server on a background thread.
|
||||
std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
|
||||
base::StringPrintf("%08x", GetCurrentProcessId());
|
||||
ScopedKernelHANDLE server_ready(CreateEvent(nullptr, false, false, nullptr));
|
||||
ScopedKernelHANDLE completed(CreateEvent(nullptr, false, false, nullptr));
|
||||
SimulateDelegate delegate(server_ready.get(), completed.get());
|
||||
|
||||
ExceptionHandlerServer exception_handler_server;
|
||||
RunServerThread server_thread(
|
||||
&exception_handler_server, &delegate, pipe_name);
|
||||
server_thread.Start();
|
||||
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
|
||||
&exception_handler_server, &server_thread);
|
||||
|
||||
WaitForSingleObject(server_ready.get(), INFINITE);
|
||||
|
||||
// Spawn a child process, passing it the pipe name to connect to.
|
||||
base::FilePath test_executable = Paths::Executable();
|
||||
std::wstring child_test_executable =
|
||||
test_executable.DirName()
|
||||
.Append(directory_modification)
|
||||
.Append(test_executable.BaseName().RemoveFinalExtension().value() +
|
||||
L"_dump_without_crashing.exe")
|
||||
.value();
|
||||
ChildLauncher child(child_test_executable, base::UTF8ToUTF16(pipe_name));
|
||||
child.Start();
|
||||
|
||||
// The child tells us (approximately) where it will capture a dump.
|
||||
WinVMAddress dump_near_address;
|
||||
LoggingReadFile(child.stdout_read_handle(),
|
||||
&dump_near_address,
|
||||
sizeof(dump_near_address));
|
||||
delegate.set_dump_near(dump_near_address);
|
||||
|
||||
// Wait for the child to crash and the exception information to be validated.
|
||||
WaitForSingleObject(completed.get(), INFINITE);
|
||||
}
|
||||
|
||||
TEST(SimulateCrash, ChildDumpWithoutCrashing) {
|
||||
TestDumpWithoutCrashingChild(FILE_PATH_LITERAL("."));
|
||||
}
|
||||
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
TEST(SimulateCrash, ChildDumpWithoutCrashingWOW64) {
|
||||
#ifndef NDEBUG
|
||||
TestDumpWithoutCrashingChild(FILE_PATH_LITERAL("..\\..\\out\\Debug"));
|
||||
#else
|
||||
TestDumpWithoutCrashingChild(FILE_PATH_LITERAL("..\\..\\out\\Release"));
|
||||
#endif
|
||||
}
|
||||
#endif // ARCH_CPU_64_BITS
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -103,19 +103,31 @@ class ClientData {
|
||||
ClientData(HANDLE port,
|
||||
ExceptionHandlerServer::Delegate* delegate,
|
||||
ScopedKernelHANDLE process,
|
||||
WinVMAddress exception_information_address,
|
||||
WAITORTIMERCALLBACK dump_request_callback,
|
||||
WinVMAddress crash_exception_information_address,
|
||||
WinVMAddress non_crash_exception_information_address,
|
||||
WAITORTIMERCALLBACK crash_dump_request_callback,
|
||||
WAITORTIMERCALLBACK non_crash_dump_request_callback,
|
||||
WAITORTIMERCALLBACK process_end_callback)
|
||||
: dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
|
||||
: crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
|
||||
non_crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
|
||||
process_end_thread_pool_wait_(INVALID_HANDLE_VALUE),
|
||||
lock_(),
|
||||
port_(port),
|
||||
delegate_(delegate),
|
||||
dump_requested_event_(
|
||||
crash_dump_requested_event_(
|
||||
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
|
||||
non_crash_dump_requested_event_(
|
||||
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
|
||||
non_crash_dump_completed_event_(
|
||||
CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
|
||||
process_(process.Pass()),
|
||||
exception_information_address_(exception_information_address) {
|
||||
RegisterThreadPoolWaits(dump_request_callback, process_end_callback);
|
||||
crash_exception_information_address_(
|
||||
crash_exception_information_address),
|
||||
non_crash_exception_information_address_(
|
||||
non_crash_exception_information_address) {
|
||||
RegisterThreadPoolWaits(crash_dump_request_callback,
|
||||
non_crash_dump_request_callback,
|
||||
process_end_callback);
|
||||
}
|
||||
|
||||
~ClientData() {
|
||||
@ -128,22 +140,44 @@ class ClientData {
|
||||
base::Lock* lock() { return &lock_; }
|
||||
HANDLE port() const { return port_; }
|
||||
ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
|
||||
HANDLE dump_requested_event() const { return dump_requested_event_.get(); }
|
||||
WinVMAddress exception_information_address() const {
|
||||
return exception_information_address_;
|
||||
HANDLE crash_dump_requested_event() const {
|
||||
return crash_dump_requested_event_.get();
|
||||
}
|
||||
HANDLE non_crash_dump_requested_event() const {
|
||||
return non_crash_dump_requested_event_.get();
|
||||
}
|
||||
HANDLE non_crash_dump_completed_event() const {
|
||||
return non_crash_dump_completed_event_.get();
|
||||
}
|
||||
WinVMAddress crash_exception_information_address() const {
|
||||
return crash_exception_information_address_;
|
||||
}
|
||||
WinVMAddress non_crash_exception_information_address() const {
|
||||
return non_crash_exception_information_address_;
|
||||
}
|
||||
HANDLE process() const { return process_.get(); }
|
||||
|
||||
private:
|
||||
void RegisterThreadPoolWaits(WAITORTIMERCALLBACK dump_request_callback,
|
||||
WAITORTIMERCALLBACK process_end_callback) {
|
||||
if (!RegisterWaitForSingleObject(&dump_request_thread_pool_wait_,
|
||||
dump_requested_event_.get(),
|
||||
dump_request_callback,
|
||||
void RegisterThreadPoolWaits(
|
||||
WAITORTIMERCALLBACK crash_dump_request_callback,
|
||||
WAITORTIMERCALLBACK non_crash_dump_request_callback,
|
||||
WAITORTIMERCALLBACK process_end_callback) {
|
||||
if (!RegisterWaitForSingleObject(&crash_dump_request_thread_pool_wait_,
|
||||
crash_dump_requested_event_.get(),
|
||||
crash_dump_request_callback,
|
||||
this,
|
||||
INFINITE,
|
||||
WT_EXECUTEDEFAULT)) {
|
||||
LOG(ERROR) << "RegisterWaitForSingleObject dump requested";
|
||||
LOG(ERROR) << "RegisterWaitForSingleObject crash dump requested";
|
||||
}
|
||||
|
||||
if (!RegisterWaitForSingleObject(&non_crash_dump_request_thread_pool_wait_,
|
||||
non_crash_dump_requested_event_.get(),
|
||||
non_crash_dump_request_callback,
|
||||
this,
|
||||
INFINITE,
|
||||
WT_EXECUTEDEFAULT)) {
|
||||
LOG(ERROR) << "RegisterWaitForSingleObject non-crash dump requested";
|
||||
}
|
||||
|
||||
if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_,
|
||||
@ -160,23 +194,31 @@ class ClientData {
|
||||
// delete this object. Because of this, it must be executed on the main
|
||||
// thread, not a threadpool thread.
|
||||
void UnregisterThreadPoolWaits() {
|
||||
UnregisterWaitEx(dump_request_thread_pool_wait_, INVALID_HANDLE_VALUE);
|
||||
dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
|
||||
UnregisterWaitEx(crash_dump_request_thread_pool_wait_,
|
||||
INVALID_HANDLE_VALUE);
|
||||
crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
|
||||
UnregisterWaitEx(non_crash_dump_request_thread_pool_wait_,
|
||||
INVALID_HANDLE_VALUE);
|
||||
non_crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
|
||||
UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE);
|
||||
process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// These are only accessed on the main thread.
|
||||
HANDLE dump_request_thread_pool_wait_;
|
||||
HANDLE crash_dump_request_thread_pool_wait_;
|
||||
HANDLE non_crash_dump_request_thread_pool_wait_;
|
||||
HANDLE process_end_thread_pool_wait_;
|
||||
|
||||
base::Lock lock_;
|
||||
// Access to these fields must be guarded by lock_.
|
||||
HANDLE port_; // weak
|
||||
ExceptionHandlerServer::Delegate* delegate_; // weak
|
||||
ScopedKernelHANDLE dump_requested_event_;
|
||||
ScopedKernelHANDLE crash_dump_requested_event_;
|
||||
ScopedKernelHANDLE non_crash_dump_requested_event_;
|
||||
ScopedKernelHANDLE non_crash_dump_completed_event_;
|
||||
ScopedKernelHANDLE process_;
|
||||
WinVMAddress exception_information_address_;
|
||||
WinVMAddress crash_exception_information_address_;
|
||||
WinVMAddress non_crash_exception_information_address_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ClientData);
|
||||
};
|
||||
@ -364,21 +406,29 @@ bool ExceptionHandlerServer::ServiceClientConnection(
|
||||
internal::ClientData* client;
|
||||
{
|
||||
base::AutoLock lock(*service_context.clients_lock());
|
||||
client =
|
||||
new internal::ClientData(service_context.port(),
|
||||
service_context.delegate(),
|
||||
ScopedKernelHANDLE(client_process),
|
||||
message.registration.exception_information,
|
||||
&OnDumpEvent,
|
||||
&OnProcessEnd);
|
||||
client = new internal::ClientData(
|
||||
service_context.port(),
|
||||
service_context.delegate(),
|
||||
ScopedKernelHANDLE(client_process),
|
||||
message.registration.crash_exception_information,
|
||||
message.registration.non_crash_exception_information,
|
||||
&OnCrashDumpEvent,
|
||||
&OnNonCrashDumpEvent,
|
||||
&OnProcessEnd);
|
||||
service_context.clients()->insert(client);
|
||||
}
|
||||
|
||||
// Duplicate the events back to the client so they can request a dump.
|
||||
ServerToClientMessage response;
|
||||
response.registration.request_report_event =
|
||||
base::checked_cast<uint32_t>(reinterpret_cast<uintptr_t>(
|
||||
DuplicateEvent(client->process(), client->dump_requested_event())));
|
||||
response.registration.request_crash_dump_event =
|
||||
base::checked_cast<uint32_t>(reinterpret_cast<uintptr_t>(DuplicateEvent(
|
||||
client->process(), client->crash_dump_requested_event())));
|
||||
response.registration.request_non_crash_dump_event =
|
||||
base::checked_cast<uint32_t>(reinterpret_cast<uintptr_t>(DuplicateEvent(
|
||||
client->process(), client->non_crash_dump_requested_event())));
|
||||
response.registration.non_crash_dump_completed_event =
|
||||
base::checked_cast<uint32_t>(reinterpret_cast<uintptr_t>(DuplicateEvent(
|
||||
client->process(), client->non_crash_dump_completed_event())));
|
||||
|
||||
if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response)))
|
||||
return false;
|
||||
@ -408,18 +458,32 @@ DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) {
|
||||
}
|
||||
|
||||
// static
|
||||
void __stdcall ExceptionHandlerServer::OnDumpEvent(void* ctx, BOOLEAN) {
|
||||
void __stdcall ExceptionHandlerServer::OnCrashDumpEvent(void* ctx, BOOLEAN) {
|
||||
// This function is executed on the thread pool.
|
||||
internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
|
||||
base::AutoLock lock(*client->lock());
|
||||
|
||||
// Capture the exception.
|
||||
unsigned int exit_code = client->delegate()->ExceptionHandlerServerException(
|
||||
client->process(), client->exception_information_address());
|
||||
client->process(), client->crash_exception_information_address());
|
||||
|
||||
TerminateProcess(client->process(), exit_code);
|
||||
}
|
||||
|
||||
// static
|
||||
void __stdcall ExceptionHandlerServer::OnNonCrashDumpEvent(void* ctx, BOOLEAN) {
|
||||
// This function is executed on the thread pool.
|
||||
internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
|
||||
base::AutoLock lock(*client->lock());
|
||||
|
||||
// Capture the exception.
|
||||
client->delegate()->ExceptionHandlerServerException(
|
||||
client->process(), client->non_crash_exception_information_address());
|
||||
|
||||
bool result = SetEvent(client->non_crash_dump_completed_event());
|
||||
PLOG_IF(ERROR, !result) << "SetEvent";
|
||||
}
|
||||
|
||||
// static
|
||||
void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) {
|
||||
// This function is executed on the thread pool.
|
||||
|
@ -76,7 +76,8 @@ class ExceptionHandlerServer {
|
||||
static bool ServiceClientConnection(
|
||||
const internal::PipeServiceContext& service_context);
|
||||
static DWORD __stdcall PipeServiceProc(void* ctx);
|
||||
static void __stdcall OnDumpEvent(void* ctx, BOOLEAN);
|
||||
static void __stdcall OnCrashDumpEvent(void* ctx, BOOLEAN);
|
||||
static void __stdcall OnNonCrashDumpEvent(void* ctx, BOOLEAN);
|
||||
static void __stdcall OnProcessEnd(void* ctx, BOOLEAN);
|
||||
|
||||
ScopedKernelHANDLE port_;
|
||||
|
@ -50,8 +50,14 @@ struct RegistrationRequest {
|
||||
DWORD client_process_id;
|
||||
|
||||
//! \brief The address, in the client process address space, of an
|
||||
//! ExceptionInformation structure.
|
||||
WinVMAddress exception_information;
|
||||
//! ExceptionInformation structure, used when handling a crash dump
|
||||
//! request.
|
||||
WinVMAddress crash_exception_information;
|
||||
|
||||
//! \brief The address, in the client process address space, of an
|
||||
//! ExceptionInformation structure, used when handling a non-crashing dump
|
||||
//! request.
|
||||
WinVMAddress non_crash_exception_information;
|
||||
};
|
||||
|
||||
//! \brief A message only sent to the server by itself to trigger shutdown.
|
||||
@ -88,7 +94,17 @@ struct RegistrationResponse {
|
||||
//! \brief An event `HANDLE`, valid in the client process, that should be
|
||||
//! signaled to request a crash report. 64-bit clients should convert the
|
||||
//! value to a `HANDLE` using sign-extension.
|
||||
uint32_t request_report_event;
|
||||
uint32_t request_crash_dump_event;
|
||||
|
||||
//! \brief An event `HANDLE`, valid in the client process, that should be
|
||||
//! signaled to request a non-crashing dump be taken. 64-bit clients
|
||||
//! should convert the value to `HANDLEEE` using sign-extension.
|
||||
uint32_t request_non_crash_dump_event;
|
||||
|
||||
//! \brief An event `HANDLE`, valid in the client process, that will be
|
||||
//! signaled by the server when the non-crashing dump is complete. 64-bit
|
||||
//! clients should convert the value to `HANDLEEE` using sign-extension.
|
||||
uint32_t non_crash_dump_completed_event;
|
||||
};
|
||||
|
||||
//! \brief The response sent back to the client via SendToCrashHandlerServer().
|
||||
|
Loading…
x
Reference in New Issue
Block a user