win: Add memory map range intersection helper

To be used for improved version of ReadMemory() that is memory-map
aware, in particular for reading the environment block in
https://codereview.chromium.org/1360863006/.

R=mark@chromium.org
BUG=crashpad:20, crashpad:46

Review URL: https://codereview.chromium.org/1372183002 .
This commit is contained in:
Scott Graham 2015-10-01 11:47:32 -07:00
parent 7942b87fcb
commit 4df538f283
5 changed files with 347 additions and 6 deletions

View File

@ -104,11 +104,31 @@ class CheckedRange {
return that.base() >= base() && that.end() <= end();
}
//! \brief Returns whether the range overlaps another range.
//!
//! \param[in] that The (possibly) overlapping range.
//!
//! \return `true` if `this` range, the first range, overlaps \a that,
//! the provided range. `false` otherwise.
//!
//! Ranges are considered to be closed-open [base, end) for this test. Zero
//! length ranges are never considered to overlap another range.
//!
//! This method must only be called if IsValid() would return `true` for both
//! CheckedRange objects involved.
bool OverlapsRange(const CheckedRange<ValueType, SizeType>& that) const {
DCHECK(IsValid());
DCHECK(that.IsValid());
if (size() == 0 || that.size() == 0)
return false;
return base() < that.end() && that.base() < end();
}
private:
ValueType base_;
SizeType size_;
DISALLOW_COPY_AND_ASSIGN(CheckedRange);
};
} // namespace crashpad

View File

@ -155,7 +155,7 @@ TEST(CheckedRange, IsValid) {
TEST(CheckedRange, ContainsValue) {
const struct TestData {
uint32_t value;
bool valid;
bool contains;
} kTestData[] = {
{0, false},
{1, false},
@ -190,7 +190,7 @@ TEST(CheckedRange, ContainsValue) {
SCOPED_TRACE(base::StringPrintf(
"index %" PRIuS ", value 0x%x", index, testcase.value));
EXPECT_EQ(testcase.valid, parent_range.ContainsValue(testcase.value));
EXPECT_EQ(testcase.contains, parent_range.ContainsValue(testcase.value));
}
}
@ -198,7 +198,7 @@ TEST(CheckedRange, ContainsRange) {
const struct TestData {
uint32_t base;
uint32_t size;
bool valid;
bool contains;
} kTestData[] = {
{0, 0, false},
{0, 1, false},
@ -242,7 +242,60 @@ TEST(CheckedRange, ContainsRange) {
CheckedRange<uint32_t> child_range(testcase.base, testcase.size);
ASSERT_TRUE(child_range.IsValid());
EXPECT_EQ(testcase.valid, parent_range.ContainsRange(child_range));
EXPECT_EQ(testcase.contains, parent_range.ContainsRange(child_range));
}
}
TEST(CheckedRange, OverlapsRange) {
const struct TestData {
uint32_t base;
uint32_t size;
bool overlaps;
} kTestData[] = {
{0, 0, false},
{0, 1, false},
{0x2000, 0x1000, true},
{0, 0x2000, false},
{0x3000, 0x1000, false},
{0x1800, 0x1000, true},
{0x1800, 0x2000, true},
{0x2800, 0x1000, true},
{0x2000, 0x800, true},
{0x2800, 0x800, true},
{0x2400, 0x800, true},
{0x2800, 0, false},
{0x2000, 0xffffdfff, true},
{0x2800, 0xffffd7ff, true},
{0x3000, 0xffffcfff, false},
{0xfffffffe, 1, false},
{0xffffffff, 0, false},
{0x1fff, 0, false},
{0x2000, 0, false},
{0x2001, 0, false},
{0x2fff, 0, false},
{0x3000, 0, false},
{0x3001, 0, false},
{0x1fff, 1, false},
{0x2000, 1, true},
{0x2001, 1, true},
{0x2fff, 1, true},
{0x3000, 1, false},
{0x3001, 1, false},
};
CheckedRange<uint32_t> first_range(0x2000, 0x1000);
ASSERT_TRUE(first_range.IsValid());
for (size_t index = 0; index < arraysize(kTestData); ++index) {
const TestData& testcase = kTestData[index];
SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%x, size 0x%x",
index,
testcase.base,
testcase.size));
CheckedRange<uint32_t> second_range(testcase.base, testcase.size);
ASSERT_TRUE(second_range.IsValid());
EXPECT_EQ(testcase.overlaps, first_range.OverlapsRange(second_range));
}
}

View File

@ -16,6 +16,7 @@
#include <winternl.h>
#include <algorithm>
#include <limits>
#include "base/logging.h"
@ -106,6 +107,13 @@ bool ReadStruct(HANDLE process, WinVMAddress at, T* into) {
return true;
}
bool RegionIsInaccessible(const ProcessInfo::MemoryInfo& memory_info) {
return memory_info.state == MEM_FREE ||
(memory_info.state == MEM_COMMIT &&
((memory_info.protect & PAGE_NOACCESS) ||
(memory_info.protect & PAGE_GUARD)));
}
} // namespace
template <class Traits>
@ -421,7 +429,72 @@ bool ProcessInfo::Modules(std::vector<Module>* modules) const {
const std::vector<ProcessInfo::MemoryInfo>& ProcessInfo::MemoryInformation()
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, MemoryInformation());
}
std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap(
const CheckedRange<WinVMAddress, WinVMSize>& range,
const std::vector<ProcessInfo::MemoryInfo>& memory_info) {
using Range = CheckedRange<WinVMAddress, WinVMSize>;
// Find all the ranges that overlap the target range, maintaining their order.
std::vector<ProcessInfo::MemoryInfo> overlapping;
for (const auto& mi : memory_info) {
if (range.OverlapsRange(Range(mi.base_address, mi.region_size)))
overlapping.push_back(mi);
}
if (overlapping.empty())
return std::vector<Range>();
// For the first and last, trim to the boundary of the incoming range.
ProcessInfo::MemoryInfo& front = overlapping.front();
WinVMAddress original_front_base_address = front.base_address;
front.base_address = std::max(front.base_address, range.base());
front.region_size =
(original_front_base_address + front.region_size) - front.base_address;
ProcessInfo::MemoryInfo& back = overlapping.back();
WinVMAddress back_end = back.base_address + back.region_size;
back.region_size = std::min(range.end(), back_end) - back.base_address;
// Discard all non-accessible.
overlapping.erase(std::remove_if(overlapping.begin(),
overlapping.end(),
[](const ProcessInfo::MemoryInfo& mi) {
return RegionIsInaccessible(mi);
}),
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.base_address, mi.region_size));
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

View File

@ -23,6 +23,7 @@
#include "base/basictypes.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/numeric/checked_range.h"
#include "util/win/address_types.h"
namespace crashpad {
@ -129,6 +130,16 @@ class ProcessInfo {
//! \brief Retrieves information about all pages mapped into the process.
const std::vector<MemoryInfo>& MemoryInformation() const;
//! \brief Given a range to be read from the target process, returns a vector
//! of ranges, representing the readable portions of the original range.
//!
//! \param[in] range The range being identified.
//!
//! \return A vector of ranges corresponding to the portion of \a range that
//! is readable based on the memory map.
std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRanges(
const CheckedRange<WinVMAddress, WinVMSize>& range) const;
private:
template <class Traits>
friend bool GetProcessBasicInformation(HANDLE process,
@ -159,6 +170,16 @@ class ProcessInfo {
DISALLOW_COPY_AND_ASSIGN(ProcessInfo);
};
//! \brief Given a memory map of a process, and a range to be read from the
//! target process, returns a vector of ranges, representing the readable
//! portions of the original range.
//!
//! This is a free function for testing, but prefer
//! ProcessInfo::GetReadableRanges().
std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap(
const CheckedRange<WinVMAddress, WinVMSize>& range,
const std::vector<ProcessInfo::MemoryInfo>& memory_info);
} // namespace crashpad
#endif // CRASHPAD_UTIL_WIN_PROCESS_INFO_H_

View File

@ -199,6 +199,180 @@ TEST(ProcessInfo, OtherProcessWOW64) {
}
#endif // ARCH_CPU_64_BITS
TEST(ProcessInfo, AccessibleRangesNone) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = 0;
mbi.RegionSize = 10;
mbi.State = MEM_FREE;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(2, 4),
memory_info);
EXPECT_TRUE(result.empty());
}
TEST(ProcessInfo, AccessibleRangesOneInside) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = 0;
mbi.RegionSize = 10;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(2, 4),
memory_info);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(2, result[0].base());
EXPECT_EQ(4, result[0].size());
}
TEST(ProcessInfo, AccessibleRangesOneTruncatedSize) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = 0;
mbi.RegionSize = 10;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
mbi.BaseAddress = reinterpret_cast<void*>(10);
mbi.RegionSize = 20;
mbi.State = MEM_FREE;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
memory_info);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(5, result[0].base());
EXPECT_EQ(5, result[0].size());
}
TEST(ProcessInfo, AccessibleRangesOneMovedStart) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = 0;
mbi.RegionSize = 10;
mbi.State = MEM_FREE;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
mbi.BaseAddress = reinterpret_cast<void*>(10);
mbi.RegionSize = 20;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
memory_info);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(10, result[0].base());
EXPECT_EQ(5, result[0].size());
}
TEST(ProcessInfo, AccessibleRangesCoalesced) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = 0;
mbi.RegionSize = 10;
mbi.State = MEM_FREE;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
mbi.BaseAddress = reinterpret_cast<void*>(10);
mbi.RegionSize = 2;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
mbi.BaseAddress = reinterpret_cast<void*>(12);
mbi.RegionSize = 5;
mbi.State = MEM_RESERVE;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(11, 4),
memory_info);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(11, result[0].base());
EXPECT_EQ(4, result[0].size());
}
TEST(ProcessInfo, AccessibleRangesMiddleUnavailable) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = 0;
mbi.RegionSize = 10;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
mbi.BaseAddress = reinterpret_cast<void*>(10);
mbi.RegionSize = 5;
mbi.State = MEM_FREE;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
mbi.BaseAddress = reinterpret_cast<void*>(15);
mbi.RegionSize = 100;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 45),
memory_info);
ASSERT_EQ(result.size(), 2u);
EXPECT_EQ(5, result[0].base());
EXPECT_EQ(5, result[0].size());
EXPECT_EQ(15, result[1].base());
EXPECT_EQ(35, result[1].size());
}
TEST(ProcessInfo, RequestedBeforeMap) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = reinterpret_cast<void*>(10);
mbi.RegionSize = 10;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
memory_info);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(10, result[0].base());
EXPECT_EQ(5, result[0].size());
}
TEST(ProcessInfo, RequestedAfterMap) {
std::vector<ProcessInfo::MemoryInfo> memory_info;
MEMORY_BASIC_INFORMATION mbi = {0};
mbi.BaseAddress = reinterpret_cast<void*>(10);
mbi.RegionSize = 10;
mbi.State = MEM_COMMIT;
memory_info.push_back(ProcessInfo::MemoryInfo(mbi));
std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
GetReadableRangesOfMemoryMap(
CheckedRange<WinVMAddress, WinVMSize>(15, 100), memory_info);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(15, result[0].base());
EXPECT_EQ(5, result[0].size());
}
} // namespace
} // namespace test
} // namespace crashpad