| // 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/process_info.h" |
| |
| #include <stddef.h> |
| #include <winternl.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <new> |
| #include <type_traits> |
| |
| #include "base/logging.h" |
| #include "base/memory/free_deleter.h" |
| #include "base/process/memory.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| #include "util/misc/from_pointer_cast.h" |
| #include "util/numeric/safe_assignment.h" |
| #include "util/win/get_function.h" |
| #include "util/win/handle.h" |
| #include "util/win/nt_internals.h" |
| #include "util/win/ntstatus_logging.h" |
| #include "util/win/process_structs.h" |
| #include "util/win/scoped_handle.h" |
| |
| namespace crashpad { |
| |
| namespace { |
| |
| using UniqueMallocPtr = std::unique_ptr<uint8_t[], base::FreeDeleter>; |
| |
| UniqueMallocPtr UncheckedAllocate(size_t size) { |
| void* raw_ptr = nullptr; |
| if (!base::UncheckedMalloc(size, &raw_ptr)) |
| return UniqueMallocPtr(); |
| |
| return UniqueMallocPtr(new (raw_ptr) uint8_t[size]); |
| } |
| |
| NTSTATUS NtQueryInformationProcess(HANDLE process_handle, |
| PROCESSINFOCLASS process_information_class, |
| PVOID process_information, |
| ULONG process_information_length, |
| PULONG return_length) { |
| static const auto nt_query_information_process = |
| GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryInformationProcess); |
| return nt_query_information_process(process_handle, |
| process_information_class, |
| process_information, |
| process_information_length, |
| return_length); |
| } |
| |
| bool IsProcessWow64(HANDLE process_handle) { |
| static const auto is_wow64_process = |
| GET_FUNCTION(L"kernel32.dll", ::IsWow64Process); |
| if (!is_wow64_process) |
| return false; |
| BOOL is_wow64; |
| if (!is_wow64_process(process_handle, &is_wow64)) { |
| PLOG(ERROR) << "IsWow64Process"; |
| return false; |
| } |
| return !!is_wow64; |
| } |
| |
| template <class T> |
| bool ReadUnicodeString(HANDLE process, |
| const process_types::UNICODE_STRING<T>& us, |
| std::wstring* result) { |
| if (us.Length == 0) { |
| result->clear(); |
| return true; |
| } |
| DCHECK_EQ(us.Length % sizeof(wchar_t), 0u); |
| result->resize(us.Length / sizeof(wchar_t)); |
| SIZE_T bytes_read; |
| if (!ReadProcessMemory( |
| process, |
| reinterpret_cast<const void*>(static_cast<uintptr_t>(us.Buffer)), |
| &result->operator[](0), |
| us.Length, |
| &bytes_read)) { |
| PLOG(ERROR) << "ReadProcessMemory UNICODE_STRING"; |
| return false; |
| } |
| if (bytes_read != us.Length) { |
| LOG(ERROR) << "ReadProcessMemory UNICODE_STRING incorrect size"; |
| return false; |
| } |
| return true; |
| } |
| |
| template <class T> |
| bool ReadStruct(HANDLE process, WinVMAddress at, T* into) { |
| SIZE_T bytes_read; |
| if (!ReadProcessMemory(process, |
| reinterpret_cast<const void*>(at), |
| into, |
| sizeof(T), |
| &bytes_read)) { |
| // We don't have a name for the type we're reading, so include the signature |
| // to get the type of T. |
| PLOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__; |
| return false; |
| } |
| if (bytes_read != sizeof(T)) { |
| LOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__ << " incorrect size"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool RegionIsAccessible(const MEMORY_BASIC_INFORMATION64& memory_info) { |
| return memory_info.State == MEM_COMMIT && |
| (memory_info.Protect & PAGE_NOACCESS) == 0 && |
| (memory_info.Protect & PAGE_GUARD) == 0; |
| } |
| |
| MEMORY_BASIC_INFORMATION64 MemoryBasicInformationToMemoryBasicInformation64( |
| const MEMORY_BASIC_INFORMATION& mbi) { |
| MEMORY_BASIC_INFORMATION64 mbi64 = {0}; |
| mbi64.BaseAddress = FromPointerCast<ULONGLONG>(mbi.BaseAddress); |
| mbi64.AllocationBase = reinterpret_cast<ULONGLONG>(mbi.AllocationBase); |
| mbi64.AllocationProtect = mbi.AllocationProtect; |
| mbi64.RegionSize = mbi.RegionSize; |
| mbi64.State = mbi.State; |
| mbi64.Protect = mbi.Protect; |
| mbi64.Type = mbi.Type; |
| return mbi64; |
| } |
| |
| // NtQueryObject with a retry for size mismatch as well as a minimum size to |
| // retrieve (and expect). |
| std::unique_ptr<uint8_t[]> QueryObject( |
| HANDLE handle, |
| OBJECT_INFORMATION_CLASS object_information_class, |
| ULONG minimum_size) { |
| ULONG size = minimum_size; |
| ULONG return_length; |
| std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]); |
| NTSTATUS status = crashpad::NtQueryObject( |
| handle, object_information_class, buffer.get(), size, &return_length); |
| if (status == STATUS_INFO_LENGTH_MISMATCH) { |
| DCHECK_GT(return_length, size); |
| size = return_length; |
| |
| // Free the old buffer before attempting to allocate a new one. |
| buffer.reset(); |
| |
| buffer.reset(new uint8_t[size]); |
| status = crashpad::NtQueryObject( |
| handle, object_information_class, buffer.get(), size, &return_length); |
| } |
| |
| if (!NT_SUCCESS(status)) { |
| NTSTATUS_LOG(ERROR, status) << "NtQueryObject"; |
| return nullptr; |
| } |
| |
| DCHECK_LE(return_length, size); |
| DCHECK_GE(return_length, minimum_size); |
| return buffer; |
| } |
| |
| } // namespace |
| |
| template <class Traits> |
| bool GetProcessBasicInformation(HANDLE process, |
| bool is_wow64, |
| ProcessInfo* process_info, |
| WinVMAddress* peb_address, |
| WinVMSize* peb_size) { |
| ULONG bytes_returned; |
| process_types::PROCESS_BASIC_INFORMATION<Traits> process_basic_information; |
| NTSTATUS status = |
| crashpad::NtQueryInformationProcess(process, |
| ProcessBasicInformation, |
| &process_basic_information, |
| sizeof(process_basic_information), |
| &bytes_returned); |
| if (!NT_SUCCESS(status)) { |
| NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess"; |
| return false; |
| } |
| if (bytes_returned != sizeof(process_basic_information)) { |
| LOG(ERROR) << "NtQueryInformationProcess incorrect size"; |
| return false; |
| } |
| |
| // API functions (e.g. OpenProcess) take only a DWORD, so there's no sense in |
| // maintaining the top bits. |
| process_info->process_id_ = |
| static_cast<DWORD>(process_basic_information.UniqueProcessId); |
| process_info->inherited_from_process_id_ = static_cast<DWORD>( |
| process_basic_information.InheritedFromUniqueProcessId); |
| |
| // We now want to read the PEB to gather the rest of our information. The |
| // PebBaseAddress as returned above is what we want for 64-on-64 and 32-on-32, |
| // but for Wow64, we want to read the 32 bit PEB (a Wow64 process has both). |
| // The address of this is found by a second call to NtQueryInformationProcess. |
| if (!is_wow64) { |
| *peb_address = process_basic_information.PebBaseAddress; |
| *peb_size = sizeof(process_types::PEB<Traits>); |
| } else { |
| ULONG_PTR wow64_peb_address; |
| status = crashpad::NtQueryInformationProcess(process, |
| ProcessWow64Information, |
| &wow64_peb_address, |
| sizeof(wow64_peb_address), |
| &bytes_returned); |
| if (!NT_SUCCESS(status)) { |
| NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess"; |
| return false; |
| } |
| if (bytes_returned != sizeof(wow64_peb_address)) { |
| LOG(ERROR) << "NtQueryInformationProcess incorrect size"; |
| return false; |
| } |
| *peb_address = wow64_peb_address; |
| *peb_size = sizeof(process_types::PEB<process_types::internal::Traits32>); |
| } |
| |
| return true; |
| } |
| |
| template <class Traits> |
| bool ReadProcessData(HANDLE process, |
| WinVMAddress peb_address_vmaddr, |
| ProcessInfo* process_info) { |
| typename Traits::Pointer peb_address; |
| if (!AssignIfInRange(&peb_address, peb_address_vmaddr)) { |
| LOG(ERROR) << base::StringPrintf("peb address 0x%llx out of range", |
| peb_address_vmaddr); |
| return false; |
| } |
| |
| // Try to read the process environment block. |
| process_types::PEB<Traits> peb; |
| if (!ReadStruct(process, peb_address, &peb)) |
| return false; |
| |
| process_types::RTL_USER_PROCESS_PARAMETERS<Traits> process_parameters; |
| if (!ReadStruct(process, peb.ProcessParameters, &process_parameters)) |
| return false; |
| |
| if (!ReadUnicodeString(process, |
| process_parameters.CommandLine, |
| &process_info->command_line_)) { |
| return false; |
| } |
| |
| process_types::PEB_LDR_DATA<Traits> peb_ldr_data; |
| if (!ReadStruct(process, peb.Ldr, &peb_ldr_data)) |
| return false; |
| |
| process_types::LDR_DATA_TABLE_ENTRY<Traits> ldr_data_table_entry; |
| ProcessInfo::Module module; |
| |
| // Walk the PEB LDR structure (doubly-linked list) to get the list of loaded |
| // modules. We use this method rather than EnumProcessModules to get the |
| // modules in load order rather than memory order. Notably, this includes the |
| // main executable as the first element. |
| typename Traits::Pointer last = peb_ldr_data.InLoadOrderModuleList.Blink; |
| for (typename Traits::Pointer cur = peb_ldr_data.InLoadOrderModuleList.Flink;; |
| cur = ldr_data_table_entry.InLoadOrderLinks.Flink) { |
| // |cur| is the pointer to the LIST_ENTRY embedded in the |
| // LDR_DATA_TABLE_ENTRY, in the target process's address space. So we need |
| // to read from the target, and also offset back to the beginning of the |
| // structure. |
| if (!ReadStruct(process, |
| static_cast<WinVMAddress>(cur) - |
| offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, |
| InLoadOrderLinks), |
| &ldr_data_table_entry)) { |
| break; |
| } |
| // TODO(scottmg): Capture Checksum, etc. too? |
| if (!ReadUnicodeString( |
| process, ldr_data_table_entry.FullDllName, &module.name)) { |
| module.name = L"???"; |
| } |
| module.dll_base = ldr_data_table_entry.DllBase; |
| module.size = ldr_data_table_entry.SizeOfImage; |
| module.timestamp = ldr_data_table_entry.TimeDateStamp; |
| process_info->modules_.push_back(module); |
| if (cur == last) |
| break; |
| } |
| |
| return true; |
| } |
| |
| bool ReadMemoryInfo(HANDLE process, bool is_64_bit, ProcessInfo* process_info) { |
| DCHECK(process_info->memory_info_.empty()); |
| |
| constexpr WinVMAddress min_address = 0; |
| // We can't use GetSystemInfo() to get the address space range for another |
| // process. VirtualQueryEx() will fail with ERROR_INVALID_PARAMETER if the |
| // address is above the highest memory address accessible to the process, so |
| // we just probe the entire potential range (2^32 for x86, or 2^64 for x64). |
| const WinVMAddress max_address = is_64_bit |
| ? std::numeric_limits<uint64_t>::max() |
| : std::numeric_limits<uint32_t>::max(); |
| MEMORY_BASIC_INFORMATION memory_basic_information; |
| for (WinVMAddress address = min_address; address <= max_address; |
| address += memory_basic_information.RegionSize) { |
| size_t result = VirtualQueryEx(process, |
| reinterpret_cast<void*>(address), |
| &memory_basic_information, |
| sizeof(memory_basic_information)); |
| if (result == 0) { |
| if (GetLastError() == ERROR_INVALID_PARAMETER) |
| break; |
| PLOG(ERROR) << "VirtualQueryEx"; |
| return false; |
| } |
| |
| process_info->memory_info_.push_back( |
| MemoryBasicInformationToMemoryBasicInformation64( |
| memory_basic_information)); |
| |
| if (memory_basic_information.RegionSize == 0) { |
| LOG(ERROR) << "RegionSize == 0"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| std::vector<ProcessInfo::Handle> ProcessInfo::BuildHandleVector( |
| HANDLE process) const { |
| ULONG buffer_size = 2 * 1024 * 1024; |
| // Typically if the buffer were too small, STATUS_INFO_LENGTH_MISMATCH would |
| // return the correct size in the final argument, but it does not for |
| // SystemExtendedHandleInformation, so we loop and attempt larger sizes. |
| NTSTATUS status; |
| ULONG returned_length; |
| UniqueMallocPtr buffer; |
| for (int tries = 0; tries < 5; ++tries) { |
| buffer.reset(); |
| buffer = UncheckedAllocate(buffer_size); |
| if (!buffer) { |
| LOG(ERROR) << "UncheckedAllocate"; |
| return std::vector<Handle>(); |
| } |
| |
| status = crashpad::NtQuerySystemInformation( |
| static_cast<SYSTEM_INFORMATION_CLASS>(SystemExtendedHandleInformation), |
| buffer.get(), |
| buffer_size, |
| &returned_length); |
| if (NT_SUCCESS(status) || status != STATUS_INFO_LENGTH_MISMATCH) |
| break; |
| |
| buffer_size *= 2; |
| } |
| |
| if (!NT_SUCCESS(status)) { |
| NTSTATUS_LOG(ERROR, status) |
| << "NtQuerySystemInformation SystemExtendedHandleInformation"; |
| return std::vector<Handle>(); |
| } |
| |
| const auto& system_handle_information_ex = |
| *reinterpret_cast<process_types::SYSTEM_HANDLE_INFORMATION_EX*>( |
| buffer.get()); |
| |
| DCHECK_LE(offsetof(process_types::SYSTEM_HANDLE_INFORMATION_EX, Handles) + |
| system_handle_information_ex.NumberOfHandles * |
| sizeof(system_handle_information_ex.Handles[0]), |
| returned_length); |
| |
| std::vector<Handle> handles; |
| |
| for (size_t i = 0; i < system_handle_information_ex.NumberOfHandles; ++i) { |
| const auto& handle = system_handle_information_ex.Handles[i]; |
| if (handle.UniqueProcessId != process_id_) |
| continue; |
| |
| Handle result_handle; |
| result_handle.handle = HandleToInt(handle.HandleValue); |
| result_handle.attributes = handle.HandleAttributes; |
| result_handle.granted_access = handle.GrantedAccess; |
| |
| // TODO(scottmg): Could special case for self. |
| HANDLE dup_handle; |
| if (DuplicateHandle(process, |
| handle.HandleValue, |
| GetCurrentProcess(), |
| &dup_handle, |
| 0, |
| false, |
| DUPLICATE_SAME_ACCESS)) { |
| // Some handles cannot be duplicated, for example, handles of type |
| // EtwRegistration. If we fail to duplicate, then we can't gather any more |
| // information, but include the information that we do have already. |
| ScopedKernelHANDLE scoped_dup_handle(dup_handle); |
| |
| std::unique_ptr<uint8_t[]> object_basic_information_buffer = |
| QueryObject(dup_handle, |
| ObjectBasicInformation, |
| sizeof(PUBLIC_OBJECT_BASIC_INFORMATION)); |
| if (object_basic_information_buffer) { |
| PUBLIC_OBJECT_BASIC_INFORMATION* object_basic_information = |
| reinterpret_cast<PUBLIC_OBJECT_BASIC_INFORMATION*>( |
| object_basic_information_buffer.get()); |
| // The Attributes and GrantedAccess sometimes differ slightly between |
| // the data retrieved in SYSTEM_HANDLE_INFORMATION_EX and |
| // PUBLIC_OBJECT_TYPE_INFORMATION. We prefer the values in |
| // SYSTEM_HANDLE_INFORMATION_EX because they were retrieved from the |
| // target process, rather than on the duplicated handle, so don't use |
| // them here. |
| |
| // Subtract one to account for our DuplicateHandle() and another for |
| // NtQueryObject() while the query was being executed. |
| DCHECK_GT(object_basic_information->PointerCount, 2u); |
| result_handle.pointer_count = |
| object_basic_information->PointerCount - 2; |
| |
| // Subtract one to account for our DuplicateHandle(). |
| DCHECK_GT(object_basic_information->HandleCount, 1u); |
| result_handle.handle_count = object_basic_information->HandleCount - 1; |
| } |
| |
| std::unique_ptr<uint8_t[]> object_type_information_buffer = |
| QueryObject(dup_handle, |
| ObjectTypeInformation, |
| sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); |
| if (object_type_information_buffer) { |
| PUBLIC_OBJECT_TYPE_INFORMATION* object_type_information = |
| reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>( |
| object_type_information_buffer.get()); |
| |
| DCHECK_EQ(object_type_information->TypeName.Length % |
| sizeof(result_handle.type_name[0]), |
| 0u); |
| result_handle.type_name = |
| std::wstring(object_type_information->TypeName.Buffer, |
| object_type_information->TypeName.Length / |
| sizeof(result_handle.type_name[0])); |
| } |
| } |
| |
| handles.push_back(result_handle); |
| } |
| return handles; |
| } |
| |
| ProcessInfo::Module::Module() : name(), dll_base(0), size(0), timestamp() { |
| } |
| |
| ProcessInfo::Module::~Module() { |
| } |
| |
| ProcessInfo::Handle::Handle() |
| : type_name(), |
| handle(0), |
| attributes(0), |
| granted_access(0), |
| pointer_count(0), |
| handle_count(0) { |
| } |
| |
| ProcessInfo::Handle::~Handle() { |
| } |
| |
| ProcessInfo::ProcessInfo() |
| : process_id_(), |
| inherited_from_process_id_(), |
| process_(), |
| command_line_(), |
| peb_address_(0), |
| peb_size_(0), |
| modules_(), |
| memory_info_(), |
| handles_(), |
| is_64_bit_(false), |
| is_wow64_(false), |
| initialized_() { |
| } |
| |
| ProcessInfo::~ProcessInfo() { |
| } |
| |
| bool ProcessInfo::Initialize(HANDLE process) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| |
| process_ = process; |
| |
| is_wow64_ = IsProcessWow64(process); |
| |
| if (is_wow64_) { |
| // If it's WoW64, then it's 32-on-64. |
| is_64_bit_ = false; |
| } else { |
| // Otherwise, it's either 32 on 32, or 64 on 64. Use GetSystemInfo() to |
| // distinguish between these two cases. |
| SYSTEM_INFO system_info; |
| GetSystemInfo(&system_info); |
| |
| #if defined(ARCH_CPU_X86_FAMILY) |
| constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_AMD64; |
| #elif defined(ARCH_CPU_ARM_FAMILY) |
| constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_ARM64; |
| #endif |
| |
| is_64_bit_ = system_info.wProcessorArchitecture == kNative64BitArchitecture; |
| } |
| |
| #if defined(ARCH_CPU_32_BITS) |
| if (is_64_bit_) { |
| LOG(ERROR) << "Reading x64 process from x86 process not supported"; |
| return false; |
| } |
| #endif // ARCH_CPU_32_BITS |
| |
| #if defined(ARCH_CPU_64_BITS) |
| bool result = GetProcessBasicInformation<process_types::internal::Traits64>( |
| process, is_wow64_, this, &peb_address_, &peb_size_); |
| #else |
| bool result = GetProcessBasicInformation<process_types::internal::Traits32>( |
| process, false, this, &peb_address_, &peb_size_); |
| #endif // ARCH_CPU_64_BITS |
| |
| if (!result) { |
| LOG(ERROR) << "GetProcessBasicInformation failed"; |
| return false; |
| } |
| |
| result = is_64_bit_ ? ReadProcessData<process_types::internal::Traits64>( |
| process, peb_address_, this) |
| : ReadProcessData<process_types::internal::Traits32>( |
| process, peb_address_, this); |
| if (!result) { |
| LOG(ERROR) << "ReadProcessData failed"; |
| return false; |
| } |
| |
| if (!ReadMemoryInfo(process, is_64_bit_, this)) { |
| LOG(ERROR) << "ReadMemoryInfo failed"; |
| return false; |
| } |
| |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| bool ProcessInfo::Is64Bit() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return is_64_bit_; |
| } |
| |
| bool ProcessInfo::IsWow64() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return is_wow64_; |
| } |
| |
| crashpad::ProcessID ProcessInfo::ProcessID() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return process_id_; |
| } |
| |
| crashpad::ProcessID ProcessInfo::ParentProcessID() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return inherited_from_process_id_; |
| } |
| |
| bool ProcessInfo::CommandLine(std::wstring* command_line) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| *command_line = command_line_; |
| return true; |
| } |
| |
| void ProcessInfo::Peb(WinVMAddress* peb_address, WinVMSize* peb_size) const { |
| *peb_address = peb_address_; |
| *peb_size = peb_size_; |
| } |
| |
| bool ProcessInfo::Modules(std::vector<Module>* modules) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| *modules = modules_; |
| return true; |
| } |
| |
| const ProcessInfo::MemoryBasicInformation64Vector& ProcessInfo::MemoryInfo() |
| const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return memory_info_; |
| } |
| |
| std::vector<CheckedRange<WinVMAddress, WinVMSize>> |
| ProcessInfo::GetReadableRanges( |
| const CheckedRange<WinVMAddress, WinVMSize>& range) const { |
| return GetReadableRangesOfMemoryMap(range, MemoryInfo()); |
| } |
| |
| bool ProcessInfo::LoggingRangeIsFullyReadable( |
| const CheckedRange<WinVMAddress, WinVMSize>& range) const { |
| const auto ranges = GetReadableRanges(range); |
| if (ranges.empty()) { |
| LOG(ERROR) << base::StringPrintf( |
| "range at 0x%llx, size 0x%llx fully unreadable", |
| range.base(), |
| range.size()); |
| return false; |
| } |
| |
| if (ranges.size() != 1 || |
| ranges[0].base() != range.base() || ranges[0].size() != range.size()) { |
| LOG(ERROR) << base::StringPrintf( |
| "range at 0x%llx, size 0x%llx partially unreadable", |
| range.base(), |
| range.size()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| const std::vector<ProcessInfo::Handle>& ProcessInfo::Handles() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| if (handles_.empty()) |
| handles_ = BuildHandleVector(process_); |
| return handles_; |
| } |
| |
| std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap( |
| const CheckedRange<WinVMAddress, WinVMSize>& range, |
| const ProcessInfo::MemoryBasicInformation64Vector& memory_info) { |
| using Range = CheckedRange<WinVMAddress, WinVMSize>; |
| |
| // Constructing Ranges and using OverlapsRange() is very, very slow in Debug |
| // builds, so do a manual check in this loop. The ranges are still validated |
| // by a CheckedRange before being returned. |
| WinVMAddress range_base = range.base(); |
| WinVMAddress range_end = range.end(); |
| |
| // Find all the ranges that overlap the target range, maintaining their order. |
| ProcessInfo::MemoryBasicInformation64Vector overlapping; |
| const size_t size = memory_info.size(); |
| |
| // This loop is written in an ugly fashion to make Debug performance |
| // reasonable. |
| const MEMORY_BASIC_INFORMATION64* begin = &memory_info[0]; |
| for (size_t i = 0; i < size; ++i) { |
| const MEMORY_BASIC_INFORMATION64& mi = *(begin + i); |
| static_assert(std::is_same<decltype(mi.BaseAddress), WinVMAddress>::value, |
| "expected range address to be WinVMAddress"); |
| static_assert(std::is_same<decltype(mi.RegionSize), WinVMSize>::value, |
| "expected range size to be WinVMSize"); |
| WinVMAddress mi_end = mi.BaseAddress + mi.RegionSize; |
| if (range_base < mi_end && mi.BaseAddress < range_end) |
| overlapping.push_back(mi); |
| } |
| if (overlapping.empty()) |
| return std::vector<Range>(); |
| |
| // For the first and last, trim to the boundary of the incoming range. |
| MEMORY_BASIC_INFORMATION64& front = overlapping.front(); |
| WinVMAddress original_front_base_address = front.BaseAddress; |
| front.BaseAddress = std::max(front.BaseAddress, range.base()); |
| front.RegionSize = |
| (original_front_base_address + front.RegionSize) - front.BaseAddress; |
| |
| MEMORY_BASIC_INFORMATION64& back = overlapping.back(); |
| WinVMAddress back_end = back.BaseAddress + back.RegionSize; |
| back.RegionSize = std::min(range.end(), back_end) - back.BaseAddress; |
| |
| // Discard all non-accessible. |
| overlapping.erase(std::remove_if(overlapping.begin(), |
| overlapping.end(), |
| [](const MEMORY_BASIC_INFORMATION64& mbi) { |
| return !RegionIsAccessible(mbi); |
| }), |
| overlapping.end()); |
| if (overlapping.empty()) |
| return std::vector<Range>(); |
| |
| // Convert to return type. |
| std::vector<Range> as_ranges; |
| for (const auto& mi : overlapping) { |
| as_ranges.push_back(Range(mi.BaseAddress, mi.RegionSize)); |
| DCHECK(as_ranges.back().IsValid()); |
| } |
| |
| // Coalesce remaining regions. |
| std::vector<Range> result; |
| result.push_back(as_ranges[0]); |
| for (size_t i = 1; i < as_ranges.size(); ++i) { |
| if (result.back().end() == as_ranges[i].base()) { |
| result.back().SetRange(result.back().base(), |
| result.back().size() + as_ranges[i].size()); |
| } else { |
| result.push_back(as_ranges[i]); |
| } |
| DCHECK(result.back().IsValid()); |
| } |
| |
| return result; |
| } |
| |
| } // namespace crashpad |