diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index 3e109005..3842e71c 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -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) { diff --git a/client/crashpad_info.cc b/client/crashpad_info.cc index b1b1f216..977ea92d 100644 --- a/client/crashpad_info.cc +++ b/client/crashpad_info.cc @@ -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 diff --git a/client/crashpad_info.h b/client/crashpad_info.h index 57ea4eb3..d9d239c4 100644 --- a/client/crashpad_info.h +++ b/client/crashpad_info.h @@ -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__) diff --git a/compat/non_win/dbghelp.h b/compat/non_win/dbghelp.h index 3be6e5af..64d599d4 100644 --- a/compat/non_win/dbghelp.h +++ b/compat/non_win/dbghelp.h @@ -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]; }; diff --git a/snapshot/exception_snapshot.h b/snapshot/exception_snapshot.h index f5ffd06a..bd8ad8a6 100644 --- a/snapshot/exception_snapshot.h +++ b/snapshot/exception_snapshot.h @@ -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& Codes() const = 0; }; diff --git a/snapshot/mac/process_snapshot_mac.cc b/snapshot/mac/process_snapshot_mac.cc index b8c0b531..8bbacded 100644 --- a/snapshot/mac/process_snapshot_mac.cc +++ b/snapshot/mac/process_snapshot_mac.cc @@ -14,6 +14,7 @@ #include "snapshot/mac/process_snapshot_mac.h" +#include "base/logging.h" #include "util/misc/tri_state.h" namespace crashpad { diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index 23d412f9..6ad9f189 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -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', diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp index 9b94a106..a49ee96e 100644 --- a/snapshot/snapshot_test.gyp +++ b/snapshot/snapshot_test.gyp @@ -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', diff --git a/snapshot/win/cpu_context_win.cc b/snapshot/win/cpu_context_win.cc new file mode 100644 index 00000000..a77ea247 --- /dev/null +++ b/snapshot/win/cpu_context_win.cc @@ -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 + +#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 diff --git a/snapshot/win/cpu_context_win.h b/snapshot/win/cpu_context_win.h new file mode 100644 index 00000000..a384ccb3 --- /dev/null +++ b/snapshot/win/cpu_context_win.h @@ -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 + +#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_ diff --git a/snapshot/win/cpu_context_win_test.cc b/snapshot/win/cpu_context_win_test.cc new file mode 100644 index 00000000..99d7c928 --- /dev/null +++ b/snapshot/win/cpu_context_win_test.cc @@ -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 + +#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 diff --git a/snapshot/win/exception_snapshot_win.cc b/snapshot/win/exception_snapshot_win.cc new file mode 100644 index 00000000..a32c9294 --- /dev/null +++ b/snapshot/win/exception_snapshot_win.cc @@ -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(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(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& ExceptionSnapshotWin::Codes() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return codes_; +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/win/exception_snapshot_win.h b/snapshot/win/exception_snapshot_win.h new file mode 100644 index 00000000..588c4ac9 --- /dev/null +++ b/snapshot/win/exception_snapshot_win.h @@ -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 +#include + +#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& Codes() const override; + + private: +#if defined(ARCH_CPU_X86_FAMILY) + union { + CPUContextX86 x86; + CPUContextX86_64 x86_64; + } context_union_; +#endif + CPUContext context_; + std::vector 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_ diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc new file mode 100644 index 00000000..268d64e4 --- /dev/null +++ b/snapshot/win/exception_snapshot_win_test.cc @@ -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 + +#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(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(break_near)); + EXPECT_LT(snapshot.Exception()->ExceptionAddress(), + reinterpret_cast(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 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(); + scoped_ptr 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 diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index 09e04594..67e59e01 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -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(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 -NTSTATUS NtOpenThread(PHANDLE thread_handle, - ACCESS_MASK desired_access, - POBJECT_ATTRIBUTES object_attributes, - const process_types::CLIENT_ID* client_id) { - static NtOpenThreadFunction nt_open_thread = - reinterpret_cast( - GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread")); - DCHECK(nt_open_thread); - return nt_open_thread(thread_handle, - desired_access, - object_attributes, - static_cast(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(). diff --git a/snapshot/win/process_snapshot_win.cc b/snapshot/win/process_snapshot_win.cc index b25e8caa..cb0500ab 100644 --- a/snapshot/win/process_snapshot_win.cc +++ b/snapshot/win/process_snapshot_win.cc @@ -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 ProcessSnapshotWin::Modules() const { } const ExceptionSnapshot* ProcessSnapshotWin::Exception() const { - CHECK(false) << "TODO(scottmg): Exception()"; - return nullptr; + return exception_.get(); } void ProcessSnapshotWin::InitializeThreads() { diff --git a/snapshot/win/process_snapshot_win.h b/snapshot/win/process_snapshot_win.h index 97bb1c69..9e668ef9 100644 --- a/snapshot/win/process_snapshot_win.h +++ b/snapshot/win/process_snapshot_win.h @@ -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 threads_; PointerVector modules_; - // TODO(scottmg): scoped_ptr exception_; + scoped_ptr exception_; ProcessReaderWin process_reader_; UUID report_id_; UUID client_id_; diff --git a/snapshot/win/thread_snapshot_win.cc b/snapshot/win/thread_snapshot_win.cc index 83ad54ef..1026cfb9 100644 --- a/snapshot/win/thread_snapshot_win.cc +++ b/snapshot/win/thread_snapshot_win.cc @@ -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(&process_reader_thread.context), + context_.x86); } -#endif +#else +#error ARCH_CPU_X86 +#endif // ARCH_CPU_X86_64 INITIALIZATION_STATE_SET_VALID(initialized_); return true; diff --git a/util/util.gyp b/util/util.gyp index b93e1667..df377592 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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', diff --git a/util/win/nt_internals.cc b/util/win/nt_internals.cc new file mode 100644 index 00000000..01f889b9 --- /dev/null +++ b/util/win/nt_internals.cc @@ -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(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(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 +NTSTATUS NtOpenThread(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID* client_id) { + static NtOpenThreadFunction nt_open_thread = + reinterpret_cast( + GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread")); + DCHECK(nt_open_thread); + return nt_open_thread(thread_handle, + desired_access, + object_attributes, + static_cast(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( + PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID* + client_id); + +template NTSTATUS NtOpenThread( + PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID* + client_id); + +} // namespace crashpad diff --git a/util/win/nt_internals.h b/util/win/nt_internals.h new file mode 100644 index 00000000..4743c434 --- /dev/null +++ b/util/win/nt_internals.h @@ -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 +#include + +#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 +NTSTATUS NtOpenThread(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID* client_id); + +} // namespace crashpad