| // 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 <stddef.h> |
| #include <wchar.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <string_view> |
| #include <utility> |
| |
| #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<process_types::internal::Traits64>( |
| debug_critical_section_address); |
| } else { |
| InitializePebData<process_types::internal::Traits32>( |
| 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<internal::MemoryMapRegionSnapshotWin>(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<std::string, std::string>& |
| 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<const ThreadSnapshot*> ProcessSnapshotWin::Threads() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const ThreadSnapshot*> threads; |
| for (const auto& thread : threads_) { |
| threads.push_back(thread.get()); |
| } |
| return threads; |
| } |
| |
| std::vector<const ModuleSnapshot*> ProcessSnapshotWin::Modules() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const ModuleSnapshot*> modules; |
| for (const auto& module : modules_) { |
| modules.push_back(module.get()); |
| } |
| return modules; |
| } |
| |
| std::vector<UnloadedModuleSnapshot> ProcessSnapshotWin::UnloadedModules() |
| const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return unloaded_modules_; |
| } |
| |
| const ExceptionSnapshot* ProcessSnapshotWin::Exception() const { |
| return exception_.get(); |
| } |
| |
| std::vector<const MemoryMapRegionSnapshot*> ProcessSnapshotWin::MemoryMap() |
| const { |
| std::vector<const MemoryMapRegionSnapshot*> memory_map; |
| for (const auto& item : memory_map_) { |
| memory_map.push_back(item.get()); |
| } |
| return memory_map; |
| } |
| |
| std::vector<HandleSnapshot> ProcessSnapshotWin::Handles() const { |
| std::vector<HandleSnapshot> 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<const MemorySnapshot*> ProcessSnapshotWin::ExtraMemory() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const MemorySnapshot*> 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<ProcessReaderWin::Thread>& process_reader_threads = |
| process_reader_.Threads(); |
| for (const ProcessReaderWin::Thread& process_reader_thread : |
| process_reader_threads) { |
| auto thread = std::make_unique<internal::ThreadSnapshotWin>(); |
| if (thread->Initialize(&process_reader_, |
| process_reader_thread, |
| budget_remaining_pointer)) { |
| threads_.push_back(std::move(thread)); |
| } |
| } |
| } |
| |
| void ProcessSnapshotWin::InitializeModules() { |
| const std::vector<ProcessInfo::Module>& process_reader_modules = |
| process_reader_.Modules(); |
| for (const ProcessInfo::Module& process_reader_module : |
| process_reader_modules) { |
| auto module = std::make_unique<internal::ModuleSnapshotWin>(); |
| 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<Traits>)) { |
| LOG(ERROR) << "unexpected unloaded module list element size"; |
| return; |
| } |
| |
| const WinVMAddress address_in_target_process = |
| FromPointerCast<WinVMAddress>(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<uint8_t> 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<const RTL_UNLOAD_EVENT_TRACE<Traits>*>(base_address); |
| if (uet.ImageName[0] != 0) { |
| unloaded_modules_.push_back(UnloadedModuleSnapshot( |
| uet.BaseAddress, |
| uet.SizeOfImage, |
| uet.CheckSum, |
| uet.TimeDateStamp, |
| base::WideToUTF8(std::wstring_view( |
| 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 <class Traits> |
| 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<Traits> peb_data; |
| if (!process_reader_.Memory()->Read( |
| peb_address, base::checked_cast<size_t>(peb_size), &peb_data)) { |
| LOG(ERROR) << "ReadMemory PEB"; |
| return; |
| } |
| |
| process_types::PEB_LDR_DATA<Traits> 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<Traits>, InLoadOrderLinks), |
| &extra_memory_); |
| AddMemorySnapshotForLdrLIST_ENTRY( |
| peb_ldr_data.InMemoryOrderModuleList, |
| offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, |
| InMemoryOrderLinks), |
| &extra_memory_); |
| AddMemorySnapshotForLdrLIST_ENTRY( |
| peb_ldr_data.InInitializationOrderModuleList, |
| offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, |
| InInitializationOrderLinks), |
| &extra_memory_); |
| } |
| |
| process_types::RTL_USER_PROCESS_PARAMETERS<Traits> 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<Traits>(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<std::unique_ptr<internal::MemorySnapshotGeneric>>* into) { |
| if (size == 0) |
| return; |
| |
| if (!process_reader_.GetProcessInfo().LoggingRangeIsFullyReadable( |
| CheckedRange<WinVMAddress, WinVMSize>(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<internal::MemorySnapshotGeneric>()); |
| into->back()->Initialize(process_reader_.Memory(), address, size); |
| } |
| |
| template <class Traits> |
| void ProcessSnapshotWin::AddMemorySnapshotForUNICODE_STRING( |
| const process_types::UNICODE_STRING<Traits>& us, |
| std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into) { |
| AddMemorySnapshot(us.Buffer, us.Length, into); |
| } |
| |
| template <class Traits> |
| void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY( |
| const process_types::LIST_ENTRY<Traits>& le, |
| size_t offset_of_member, |
| std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* 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<Traits> 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<Traits>* links = |
| reinterpret_cast<process_types::LIST_ENTRY<Traits>*>( |
| reinterpret_cast<unsigned char*>(&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<unsigned int>(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 <class Traits> |
| void ProcessSnapshotWin::ReadLock( |
| WinVMAddress start, |
| std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* 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<Traits> 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<Traits>), into); |
| |
| constexpr decltype(critical_section.DebugInfo) kInvalid = |
| static_cast<decltype(critical_section.DebugInfo)>(-1); |
| if (critical_section.DebugInfo == kInvalid) |
| return; |
| |
| AddMemorySnapshot(critical_section.DebugInfo, |
| sizeof(process_types::RTL_CRITICAL_SECTION_DEBUG<Traits>), |
| into); |
| } |
| |
| } // namespace crashpad |