chromium/third_party/crashpad/crashpad/util/win/process_info.cc

// 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 "util/win/process_info.h"

#include <stddef.h>
#include <winternl.h>

#include <algorithm>
#include <limits>
#include <memory>
#include <new>
#include <type_traits>

#include "base/check_op.h"
#include "base/containers/heap_array.h"
#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).
base::HeapArray<uint8_t> QueryObject(
    HANDLE handle,
    OBJECT_INFORMATION_CLASS object_information_class,
    ULONG minimum_size) {
  ULONG return_length;
  auto buffer = base::HeapArray<uint8_t>::Uninit(minimum_size);
  NTSTATUS status = crashpad::NtQueryObject(handle,
                                            object_information_class,
                                            buffer.data(),
                                            (ULONG)buffer.size(),
                                            &return_length);
  if (status == STATUS_INFO_LENGTH_MISMATCH) {
    DCHECK_GT(return_length, buffer.size());

    buffer = base::HeapArray<uint8_t>::Uninit(return_length);
    status = crashpad::NtQueryObject(handle,
                                     object_information_class,
                                     buffer.data(),
                                     (ULONG)buffer.size(),
                                     &return_length);
  }

  if (!NT_SUCCESS(status)) {
    NTSTATUS_LOG(ERROR, status) << "NtQueryObject";
    return base::HeapArray<uint8_t>();
  }

  DCHECK_LE(return_length, buffer.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);

      auto object_basic_information_buffer =
          QueryObject(dup_handle,
                      ObjectBasicInformation,
                      sizeof(PUBLIC_OBJECT_BASIC_INFORMATION));
      if (!object_basic_information_buffer.empty()) {
        PUBLIC_OBJECT_BASIC_INFORMATION* object_basic_information =
            reinterpret_cast<PUBLIC_OBJECT_BASIC_INFORMATION*>(
                object_basic_information_buffer.data());
        // 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;
      }

      auto object_type_information_buffer =
          QueryObject(dup_handle,
                      ObjectTypeInformation,
                      sizeof(PUBLIC_OBJECT_TYPE_INFORMATION));
      if (!object_type_information_buffer.empty()) {
        PUBLIC_OBJECT_TYPE_INFORMATION* object_type_information =
            reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(
                object_type_information_buffer.data());

        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