// 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/process_snapshot_win.h" #include #include #include #include #include #include "base/logging.h" #include "base/numerics/safe_conversions.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/time.h" #include "util/win/nt_internals.h" #include "util/win/registration_protocol_win.h" namespace crashpad { ProcessSnapshotWin::ProcessSnapshotWin() : ProcessSnapshot(), system_(), threads_(), modules_(), exception_(), memory_map_(), process_reader_(), report_id_(), client_id_(), annotations_simple_map_(), snapshot_time_(), options_(), initialized_() {} ProcessSnapshotWin::~ProcessSnapshotWin() { } bool ProcessSnapshotWin::Initialize( HANDLE process, ProcessSuspensionState suspension_state, WinVMAddress exception_information_address, WinVMAddress debug_critical_section_address) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); GetTimeOfDay(&snapshot_time_); if (!process_reader_.Initialize(process, suspension_state)) return false; client_id_.InitializeToZero(); system_.Initialize(&process_reader_); if (process_reader_.Is64Bit()) { InitializePebData( debug_critical_section_address); } else { InitializePebData( debug_critical_section_address); } InitializeModules(); InitializeUnloadedModules(); GetCrashpadOptionsInternal(&options_); uint32_t* budget_remaining_pointer = options_.gather_indirectly_referenced_memory == TriState::kEnabled ? &options_.indirectly_referenced_memory_cap : nullptr; if (exception_information_address != 0) { ExceptionInformation exception_information = {}; if (!process_reader_.Memory()->Read(exception_information_address, sizeof(exception_information), &exception_information)) { LOG(WARNING) << "ReadMemory ExceptionInformation failed"; return false; } exception_.reset(new internal::ExceptionSnapshotWin()); if (!exception_->Initialize(&process_reader_, exception_information.thread_id, exception_information.exception_pointers, budget_remaining_pointer)) { exception_.reset(); return false; } } InitializeThreads(budget_remaining_pointer); for (const MEMORY_BASIC_INFORMATION64& mbi : process_reader_.GetProcessInfo().MemoryInfo()) { memory_map_.push_back( std::make_unique(mbi)); } for (const auto& module : modules_) { for (const auto& range : module->ExtraMemoryRanges()) { AddMemorySnapshot(range.base(), range.size(), &extra_memory_); } } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } void ProcessSnapshotWin::GetCrashpadOptions( CrashpadInfoClientOptions* options) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *options = options_; } crashpad::ProcessID ProcessSnapshotWin::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.GetProcessInfo().ProcessID(); } crashpad::ProcessID ProcessSnapshotWin::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.GetProcessInfo().ParentProcessID(); } void ProcessSnapshotWin::SnapshotTime(timeval* snapshot_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *snapshot_time = snapshot_time_; } void ProcessSnapshotWin::ProcessStartTime(timeval* start_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); process_reader_.StartTime(start_time); } void ProcessSnapshotWin::ProcessCPUTimes(timeval* user_time, timeval* system_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); process_reader_.CPUTimes(user_time, system_time); } void ProcessSnapshotWin::ReportID(UUID* report_id) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *report_id = report_id_; } void ProcessSnapshotWin::ClientID(UUID* client_id) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *client_id = client_id_; } const std::map& ProcessSnapshotWin::AnnotationsSimpleMap() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return annotations_simple_map_; } const SystemSnapshot* ProcessSnapshotWin::System() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return &system_; } std::vector ProcessSnapshotWin::Threads() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector threads; for (const auto& thread : threads_) { threads.push_back(thread.get()); } return threads; } std::vector ProcessSnapshotWin::Modules() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector modules; for (const auto& module : modules_) { modules.push_back(module.get()); } return modules; } std::vector ProcessSnapshotWin::UnloadedModules() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return unloaded_modules_; } const ExceptionSnapshot* ProcessSnapshotWin::Exception() const { return exception_.get(); } std::vector ProcessSnapshotWin::MemoryMap() const { std::vector memory_map; for (const auto& item : memory_map_) { memory_map.push_back(item.get()); } return memory_map; } std::vector ProcessSnapshotWin::Handles() const { std::vector result; for (const auto& handle : process_reader_.GetProcessInfo().Handles()) { HandleSnapshot snapshot; // This is probably not strictly correct, but these are not localized so we // expect them all to be in ASCII range anyway. This will need to be more // carefully done if the object name is added. snapshot.type_name = base::WideToUTF8(handle.type_name); snapshot.handle = handle.handle; snapshot.attributes = handle.attributes; snapshot.granted_access = handle.granted_access; snapshot.pointer_count = handle.pointer_count; snapshot.handle_count = handle.handle_count; result.push_back(snapshot); } return result; } std::vector ProcessSnapshotWin::ExtraMemory() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector extra_memory; for (const auto& em : extra_memory_) { extra_memory.push_back(em.get()); } return extra_memory; } const ProcessMemory* ProcessSnapshotWin::Memory() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.Memory(); } void ProcessSnapshotWin::InitializeThreads(uint32_t* budget_remaining_pointer) { const std::vector& process_reader_threads = process_reader_.Threads(); for (const ProcessReaderWin::Thread& process_reader_thread : process_reader_threads) { auto thread = std::make_unique(); if (thread->Initialize(&process_reader_, process_reader_thread, budget_remaining_pointer)) { threads_.push_back(std::move(thread)); } } } void ProcessSnapshotWin::InitializeModules() { const std::vector& process_reader_modules = process_reader_.Modules(); for (const ProcessInfo::Module& process_reader_module : process_reader_modules) { auto module = std::make_unique(); if (module->Initialize(&process_reader_, process_reader_module)) { modules_.push_back(std::move(module)); } } } void ProcessSnapshotWin::InitializeUnloadedModules() { // As documented by https://msdn.microsoft.com/library/cc678403.aspx, we can // retrieve the location for our unload events, and use that address in the // target process. Unfortunately, this of course only works for 64-reading-64 // and 32-reading-32, so at the moment, we simply do not retrieve unloaded // modules for 64-reading-32. See https://crashpad.chromium.org/bug/89. #if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64) if (!process_reader_.Is64Bit()) { LOG(ERROR) << "reading unloaded modules across bitness not currently supported"; return; } using Traits = process_types::internal::Traits64; #elif defined(ARCH_CPU_X86) using Traits = process_types::internal::Traits32; #else #error port #endif ULONG* element_size; ULONG* element_count; void* event_trace_address; RtlGetUnloadEventTraceEx(&element_size, &element_count, &event_trace_address); if (*element_size < sizeof(RTL_UNLOAD_EVENT_TRACE)) { LOG(ERROR) << "unexpected unloaded module list element size"; return; } const WinVMAddress address_in_target_process = FromPointerCast(event_trace_address); Traits::Pointer pointer_to_array; if (!process_reader_.Memory()->Read(address_in_target_process, sizeof(pointer_to_array), &pointer_to_array)) { LOG(ERROR) << "failed to read target address"; return; } // No unloaded modules. if (pointer_to_array == 0) return; const size_t data_size = *element_size * *element_count; std::vector data(data_size); if (!process_reader_.Memory()->Read(pointer_to_array, data_size, &data[0])) { LOG(ERROR) << "failed to read unloaded module data"; return; } for (ULONG i = 0; i < *element_count; ++i) { const uint8_t* base_address = &data[i * *element_size]; const auto& uet = *reinterpret_cast*>(base_address); if (uet.ImageName[0] != 0) { unloaded_modules_.push_back(UnloadedModuleSnapshot( uet.BaseAddress, uet.SizeOfImage, uet.CheckSum, uet.TimeDateStamp, base::WideToUTF8(base::WStringPiece( uet.ImageName, wcsnlen(uet.ImageName, std::size(uet.ImageName)))))); } } } void ProcessSnapshotWin::GetCrashpadOptionsInternal( CrashpadInfoClientOptions* options) { CrashpadInfoClientOptions local_options; for (const auto& module : modules_) { CrashpadInfoClientOptions module_options; module->GetCrashpadOptions(&module_options); if (local_options.crashpad_handler_behavior == TriState::kUnset) { local_options.crashpad_handler_behavior = module_options.crashpad_handler_behavior; } if (local_options.system_crash_reporter_forwarding == TriState::kUnset) { local_options.system_crash_reporter_forwarding = module_options.system_crash_reporter_forwarding; } if (local_options.gather_indirectly_referenced_memory == TriState::kUnset) { local_options.gather_indirectly_referenced_memory = module_options.gather_indirectly_referenced_memory; local_options.indirectly_referenced_memory_cap = module_options.indirectly_referenced_memory_cap; } // If non-default values have been found for all options, the loop can end // early. if (local_options.crashpad_handler_behavior != TriState::kUnset && local_options.system_crash_reporter_forwarding != TriState::kUnset && local_options.gather_indirectly_referenced_memory != TriState::kUnset) { break; } } *options = local_options; } template void ProcessSnapshotWin::InitializePebData( WinVMAddress debug_critical_section_address) { WinVMAddress peb_address; WinVMSize peb_size; process_reader_.GetProcessInfo().Peb(&peb_address, &peb_size); AddMemorySnapshot(peb_address, peb_size, &extra_memory_); process_types::PEB peb_data; if (!process_reader_.Memory()->Read( peb_address, base::checked_cast(peb_size), &peb_data)) { LOG(ERROR) << "ReadMemory PEB"; return; } process_types::PEB_LDR_DATA peb_ldr_data; AddMemorySnapshot(peb_data.Ldr, sizeof(peb_ldr_data), &extra_memory_); if (!process_reader_.Memory()->Read( peb_data.Ldr, sizeof(peb_ldr_data), &peb_ldr_data)) { LOG(ERROR) << "ReadMemory PEB_LDR_DATA"; } else { // Walk the LDR structure to retrieve its pointed-to data. AddMemorySnapshotForLdrLIST_ENTRY( peb_ldr_data.InLoadOrderModuleList, offsetof(process_types::LDR_DATA_TABLE_ENTRY, InLoadOrderLinks), &extra_memory_); AddMemorySnapshotForLdrLIST_ENTRY( peb_ldr_data.InMemoryOrderModuleList, offsetof(process_types::LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks), &extra_memory_); AddMemorySnapshotForLdrLIST_ENTRY( peb_ldr_data.InInitializationOrderModuleList, offsetof(process_types::LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks), &extra_memory_); } process_types::RTL_USER_PROCESS_PARAMETERS process_parameters; if (!process_reader_.Memory()->Read(peb_data.ProcessParameters, sizeof(process_parameters), &process_parameters)) { LOG(ERROR) << "ReadMemory RTL_USER_PROCESS_PARAMETERS"; return; } AddMemorySnapshot( peb_data.ProcessParameters, sizeof(process_parameters), &extra_memory_); AddMemorySnapshotForUNICODE_STRING( process_parameters.CurrentDirectory.DosPath, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.DllPath, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.ImagePathName, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.CommandLine, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.WindowTitle, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.DesktopInfo, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.ShellInfo, &extra_memory_); AddMemorySnapshotForUNICODE_STRING(process_parameters.RuntimeData, &extra_memory_); AddMemorySnapshot( process_parameters.Environment, DetermineSizeOfEnvironmentBlock(process_parameters.Environment), &extra_memory_); // Walk the loader lock which is directly referenced by the PEB. ReadLock(peb_data.LoaderLock, &extra_memory_); // TODO(scottmg): Use debug_critical_section_address to walk the list of // locks (see history of this file for walking code). In some configurations // this can walk many thousands of locks, so we may want to get some // annotation from the client for which locks to grab. Unfortunately, without // walking the list, the !locks command in windbg won't work because it // requires the lock pointed to by ntdll!RtlCriticalSectionList, which we // won't have captured. } void ProcessSnapshotWin::AddMemorySnapshot( WinVMAddress address, WinVMSize size, std::vector>* into) { if (size == 0) return; if (!process_reader_.GetProcessInfo().LoggingRangeIsFullyReadable( CheckedRange(address, size))) { return; } // If we have already added this exact range, don't add it again. This is // useful for the LDR module lists which are a set of doubly-linked lists, all // pointing to the same module name strings. // TODO(scottmg): A more general version of this, handling overlapping, // contained, etc. https://crashpad.chromium.org/bug/61. for (const auto& memory_snapshot : *into) { if (memory_snapshot->Address() == address && memory_snapshot->Size() == size) { return; } } into->push_back(std::make_unique()); into->back()->Initialize(process_reader_.Memory(), address, size); } template void ProcessSnapshotWin::AddMemorySnapshotForUNICODE_STRING( const process_types::UNICODE_STRING& us, std::vector>* into) { AddMemorySnapshot(us.Buffer, us.Length, into); } template void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY( const process_types::LIST_ENTRY& le, size_t offset_of_member, std::vector>* into) { // Walk the doubly-linked list of entries, adding the list memory itself, as // well as pointed-to strings. typename Traits::Pointer last = le.Blink; process_types::LDR_DATA_TABLE_ENTRY entry; typename Traits::Pointer cur = le.Flink; for (;;) { // |cur| is the pointer to LIST_ENTRY embedded in the LDR_DATA_TABLE_ENTRY. // So we need to offset back to the beginning of the structure. if (!process_reader_.Memory()->Read( cur - offset_of_member, sizeof(entry), &entry)) { return; } AddMemorySnapshot(cur - offset_of_member, sizeof(entry), into); AddMemorySnapshotForUNICODE_STRING(entry.FullDllName, into); AddMemorySnapshotForUNICODE_STRING(entry.BaseDllName, into); process_types::LIST_ENTRY* links = reinterpret_cast*>( reinterpret_cast(&entry) + offset_of_member); cur = links->Flink; if (cur == last) break; } } WinVMSize ProcessSnapshotWin::DetermineSizeOfEnvironmentBlock( WinVMAddress start_of_environment_block) { // https://blogs.msdn.microsoft.com/oldnewthing/20100203-00/?p=15083: On newer // OSs there's no stated limit, but in practice grabbing 32k characters should // be more than enough. std::wstring env_block; env_block.resize(32768); size_t bytes_read = process_reader_.Memory()->ReadAvailableMemory( start_of_environment_block, env_block.size() * sizeof(env_block[0]), &env_block[0]); env_block.resize( static_cast(bytes_read / sizeof(env_block[0]))); static constexpr wchar_t terminator[] = {0, 0}; size_t at = env_block.find(std::wstring(terminator, std::size(terminator))); if (at != std::wstring::npos) env_block.resize(at + std::size(terminator)); return env_block.size() * sizeof(env_block[0]); } template void ProcessSnapshotWin::ReadLock( WinVMAddress start, std::vector>* into) { // We're walking the RTL_CRITICAL_SECTION_DEBUG ProcessLocksList, but starting // from an actual RTL_CRITICAL_SECTION, so start by getting to the first // RTL_CRITICAL_SECTION_DEBUG. process_types::RTL_CRITICAL_SECTION critical_section; if (!process_reader_.Memory()->Read( start, sizeof(critical_section), &critical_section)) { LOG(ERROR) << "failed to read RTL_CRITICAL_SECTION"; return; } AddMemorySnapshot( start, sizeof(process_types::RTL_CRITICAL_SECTION), into); constexpr decltype(critical_section.DebugInfo) kInvalid = static_cast(-1); if (critical_section.DebugInfo == kInvalid) return; AddMemorySnapshot(critical_section.DebugInfo, sizeof(process_types::RTL_CRITICAL_SECTION_DEBUG), into); } } // namespace crashpad