| // Copyright 2015 The Crashpad Authors |
| // |
| // 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 <algorithm> |
| |
| #include "base/logging.h" |
| #include "snapshot/capture_memory.h" |
| #include "snapshot/memory_snapshot.h" |
| #include "snapshot/memory_snapshot_generic.h" |
| #include "snapshot/win/capture_memory_delegate_win.h" |
| #include "snapshot/win/cpu_context_win.h" |
| #include "snapshot/win/process_reader_win.h" |
| #include "util/win/exception_codes.h" |
| #include "util/win/nt_internals.h" |
| |
| namespace crashpad { |
| namespace internal { |
| |
| namespace { |
| |
| #if defined(ARCH_CPU_X86_FAMILY) |
| #if defined(ARCH_CPU_32_BITS) |
| using Context32 = CONTEXT; |
| #elif defined(ARCH_CPU_64_BITS) |
| using Context32 = WOW64_CONTEXT; |
| #endif |
| |
| void NativeContextToCPUContext32(const Context32* context_record, |
| CPUContext* context, |
| CPUContextUnion* context_union) { |
| context->architecture = kCPUArchitectureX86; |
| context->x86 = &context_union->x86; |
| InitializeX86Context(context_record, context->x86); |
| } |
| #endif // ARCH_CPU_X86_FAMILY |
| |
| #if defined(ARCH_CPU_64_BITS) |
| void NativeContextToCPUContext64(const CONTEXT* context_record, |
| CPUContext* context, |
| CPUContextUnion* context_union) { |
| #if defined(ARCH_CPU_X86_64) |
| context->architecture = kCPUArchitectureX86_64; |
| context->x86_64 = &context_union->x86_64; |
| // Note that the context here is not extended, even if the flags suggest so, |
| // as we only copied in sizeof(CONTEXT). |
| InitializeX64Context(context_record, context->x86_64); |
| // TODO(1250098) plumb through ssp via message from crashed process. For now |
| // we zero this if CET is available in the capturing process as otherwise |
| // WinDBG will show the relevant thread's ssp for the exception which will |
| // likely be more confusing than showing a zero value. |
| if (IsXStateFeatureEnabled(XSTATE_MASK_CET_U)) { |
| XSAVE_CET_U_FORMAT cet_u_fake; |
| cet_u_fake.Ia32CetUMsr = 0; |
| cet_u_fake.Ia32Pl3SspMsr = 0; |
| InitializeX64XStateCet(context_record, &cet_u_fake, context->x86_64); |
| } |
| #elif defined(ARCH_CPU_ARM64) |
| context->architecture = kCPUArchitectureARM64; |
| context->arm64 = &context_union->arm64; |
| InitializeARM64Context(context_record, context->arm64); |
| #else |
| #error Unsupported Windows 64-bit Arch |
| #endif |
| } |
| #endif |
| |
| } // namespace |
| |
| ExceptionSnapshotWin::ExceptionSnapshotWin() |
| : ExceptionSnapshot(), |
| context_union_(), |
| context_(), |
| codes_(), |
| extra_memory_(), |
| 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, |
| uint32_t* gather_indirectly_referenced_memory_cap) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| |
| const ProcessReaderWin::Thread* thread = nullptr; |
| for (const auto& loop_thread : process_reader->Threads()) { |
| if (thread_id == loop_thread.id) { |
| thread = &loop_thread; |
| break; |
| } |
| } |
| |
| if (!thread) { |
| LOG(ERROR) << "thread ID " << thread_id << " not found in process"; |
| return false; |
| } else { |
| thread_id_ = thread_id; |
| } |
| |
| #if defined(ARCH_CPU_32_BITS) |
| const bool is_64_bit = false; |
| #elif defined(ARCH_CPU_64_BITS) |
| const bool is_64_bit = process_reader->Is64Bit(); |
| if (is_64_bit) { |
| if (!InitializeFromExceptionPointers<EXCEPTION_RECORD64, |
| process_types::EXCEPTION_POINTERS64>( |
| process_reader, |
| exception_pointers_address, |
| thread_id, |
| &NativeContextToCPUContext64)) { |
| return false; |
| } |
| } |
| #endif |
| |
| #if !defined(ARCH_CPU_ARM64) |
| if (!is_64_bit) { |
| if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32, |
| process_types::EXCEPTION_POINTERS32>( |
| process_reader, |
| exception_pointers_address, |
| thread_id, |
| &NativeContextToCPUContext32)) { |
| return false; |
| } |
| } |
| #endif |
| |
| CaptureMemoryDelegateWin capture_memory_delegate( |
| process_reader, |
| *thread, |
| &extra_memory_, |
| gather_indirectly_referenced_memory_cap); |
| CaptureMemory::PointedToByContext(context_, &capture_memory_delegate); |
| |
| 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_; |
| } |
| |
| std::vector<const MemorySnapshot*> ExceptionSnapshotWin::ExtraMemory() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const MemorySnapshot*> result; |
| result.reserve(extra_memory_.size()); |
| for (const auto& em : extra_memory_) { |
| result.push_back(em.get()); |
| } |
| return result; |
| } |
| |
| template <class ExceptionRecordType, |
| class ExceptionPointersType, |
| class ContextType> |
| bool ExceptionSnapshotWin::InitializeFromExceptionPointers( |
| ProcessReaderWin* process_reader, |
| WinVMAddress exception_pointers_address, |
| DWORD exception_thread_id, |
| void (*native_to_cpu_context)(const ContextType* context_record, |
| CPUContext* context, |
| CPUContextUnion* context_union)) { |
| ExceptionPointersType exception_pointers; |
| if (!process_reader->Memory()->Read(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; |
| } |
| |
| ExceptionRecordType first_record; |
| if (!process_reader->Memory()->Read( |
| static_cast<WinVMAddress>(exception_pointers.ExceptionRecord), |
| sizeof(first_record), |
| &first_record)) { |
| LOG(ERROR) << "ExceptionRecord"; |
| return false; |
| } |
| |
| const bool triggered_by_client = |
| first_record.ExceptionCode == ExceptionCodes::kTriggeredExceptionCode && |
| first_record.NumberParameters == 2; |
| if (triggered_by_client) |
| process_reader->DecrementThreadSuspendCounts(exception_thread_id); |
| |
| if (triggered_by_client && first_record.ExceptionInformation[0] != 0) { |
| // This special exception code indicates that the target was crashed by |
| // another client calling CrashpadClient::DumpAndCrashTargetProcess(). In |
| // this case the parameters are a thread id and an exception code which we |
| // use to fabricate a new exception record. |
| using ArgumentType = decltype(first_record.ExceptionInformation[0]); |
| const ArgumentType blame_thread_id = first_record.ExceptionInformation[0]; |
| exception_code_ = static_cast<DWORD>(first_record.ExceptionInformation[1]); |
| exception_flags_ = EXCEPTION_NONCONTINUABLE; |
| for (const auto& thread : process_reader->Threads()) { |
| if (thread.id == blame_thread_id) { |
| thread_id_ = blame_thread_id; |
| native_to_cpu_context(thread.context.context<const ContextType>(), |
| &context_, |
| &context_union_); |
| exception_address_ = context_.InstructionPointer(); |
| break; |
| } |
| } |
| |
| if (exception_address_ == 0) { |
| LOG(WARNING) << "thread " << blame_thread_id << " not found"; |
| return false; |
| } |
| } else { |
| // Normal case. |
| exception_code_ = first_record.ExceptionCode; |
| exception_flags_ = first_record.ExceptionFlags; |
| exception_address_ = first_record.ExceptionAddress; |
| |
| const DWORD number_parameters = std::min<DWORD>( |
| first_record.NumberParameters, EXCEPTION_MAXIMUM_PARAMETERS); |
| for (DWORD i = 0; i < number_parameters; ++i) { |
| codes_.push_back(first_record.ExceptionInformation[i]); |
| } |
| if (first_record.ExceptionRecord) { |
| // https://crashpad.chromium.org/bug/43 |
| LOG(WARNING) << "dropping chained ExceptionRecord"; |
| } |
| |
| ContextType context_record; |
| if (!process_reader->Memory()->Read( |
| static_cast<WinVMAddress>(exception_pointers.ContextRecord), |
| sizeof(context_record), |
| &context_record)) { |
| LOG(ERROR) << "ContextRecord"; |
| return false; |
| } |
| |
| native_to_cpu_context(&context_record, &context_, &context_union_); |
| } |
| |
| return true; |
| } |
| |
| } // namespace internal |
| } // namespace crashpad |