win: Implement exception snapshot

Refactor some of the NT internals helpers and cpu_context to share
between the thread and exception snapshot code.

Add test that runs crashing child and validates the exception in the
snapshot.

R=mark@chromium.org, cpu@chromium.org, rsesek@chromium.org
BUG=crashpad:1

Review URL: https://codereview.chromium.org/1126413008 .
This commit is contained in:
Scott Graham 2015-08-18 12:25:19 -07:00
parent 1a770c8237
commit a691448ffb
21 changed files with 882 additions and 104 deletions

View File

@ -56,17 +56,19 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
// contention here is very unlikely, and we'll still have a stack that's
// blocked at this location.
if (base::subtle::Barrier_AtomicIncrement(&g_have_crashed, 1) > 1) {
SleepEx(false, INFINITE);
SleepEx(INFINITE, false);
}
// Otherwise, we're the first thread, so record the exception pointer and
// signal the crash handler.
crashpad::CrashpadInfo::GetCrashpadInfo()->set_thread_id(
GetCurrentThreadId());
crashpad::CrashpadInfo::GetCrashpadInfo()->set_exception_pointers(
exception_pointers);
DWORD rv = SignalObjectAndWait(g_signal_exception,
g_wait_termination,
kMillisecondsUntilTerminate,
FALSE);
false);
if (rv != WAIT_OBJECT_0) {
// Something went wrong. It is likely that a dump has not been created.
if (rv == WAIT_TIMEOUT) {

View File

@ -89,7 +89,13 @@ CrashpadInfo::CrashpadInfo()
crashpad_handler_behavior_(TriState::kUnset),
system_crash_reporter_forwarding_(TriState::kUnset),
padding_0_(0),
simple_annotations_(nullptr) {
simple_annotations_(nullptr)
#if defined(OS_WIN)
,
exception_pointers_(nullptr),
thread_id_(0)
#endif // OS_WIN
{
}
} // namespace crashpad

View File

@ -99,10 +99,15 @@ struct CrashpadInfo {
}
#if defined(OS_WIN)
//! \brief Save an EXCEPTION_POINTERS record for the crash handler.
//! \brief Save the crashing thread ID for the crash handler.
void set_thread_id(DWORD thread_id) { thread_id_ = thread_id; }
DWORD thread_id() const { return thread_id_; }
//! \brief Save an `EXCEPTION_POINTERS` record for the crash handler.
void set_exception_pointers(EXCEPTION_POINTERS* exception_pointers) {
exception_pointers_ = exception_pointers;
}
EXCEPTION_POINTERS* exception_pointers() const { return exception_pointers_; }
#endif // OS_WIN
enum : uint32_t {
@ -130,6 +135,7 @@ struct CrashpadInfo {
#if defined(OS_WIN)
EXCEPTION_POINTERS* exception_pointers_;
DWORD thread_id_;
#endif // OS_WIN
#if defined(__clang__)

View File

@ -410,6 +410,9 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION {
//! the original exception code will appear instead. The exception type as it
//! was received will appear at index 0 of #ExceptionInformation.
//!
//! For Windows minidumps, this will be an \ref EXCEPTION_x "EXCEPTION_*"
//! exception type, such as `EXCEPTION_ACCESS_VIOLATION`.
//!
//! \note This field is named ExceptionCode, but what is known as the
//! “exception code” on Mac OS X/Mach is actually stored in the
//! #ExceptionFlags field of a minidump file.
@ -432,6 +435,10 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION {
//! In all cases for Mac OS X minidumps, the code as it was received by the
//! Mach exception handler will appear at index 1 of #ExceptionInformation.
//!
//! For Windows minidumps, this will either be `0` if the exception is
//! continuable, or `EXCEPTION_NONCONTINUABLE` to indicate a noncontinuable
//! exception.
//!
//! \todo Document the possible values by OS. There may be OS-specific enums
//! in minidump_extensions.h.
uint32_t ExceptionFlags;
@ -461,6 +468,9 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION {
//! exception handler. Unlike #ExceptionCode and #ExceptionFlags, the values
//! received by a Mach exception handler are used directly here even for the
//! `EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD` exception types.
//! For Windows, these are additional arguments (if any) as provided to
//! `RaiseException()`.
uint64_t ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
};

View File

@ -53,6 +53,9 @@ class ExceptionSnapshot {
//! processed as `EXC_CRASH` when generated from another preceding exception:
//! the original exception code will appear instead. The exception type as it
//! was received will appear at index 0 of Codes().
//!
//! For Windows, this will be an \ref EXCEPTION_x "EXCEPTION_*" exception type
//! such as `EXCEPTION_ACCESS_VIOLATION`.
virtual uint32_t Exception() const = 0;
//! \brief Returns the second-level exception code identifying the exception.
@ -69,6 +72,9 @@ class ExceptionSnapshot {
//!
//! In all cases on Mac OS X, the full exception code at index 0 as it was
//! received will appear at index 1 of Codes().
//!
//! On Windows, this will either be `0` if the exception is continuable, or
//! `EXCEPTION_NONCONTINUABLE` to indicate a noncontinuable exception.
virtual uint32_t ExceptionInfo() const = 0;
//! \brief Returns the address that triggered the exception.
@ -92,6 +98,10 @@ class ExceptionSnapshot {
//! For Mac OS X, this will be a vector containing the original exception type
//! and the values of `code[0]` and `code[1]` as received by a Mach exception
//! handler.
//!
//! For Windows, these are additional arguments (if any) as provided to
//! `RaiseException()`. See the documentation for `ExceptionInformation` in
//! `EXCEPTION_RECORD`.
virtual const std::vector<uint64_t>& Codes() const = 0;
};

View File

@ -14,6 +14,7 @@
#include "snapshot/mac/process_snapshot_mac.h"
#include "base/logging.h"
#include "util/misc/tri_state.h"
namespace crashpad {

View File

@ -72,6 +72,7 @@
'mac/system_snapshot_mac.h',
'mac/thread_snapshot_mac.cc',
'mac/thread_snapshot_mac.h',
'memory_snapshot.h',
'minidump/minidump_simple_string_dictionary_reader.cc',
'minidump/minidump_simple_string_dictionary_reader.h',
'minidump/minidump_string_list_reader.cc',
@ -82,11 +83,14 @@
'minidump/module_snapshot_minidump.h',
'minidump/process_snapshot_minidump.cc',
'minidump/process_snapshot_minidump.h',
'memory_snapshot.h',
'module_snapshot.h',
'process_snapshot.h',
'system_snapshot.h',
'thread_snapshot.h',
'win/cpu_context_win.cc',
'win/cpu_context_win.h',
'win/exception_snapshot_win.cc',
'win/exception_snapshot_win.h',
'win/memory_snapshot_win.cc',
'win/memory_snapshot_win.h',
'win/module_snapshot_win.cc',

View File

@ -54,6 +54,7 @@
'snapshot.gyp:crashpad_snapshot',
'../client/client.gyp:crashpad_client',
'../compat/compat.gyp:crashpad_compat',
'../handler/handler.gyp:crashpad_handler',
'../test/test.gyp:crashpad_test',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/gtest/gtest.gyp:gtest_main',
@ -74,6 +75,8 @@
'mac/process_types_test.cc',
'mac/system_snapshot_mac_test.cc',
'minidump/process_snapshot_minidump_test.cc',
'win/cpu_context_win_test.cc',
'win/exception_snapshot_win_test.cc',
'win/pe_image_annotations_reader_test.cc',
'win/process_reader_win_test.cc',
'win/system_snapshot_win_test.cc',

View File

@ -0,0 +1,77 @@
// 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 "snapshot/win/cpu_context_win.h"
#include <string.h>
#include "base/logging.h"
#include "snapshot/cpu_context.h"
namespace crashpad {
#if defined(ARCH_CPU_64_BITS)
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
}
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
out->rax = context.Rax;
out->rbx = context.Rbx;
out->rcx = context.Rcx;
out->rdx = context.Rdx;
out->rdi = context.Rdi;
out->rsi = context.Rsi;
out->rbp = context.Rbp;
out->rsp = context.Rsp;
out->r8 = context.R8;
out->r9 = context.R9;
out->r10 = context.R10;
out->r11 = context.R11;
out->r12 = context.R12;
out->r13 = context.R13;
out->r14 = context.R14;
out->r15 = context.R15;
out->rip = context.Rip;
out->rflags = context.EFlags;
out->cs = context.SegCs;
out->fs = context.SegFs;
out->gs = context.SegGs;
out->dr0 = context.Dr0;
out->dr1 = context.Dr1;
out->dr2 = context.Dr2;
out->dr3 = context.Dr3;
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
// http://en.wikipedia.org/wiki/X86_debug_register.
out->dr4 = context.Dr6;
out->dr5 = context.Dr7;
out->dr6 = context.Dr6;
out->dr7 = context.Dr7;
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
"types must be equivalent");
memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave));
}
#else // ARCH_CPU_64_BITS
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) {
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
}
#endif // ARCH_CPU_64_BITS
} // namespace crashpad

View File

@ -0,0 +1,47 @@
// 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_SNAPSHOT_WIN_CPU_CONTEXT_WIN_H_
#define CRASHPAD_SNAPSHOT_WIN_CPU_CONTEXT_WIN_H_
#include <windows.h>
#include "build/build_config.h"
namespace crashpad {
struct CPUContextX86;
struct CPUContextX86_64;
#if defined(ARCH_CPU_64_BITS) || DOXYGEN
//! \brief Initializes a CPUContextX86 structure from a native context structure
//! on Windows.
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out);
//! \brief Initializes a CPUContextX86_64 structure from a native context
//! structure on Windows.
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out);
#else // ARCH_CPU_64_BITS
//! \brief Initializes a CPUContextX86 structure from a native context structure
//! on Windows.
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out);
#endif // ARCH_CPU_64_BITS
} // namespace crashpad
#endif // CRASHPAD_SNAPSHOT_WIN_CPU_CONTEXT_WIN_H_

View File

@ -0,0 +1,54 @@
// Copyright 2014 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 "snapshot/win/cpu_context_win.h"
#include <windows.h>
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "snapshot/cpu_context.h"
namespace crashpad {
namespace test {
namespace {
#if defined(ARCH_CPU_X86_64)
TEST(CPUContextWin, InitializeX64Context) {
CONTEXT context;
context.Rax = 10;
context.FltSave.TagWord = 11;
context.Dr0 = 12;
// Test the simple case, where everything in the CPUContextX86_64 argument is
// set directly from the supplied thread, float, and debug state parameters.
{
CPUContextX86_64 cpu_context_x86_64 = {};
InitializeX64Context(context, &cpu_context_x86_64);
EXPECT_EQ(10u, cpu_context_x86_64.rax);
EXPECT_EQ(11u, cpu_context_x86_64.fxsave.ftw);
EXPECT_EQ(12u, cpu_context_x86_64.dr0);
}
}
#else // ARCH_CPU_X86_64
#error ARCH_CPU_X86
#endif // ARCH_CPU_X86_64
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,144 @@
// 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 "snapshot/win/exception_snapshot_win.h"
#include "snapshot/win/cpu_context_win.h"
#include "snapshot/win/process_reader_win.h"
#include "util/win/nt_internals.h"
#include "util/win/process_structs.h"
namespace crashpad {
namespace internal {
ExceptionSnapshotWin::ExceptionSnapshotWin()
: ExceptionSnapshot(),
context_union_(),
context_(),
codes_(),
thread_id_(0),
exception_address_(0),
exception_flags_(0),
exception_code_(0),
initialized_() {
}
ExceptionSnapshotWin::~ExceptionSnapshotWin() {
}
bool ExceptionSnapshotWin::Initialize(ProcessReaderWin* process_reader,
DWORD thread_id,
WinVMAddress exception_pointers_address) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
bool found_thread = false;
for (const auto& loop_thread : process_reader->Threads()) {
if (thread_id == loop_thread.id) {
found_thread = true;
break;
}
}
if (!found_thread) {
LOG(ERROR) << "thread ID " << thread_id << "not found in process";
return false;
} else {
thread_id_ = thread_id;
}
EXCEPTION_POINTERS exception_pointers;
if (!process_reader->ReadMemory(exception_pointers_address,
sizeof(EXCEPTION_POINTERS),
&exception_pointers)) {
LOG(ERROR) << "EXCEPTION_POINTERS read failed";
return false;
}
if (!exception_pointers.ExceptionRecord) {
LOG(ERROR) << "null ExceptionRecord";
return false;
}
if (process_reader->Is64Bit()) {
EXCEPTION_RECORD64 first_record;
if (!process_reader->ReadMemory(
reinterpret_cast<WinVMAddress>(exception_pointers.ExceptionRecord),
sizeof(first_record),
&first_record)) {
LOG(ERROR) << "ExceptionRecord";
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://code.google.com/p/crashpad/issues/detail?id=43
LOG(WARNING) << "dropping chained ExceptionRecord";
}
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
// We assume 64-on-64 here in that we're relying on the CONTEXT definition
// to be the x64 one.
CONTEXT context_record;
if (!process_reader->ReadMemory(
reinterpret_cast<WinVMAddress>(exception_pointers.ContextRecord),
sizeof(context_record),
&context_record)) {
LOG(ERROR) << "ContextRecord";
return false;
}
InitializeX64Context(context_record, context_.x86_64);
} else {
CHECK(false) << "TODO(scottmg) x86";
return false;
}
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
const CPUContext* ExceptionSnapshotWin::Context() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return &context_;
}
uint64_t ExceptionSnapshotWin::ThreadID() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return thread_id_;
}
uint32_t ExceptionSnapshotWin::Exception() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return exception_code_;
}
uint32_t ExceptionSnapshotWin::ExceptionInfo() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return exception_flags_;
}
uint64_t ExceptionSnapshotWin::ExceptionAddress() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return exception_address_;
}
const std::vector<uint64_t>& ExceptionSnapshotWin::Codes() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return codes_;
}
} // namespace internal
} // namespace crashpad

View File

@ -0,0 +1,84 @@
// 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_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_
#define CRASHPAD_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_
#include <stdint.h>
#include <windows.h>
#include "base/basictypes.h"
#include "build/build_config.h"
#include "snapshot/cpu_context.h"
#include "snapshot/exception_snapshot.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/win/address_types.h"
namespace crashpad {
class ProcessReaderWin;
namespace internal {
class ExceptionSnapshotWin final : public ExceptionSnapshot {
public:
ExceptionSnapshotWin();
~ExceptionSnapshotWin() override;
//! \brief Initializes the object.
//!
//! \param[in] process_reader A ProcessReader for the process that sustained
//! the exception.
//! \param[in] thread_id The thread ID in which the exception occurred.
//! \param[in] exception_pointers_address The address of an
//! `EXCEPTION_POINTERS` record in the target process, passed through from
//! the exception handler.
//!
//! \return `true` if the snapshot could be created, `false` otherwise with
//! an appropriate message logged.
bool Initialize(ProcessReaderWin* process_reader,
DWORD thread_id,
WinVMAddress exception_pointers);
// ExceptionSnapshot:
const CPUContext* Context() const override;
uint64_t ThreadID() const override;
uint32_t Exception() const override;
uint32_t ExceptionInfo() const override;
uint64_t ExceptionAddress() const override;
const std::vector<uint64_t>& Codes() const override;
private:
#if defined(ARCH_CPU_X86_FAMILY)
union {
CPUContextX86 x86;
CPUContextX86_64 x86_64;
} context_union_;
#endif
CPUContext context_;
std::vector<uint64_t> codes_;
uint64_t thread_id_;
uint64_t exception_address_;
uint32_t exception_flags_;
DWORD exception_code_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotWin);
};
} // namespace internal
} // namespace crashpad
#endif // CRASHPAD_SNAPSHOT_WIN_EXCEPTION_SNAPSHOT_WIN_H_

View File

@ -0,0 +1,243 @@
// 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 "snapshot/win/exception_snapshot_win.h"
#include <string>
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "client/crashpad_client.h"
#include "client/crashpad_info.h"
#include "handler/win/registration_server.h"
#include "gtest/gtest.h"
#include "snapshot/win/process_reader_win.h"
#include "snapshot/win/process_snapshot_win.h"
#include "test/win/win_child_process.h"
#include "util/thread/thread.h"
#include "util/win/scoped_handle.h"
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 ExceptionSnapshotWinTest : public testing::Test {
public:
class Delegate : public RegistrationServer::Delegate {
public:
Delegate()
: crashpad_info_address_(0),
client_process_(),
started_event_(CreateEvent(nullptr, false, false, nullptr)),
request_dump_event_(CreateEvent(nullptr, false, false, nullptr)),
dump_complete_event_(CreateEvent(nullptr, true, false, nullptr)) {
EXPECT_TRUE(started_event_.is_valid());
EXPECT_TRUE(request_dump_event_.is_valid());
EXPECT_TRUE(dump_complete_event_.is_valid());
}
~Delegate() override {
}
void OnStarted() override {
EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(started_event_.get(), 0));
SetEvent(started_event_.get());
}
bool RegisterClient(ScopedKernelHANDLE client_process,
WinVMAddress crashpad_info_address,
HANDLE* request_dump_event,
HANDLE* dump_complete_event) override {
client_process_ = client_process.Pass();
crashpad_info_address_ = crashpad_info_address;
*request_dump_event =
DuplicateEvent(client_process_.get(), request_dump_event_.get());
*dump_complete_event =
DuplicateEvent(client_process_.get(), dump_complete_event_.get());
return true;
}
void WaitForStart() {
DWORD wait_result = WaitForSingleObject(started_event_.get(), INFINITE);
if (wait_result == WAIT_FAILED)
PLOG(ERROR);
ASSERT_EQ(wait_result, WAIT_OBJECT_0);
}
void WaitForDumpRequestAndValidateException(void* break_near) {
// Wait until triggered, and then grab information from the child.
WaitForSingleObject(request_dump_event_.get(), INFINITE);
// Snapshot the process and exception.
ProcessReaderWin process_reader;
ASSERT_TRUE(process_reader.Initialize(client_process_.get()));
CrashpadInfo crashpad_info;
ASSERT_TRUE(process_reader.ReadMemory(
crashpad_info_address_, sizeof(crashpad_info), &crashpad_info));
ProcessSnapshotWin snapshot;
snapshot.Initialize(client_process_.get());
snapshot.InitializeException(
crashpad_info.thread_id(),
reinterpret_cast<WinVMAddress>(crashpad_info.exception_pointers()));
// 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(),
reinterpret_cast<uint64_t>(break_near));
EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
reinterpret_cast<uint64_t>(break_near) + kAllowedOffset);
// Notify the child that we're done.
SetEvent(dump_complete_event_.get());
}
ScopedKernelHANDLE* request_dump_event() { return &request_dump_event_; }
ScopedKernelHANDLE* dump_complete_event() { return &dump_complete_event_; }
private:
WinVMAddress crashpad_info_address_;
ScopedKernelHANDLE client_process_;
ScopedKernelHANDLE started_event_;
ScopedKernelHANDLE request_dump_event_;
ScopedKernelHANDLE dump_complete_event_;
};
};
// Runs the RegistrationServer on a background thread.
class RunServerThread : public Thread {
public:
// Instantiates a thread which will invoke server->Run(pipe_name, delegate).
RunServerThread(RegistrationServer* server,
const base::string16& pipe_name,
RegistrationServer::Delegate* delegate)
: server_(server), pipe_name_(pipe_name), delegate_(delegate) {}
~RunServerThread() override {}
private:
// Thread:
void ThreadMain() override { server_->Run(pipe_name_, delegate_); }
RegistrationServer* server_;
base::string16 pipe_name_;
RegistrationServer::Delegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(RunServerThread);
};
// During destruction, ensures that the server is stopped and the background
// thread joined.
class ScopedStopServerAndJoinThread {
public:
explicit ScopedStopServerAndJoinThread(RegistrationServer* server,
Thread* thread)
: server_(server), thread_(thread) {}
~ScopedStopServerAndJoinThread() {
server_->Stop();
thread_->Join();
}
private:
RegistrationServer* server_;
Thread* thread_;
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
};
std::string ReadString(FileHandle handle) {
size_t length = 0;
EXPECT_TRUE(LoggingReadFile(handle, &length, sizeof(length)));
scoped_ptr<char[]> buffer(new char[length]);
EXPECT_TRUE(LoggingReadFile(handle, &buffer[0], length));
return std::string(&buffer[0], length);
}
void WriteString(FileHandle handle, const std::string& str) {
size_t length = str.size();
EXPECT_TRUE(LoggingWriteFile(handle, &length, sizeof(length)));
EXPECT_TRUE(LoggingWriteFile(handle, &str[0], length));
}
__declspec(noinline) void* CurrentAddress() {
return _ReturnAddress();
}
class CrashingChildProcess final : public WinChildProcess {
public:
CrashingChildProcess() : WinChildProcess() {}
~CrashingChildProcess() {}
private:
int Run() override {
std::string pipe_name = ReadString(ReadPipeHandle());
CrashpadClient client;
EXPECT_TRUE(client.SetHandler(pipe_name));
EXPECT_TRUE(client.UseHandler());
// Save the address where we're about to crash so the exception handler can
// verify it's in approximately the right location (with a bit of fudge for
// the code between here and the __debugbreak()).
void* break_address = CurrentAddress();
LoggingWriteFile(WritePipeHandle(), &break_address, sizeof(break_address));
__debugbreak();
return 0;
};
};
TEST_F(ExceptionSnapshotWinTest, ChildCrash) {
// Set up the registration server on a background thread.
RegistrationServer server;
std::string pipe_name = "\\\\.\\pipe\\handler_test_pipe_" +
base::StringPrintf("%08x", GetCurrentProcessId());
base::string16 pipe_name_16 = base::UTF8ToUTF16(pipe_name);
Delegate delegate;
RunServerThread server_thread(&server, pipe_name_16, &delegate);
server_thread.Start();
ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread(
&server, &server_thread);
ASSERT_NO_FATAL_FAILURE(delegate.WaitForStart());
// Spawn a child process that immediately crashes.
WinChildProcess::EntryPoint<CrashingChildProcess>();
scoped_ptr<WinChildProcess::Handles> handle = WinChildProcess::Launch();
WriteString(handle->write.get(), pipe_name);
void* break_near_address;
LoggingReadFile(
handle->read.get(), &break_near_address, sizeof(break_near_address));
// Verify the exception information is as expected.
delegate.WaitForDumpRequestAndValidateException(break_near_address);
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -18,6 +18,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "util/win/nt_internals.h"
#include "util/win/process_structs.h"
#include "util/win/scoped_handle.h"
#include "util/win/time.h"
@ -26,49 +27,6 @@ namespace crashpad {
namespace {
NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS system_information_class,
PVOID system_information,
ULONG system_information_length,
PULONG return_length) {
static decltype(::NtQuerySystemInformation)* nt_query_system_information =
reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtQuerySystemInformation"));
DCHECK(nt_query_system_information);
return nt_query_system_information(system_information_class,
system_information,
system_information_length,
return_length);
}
// The 4th argument is CLIENT_ID*, but as we can't typedef that, we simply cast
// to void* here.
typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
const void* ClientId);
template <class Traits>
NTSTATUS NtOpenThread(PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
const process_types::CLIENT_ID<Traits>* client_id) {
static NtOpenThreadFunction nt_open_thread =
reinterpret_cast<NtOpenThreadFunction>(
GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread"));
DCHECK(nt_open_thread);
return nt_open_thread(thread_handle,
desired_access,
object_attributes,
static_cast<const void*>(client_id));
}
// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of
// ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
// Gets a pointer to the process information structure after a given one, or
// null when iteration is complete, assuming they've been retrieved in a block
// via NtQuerySystemInformation().

View File

@ -14,6 +14,7 @@
#include "snapshot/win/process_snapshot_win.h"
#include "base/logging.h"
#include "snapshot/win/module_snapshot_win.h"
#include "util/win/time.h"
@ -24,7 +25,7 @@ ProcessSnapshotWin::ProcessSnapshotWin()
system_(),
threads_(),
modules_(),
// TODO(scottmg): exception_(),
exception_(),
process_reader_(),
report_id_(),
client_id_(),
@ -53,6 +54,22 @@ bool ProcessSnapshotWin::Initialize(HANDLE process) {
return true;
}
bool ProcessSnapshotWin::InitializeException(
DWORD thread_id,
WinVMAddress exception_pointers) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
DCHECK(!exception_);
exception_.reset(new internal::ExceptionSnapshotWin());
if (!exception_->Initialize(
&process_reader_, thread_id, exception_pointers)) {
exception_.reset();
return false;
}
return true;
}
void ProcessSnapshotWin::GetCrashpadOptions(
CrashpadInfoClientOptions* options) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
@ -149,8 +166,7 @@ std::vector<const ModuleSnapshot*> ProcessSnapshotWin::Modules() const {
}
const ExceptionSnapshot* ProcessSnapshotWin::Exception() const {
CHECK(false) << "TODO(scottmg): Exception()";
return nullptr;
return exception_.get();
}
void ProcessSnapshotWin::InitializeThreads() {

View File

@ -31,11 +31,13 @@
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "snapshot/win/exception_snapshot_win.h"
#include "snapshot/win/module_snapshot_win.h"
#include "snapshot/win/system_snapshot_win.h"
#include "snapshot/win/thread_snapshot_win.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/misc/uuid.h"
#include "util/win/address_types.h"
#include "util/stdlib/pointer_container.h"
namespace crashpad {
@ -55,6 +57,20 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
//! an appropriate message logged.
bool Initialize(HANDLE process);
//! \brief Initializes the object's exception.
//!
//! This populates the data to be returned by Exception(). The parameters may
//! be passed directly through from a Windows exception handler.
//!
//! This method must not be called until after a successful call to
//! Initialize().
//!
//! \return `true` if the exception information could be initialized, `false`
//! otherwise with an appropriate message logged. When this method returns
//! `false`, the ProcessSnapshotWin object's validity remains unchanged.
bool InitializeException(DWORD thread_id,
WinVMAddress exception_pointers);
//! \brief Sets the value to be returned by ReportID().
//!
//! The crash report ID is under the control of the snapshot producer, which
@ -113,7 +129,7 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
internal::SystemSnapshotWin system_;
PointerVector<internal::ThreadSnapshotWin> threads_;
PointerVector<internal::ModuleSnapshotWin> modules_;
// TODO(scottmg): scoped_ptr<internal::ExceptionSnapshotWin> exception_;
scoped_ptr<internal::ExceptionSnapshotWin> exception_;
ProcessReaderWin process_reader_;
UUID report_id_;
UUID client_id_;

View File

@ -15,60 +15,12 @@
#include "snapshot/win/thread_snapshot_win.h"
#include "base/logging.h"
#include "snapshot/win/cpu_context_win.h"
#include "snapshot/win/process_reader_win.h"
namespace crashpad {
namespace internal {
namespace {
void InitializeX64Context(const CONTEXT& context,
CPUContextX86_64* out) {
out->rax = context.Rax;
out->rbx = context.Rbx;
out->rcx = context.Rcx;
out->rdx = context.Rdx;
out->rdi = context.Rdi;
out->rsi = context.Rsi;
out->rbp = context.Rbp;
out->rsp = context.Rsp;
out->r8 = context.R8;
out->r9 = context.R9;
out->r10 = context.R10;
out->r11 = context.R11;
out->r12 = context.R12;
out->r13 = context.R13;
out->r14 = context.R14;
out->r15 = context.R15;
out->rip = context.Rip;
out->rflags = context.EFlags;
out->cs = context.SegCs;
out->fs = context.SegFs;
out->gs = context.SegGs;
out->dr0 = context.Dr0;
out->dr1 = context.Dr1;
out->dr2 = context.Dr2;
out->dr3 = context.Dr3;
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
// http://en.wikipedia.org/wiki/X86_debug_register.
out->dr4 = context.Dr6;
out->dr5 = context.Dr7;
out->dr6 = context.Dr6;
out->dr7 = context.Dr7;
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
"types must be equivalent");
memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave));
}
void InitializeX86Context(const CONTEXT& context,
CPUContextX86* out) {
CHECK(false) << "TODO(scottmg) InitializeX86Context()";
}
} // namespace
ThreadSnapshotWin::ThreadSnapshotWin()
: ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() {
}
@ -85,7 +37,7 @@ bool ThreadSnapshotWin::Initialize(
stack_.Initialize(
process_reader, thread_.stack_region_address, thread_.stack_region_size);
#if defined(ARCH_CPU_X86_FAMILY)
#if defined(ARCH_CPU_X86_64)
if (process_reader->Is64Bit()) {
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
@ -93,9 +45,13 @@ bool ThreadSnapshotWin::Initialize(
} else {
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(process_reader_thread.context, context_.x86);
InitializeX86Context(
*reinterpret_cast<const WOW64_CONTEXT*>(&process_reader_thread.context),
context_.x86);
}
#endif
#else
#error ARCH_CPU_X86
#endif // ARCH_CPU_X86_64
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;

View File

@ -149,6 +149,8 @@
'win/checked_win_address_range.h',
'win/module_version.cc',
'win/module_version.h',
'win/nt_internals.cc',
'win/nt_internals.h',
'win/process_info.cc',
'win/process_info.h',
'win/process_structs.h',

91
util/win/nt_internals.cc Normal file
View File

@ -0,0 +1,91 @@
// 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 "util/win/nt_internals.h"
#include "base/logging.h"
namespace crashpad {
NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS system_information_class,
PVOID system_information,
ULONG system_information_length,
PULONG return_length) {
static decltype(::NtQuerySystemInformation)* nt_query_system_information =
reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtQuerySystemInformation"));
DCHECK(nt_query_system_information);
return nt_query_system_information(system_information_class,
system_information,
system_information_length,
return_length);
}
NTSTATUS NtQueryInformationThread(HANDLE thread_handle,
THREADINFOCLASS thread_information_class,
PVOID thread_information,
ULONG thread_information_length,
PULONG return_length) {
static decltype(::NtQueryInformationThread)* nt_query_information_thread =
reinterpret_cast<decltype(::NtQueryInformationThread)*>(GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtQueryInformationThread"));
DCHECK(nt_query_information_thread);
return nt_query_information_thread(thread_handle,
thread_information_class,
thread_information,
thread_information_length,
return_length);
}
// The 4th argument is CLIENT_ID*, but as we can't typedef that, we simply cast
// to void* here.
typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
const void* ClientId);
template <class Traits>
NTSTATUS NtOpenThread(PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
const process_types::CLIENT_ID<Traits>* client_id) {
static NtOpenThreadFunction nt_open_thread =
reinterpret_cast<NtOpenThreadFunction>(
GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread"));
DCHECK(nt_open_thread);
return nt_open_thread(thread_handle,
desired_access,
object_attributes,
static_cast<const void*>(client_id));
}
// Explicit instantiations with the only 2 valid template arguments to avoid
// putting the body of the function in the header.
template NTSTATUS NtOpenThread<process_types::internal::Traits32>(
PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
const process_types::CLIENT_ID<process_types::internal::Traits32>*
client_id);
template NTSTATUS NtOpenThread<process_types::internal::Traits64>(
PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
const process_types::CLIENT_ID<process_types::internal::Traits64>*
client_id);
} // namespace crashpad

48
util/win/nt_internals.h Normal file
View File

@ -0,0 +1,48 @@
// 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 <winternl.h>
#include "util/win/process_structs.h"
namespace crashpad {
// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of
// ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
// winternal.h defines THREADINFOCLASS, but not all members.
enum { ThreadBasicInformation = 0 };
NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS system_information_class,
PVOID system_information,
ULONG system_information_length,
PULONG return_length);
NTSTATUS NtQueryInformationThread(HANDLE thread_handle,
THREADINFOCLASS thread_information_class,
PVOID thread_information,
ULONG thread_information_length,
PULONG return_length);
template <class Traits>
NTSTATUS NtOpenThread(PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
const process_types::CLIENT_ID<Traits>* client_id);
} // namespace crashpad