Coalesce memory ranges

Follows https://chromium-review.googlesource.com/c/374019/.

Causes MinidumpMemoryListWriter to merge all overlapping ranges before
writing the MINIDUMP_MEMORY_LIST. This is:

1) Necessary for the Google internal crash processor, which in some
   cases attempts to read the raw memory (displaying ASAN red zones),
   and aborts if there are any overlapping ranges in the minidump on
   load;

2) Necessary for new-ish versions of windbg (see bug 216 below). It is
   believed that this is a change in behavior in the tool that made
   dumps with overlapping ranges unreadable;

3) More efficient. The .dmp for crashy_program goes from 306K to 140K
   with this enabled. In Chrome minidumps where
   set_gather_indirectly_referenced_memory() is used (in practice this
   means Chrome Windows Beta, Dev, and Canary), the savings are expected
   to be substantial.

Bug: crashpad:61, chromium:638370, crashpad:216

Change-Id: I969e1a52da555ceba59a727d933bfeef6787c7a5
Reviewed-on: https://chromium-review.googlesource.com/374539
Commit-Queue: Scott Graham <scottmg@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Scott Graham 2018-02-01 15:54:56 -08:00 committed by Commit Bot
parent 0fa7d9d424
commit 9b6c69cbb5
19 changed files with 794 additions and 23 deletions

View File

@ -14,6 +14,8 @@
#include "minidump/minidump_memory_writer.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "base/auto_reset.h"
@ -37,7 +39,7 @@ SnapshotMinidumpMemoryWriter::~SnapshotMinidumpMemoryWriter() {}
bool SnapshotMinidumpMemoryWriter::MemorySnapshotDelegateRead(void* data,
size_t size) {
DCHECK_EQ(state(), kStateWritable);
DCHECK_EQ(size, UnderlyingSnapshot().Size());
DCHECK_EQ(size, UnderlyingSnapshot()->Size());
return file_writer_->Write(data, size);
}
@ -89,7 +91,7 @@ size_t SnapshotMinidumpMemoryWriter::Alignment() {
size_t SnapshotMinidumpMemoryWriter::SizeOfObject() {
DCHECK_GE(state(), kStateFrozen);
return UnderlyingSnapshot().Size();
return UnderlyingSnapshot()->Size();
}
bool SnapshotMinidumpMemoryWriter::WillWriteAtOffsetImpl(FileOffset offset) {
@ -99,7 +101,7 @@ bool SnapshotMinidumpMemoryWriter::WillWriteAtOffsetImpl(FileOffset offset) {
// objects own memory_descriptor_ field.
DCHECK_GE(registered_memory_descriptors_.size(), 1u);
uint64_t base_address = UnderlyingSnapshot().Address();
uint64_t base_address = UnderlyingSnapshot()->Address();
decltype(registered_memory_descriptors_[0]->StartOfMemoryRange) local_address;
if (!AssignIfInRange(&local_address, base_address)) {
LOG(ERROR) << "base_address " << base_address << " out of range";
@ -125,10 +127,11 @@ internal::MinidumpWritable::Phase SnapshotMinidumpMemoryWriter::WritePhase() {
MinidumpMemoryListWriter::MinidumpMemoryListWriter()
: MinidumpStreamWriter(),
memory_writers_(),
non_owned_memory_writers_(),
children_(),
memory_list_base_() {
}
snapshots_created_during_merge_(),
all_memory_writers_(),
memory_list_base_() {}
MinidumpMemoryListWriter::~MinidumpMemoryListWriter() {
}
@ -148,25 +151,80 @@ void MinidumpMemoryListWriter::AddMemory(
std::unique_ptr<SnapshotMinidumpMemoryWriter> memory_writer) {
DCHECK_EQ(state(), kStateMutable);
AddExtraMemory(memory_writer.get());
children_.push_back(std::move(memory_writer));
}
void MinidumpMemoryListWriter::AddExtraMemory(
void MinidumpMemoryListWriter::AddNonOwnedMemory(
SnapshotMinidumpMemoryWriter* memory_writer) {
DCHECK_EQ(state(), kStateMutable);
memory_writers_.push_back(memory_writer);
non_owned_memory_writers_.push_back(memory_writer);
}
void MinidumpMemoryListWriter::CoalesceOwnedMemory() {
if (children_.empty())
return;
DropRangesThatOverlapNonOwned();
std::sort(children_.begin(),
children_.end(),
[](const std::unique_ptr<SnapshotMinidumpMemoryWriter>& a_ptr,
const std::unique_ptr<SnapshotMinidumpMemoryWriter>& b_ptr) {
const MemorySnapshot* a = a_ptr->UnderlyingSnapshot();
const MemorySnapshot* b = b_ptr->UnderlyingSnapshot();
if (a->Address() == b->Address()) {
return a->Size() < b->Size();
}
return a->Address() < b->Address();
});
// Remove any empty ranges.
children_.erase(
std::remove_if(children_.begin(),
children_.end(),
[](const auto& snapshot) {
return snapshot->UnderlyingSnapshot()->Size() == 0;
}),
children_.end());
std::vector<std::unique_ptr<SnapshotMinidumpMemoryWriter>> all_merged;
all_merged.push_back(std::move(children_.front()));
for (size_t i = 1; i < children_.size(); ++i) {
SnapshotMinidumpMemoryWriter* top = all_merged.back().get();
auto& child = children_[i];
if (!DetermineMergedRange(
child->UnderlyingSnapshot(), top->UnderlyingSnapshot(), nullptr)) {
// If it doesn't overlap with the current range, push it.
all_merged.push_back(std::move(child));
} else {
// Otherwise, merge and update the current element.
std::unique_ptr<const MemorySnapshot> merged(
top->UnderlyingSnapshot()->MergeWithOtherSnapshot(
child->UnderlyingSnapshot()));
top->SetSnapshot(merged.get());
snapshots_created_during_merge_.push_back(std::move(merged));
}
}
std::swap(children_, all_merged);
}
bool MinidumpMemoryListWriter::Freeze() {
DCHECK_EQ(state(), kStateMutable);
CoalesceOwnedMemory();
std::copy(non_owned_memory_writers_.begin(),
non_owned_memory_writers_.end(),
std::back_inserter(all_memory_writers_));
for (const auto& ptr : children_)
all_memory_writers_.push_back(ptr.get());
if (!MinidumpStreamWriter::Freeze()) {
return false;
}
size_t memory_region_count = memory_writers_.size();
size_t memory_region_count = all_memory_writers_.size();
CHECK_LE(children_.size(), memory_region_count);
if (!AssignIfInRange(&memory_list_base_.NumberOfMemoryRanges,
@ -181,15 +239,15 @@ bool MinidumpMemoryListWriter::Freeze() {
size_t MinidumpMemoryListWriter::SizeOfObject() {
DCHECK_GE(state(), kStateFrozen);
DCHECK_LE(children_.size(), memory_writers_.size());
DCHECK_LE(children_.size(), all_memory_writers_.size());
return sizeof(memory_list_base_) +
memory_writers_.size() * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
all_memory_writers_.size() * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
}
std::vector<internal::MinidumpWritable*> MinidumpMemoryListWriter::Children() {
DCHECK_GE(state(), kStateFrozen);
DCHECK_LE(children_.size(), memory_writers_.size());
DCHECK_LE(children_.size(), all_memory_writers_.size());
std::vector<MinidumpWritable*> children;
for (const auto& child : children_) {
@ -207,7 +265,8 @@ bool MinidumpMemoryListWriter::WriteObject(FileWriterInterface* file_writer) {
iov.iov_len = sizeof(memory_list_base_);
std::vector<WritableIoVec> iovecs(1, iov);
for (const SnapshotMinidumpMemoryWriter* memory_writer : memory_writers_) {
for (const SnapshotMinidumpMemoryWriter* memory_writer :
all_memory_writers_) {
iov.iov_base = memory_writer->MinidumpMemoryDescriptor();
iov.iov_len = sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
iovecs.push_back(iov);
@ -220,4 +279,23 @@ MinidumpStreamType MinidumpMemoryListWriter::StreamType() const {
return kMinidumpStreamTypeMemoryList;
}
void MinidumpMemoryListWriter::DropRangesThatOverlapNonOwned() {
std::vector<std::unique_ptr<SnapshotMinidumpMemoryWriter>> non_overlapping;
non_overlapping.reserve(children_.size());
for (auto& child_ptr : children_) {
bool overlaps = false;
for (const auto* non_owned : non_owned_memory_writers_) {
if (DetermineMergedRange(child_ptr->UnderlyingSnapshot(),
non_owned->UnderlyingSnapshot(),
nullptr)) {
overlaps = true;
break;
}
}
if (!overlaps)
non_overlapping.push_back(std::move(child_ptr));
}
std::swap(children_, non_overlapping);
}
} // namespace crashpad

View File

@ -60,7 +60,15 @@ class SnapshotMinidumpMemoryWriter : public internal::MinidumpWritable,
//! \note Valid in #kStateFrozen or any preceding state.
void RegisterMemoryDescriptor(MINIDUMP_MEMORY_DESCRIPTOR* memory_descriptor);
//! \brief Sets the underlying memory snapshot. Does not take ownership of \a
//! memory_snapshot.
void SetSnapshot(const MemorySnapshot* memory_snapshot) {
memory_snapshot_ = memory_snapshot;
}
private:
friend class MinidumpMemoryListWriter;
// MemorySnapshot::Delegate:
bool MemorySnapshotDelegateRead(void* data, size_t size) override;
@ -95,7 +103,7 @@ class SnapshotMinidumpMemoryWriter : public internal::MinidumpWritable,
//! \brief Gets the underlying memory snapshot that the memory writer will
//! write to the minidump.
const MemorySnapshot& UnderlyingSnapshot() const { return *memory_snapshot_; }
const MemorySnapshot* UnderlyingSnapshot() const { return memory_snapshot_; }
MINIDUMP_MEMORY_DESCRIPTOR memory_descriptor_;
@ -147,7 +155,7 @@ class MinidumpMemoryListWriter final : public internal::MinidumpStreamWriter {
//! a SnapshotMinidumpMemoryWriter for thread stack memory, is an example.
//!
//! \note Valid in #kStateMutable.
void AddExtraMemory(SnapshotMinidumpMemoryWriter* memory_writer);
void AddNonOwnedMemory(SnapshotMinidumpMemoryWriter* memory_writer);
protected:
// MinidumpWritable:
@ -159,9 +167,35 @@ class MinidumpMemoryListWriter final : public internal::MinidumpStreamWriter {
// MinidumpStreamWriter:
MinidumpStreamType StreamType() const override;
//! \brief Merges any overlapping and abutting memory ranges that were added
//! via AddFromSnapshot() and AddMemory() into single entries.
//!
//! This is expected to be called once just before writing, generally from
//! Freeze().
//!
//! This function has the side-effect of merging owned ranges, dropping any
//! owned ranges that overlap with non-owned ranges, removing empty ranges,
//! and sorting all ranges by address.
//!
//! Per its name, this coalesces owned memory, however, this is not a complete
//! solution for ensuring that no overlapping memory ranges are emitted in the
//! minidump. In particular, if AddNonOwnedMemory() is used to add multiple
//! overlapping ranges, then overlapping ranges will still be emitted to the
//! minidump. Currently, AddNonOwnedMemory() is used only for adding thread
//! stacks, so overlapping shouldn't be a problem in practice. For more
//! details see https://crashpad.chromium.org/bug/61 and
//! https://crrev.com/c/374539.
void CoalesceOwnedMemory();
private:
std::vector<SnapshotMinidumpMemoryWriter*> memory_writers_; // weak
//! \brief Drops children_ ranges that overlap non_owned_memory_writers_.
void DropRangesThatOverlapNonOwned();
std::vector<SnapshotMinidumpMemoryWriter*> non_owned_memory_writers_; // weak
std::vector<std::unique_ptr<SnapshotMinidumpMemoryWriter>> children_;
std::vector<std::unique_ptr<const MemorySnapshot>>
snapshots_created_during_merge_;
std::vector<SnapshotMinidumpMemoryWriter*> all_memory_writers_; // weak
MINIDUMP_MEMORY_LIST memory_list_base_;
DISALLOW_COPY_AND_ASSIGN(MinidumpMemoryListWriter);

View File

@ -241,7 +241,7 @@ TEST(MinidumpMemoryWriter, ExtraMemory) {
std::make_unique<TestMemoryStream>(kBaseAddress0, kSize0, kValue0);
auto memory_list_writer = std::make_unique<MinidumpMemoryListWriter>();
memory_list_writer->AddExtraMemory(test_memory_stream->memory());
memory_list_writer->AddNonOwnedMemory(test_memory_stream->memory());
ASSERT_TRUE(minidump_file_writer.AddStream(std::move(test_memory_stream)));
@ -305,7 +305,7 @@ TEST(MinidumpMemoryWriter, AddFromSnapshot) {
expect_memory_descriptors[0].Memory.DataSize = 0x1000;
values[0] = 0x01;
expect_memory_descriptors[1].StartOfMemoryRange = 0x1000;
expect_memory_descriptors[1].StartOfMemoryRange = 0x2000;
expect_memory_descriptors[1].Memory.DataSize = 0x2000;
values[1] = 0xf4;
@ -353,6 +353,298 @@ TEST(MinidumpMemoryWriter, AddFromSnapshot) {
}
}
TEST(MinidumpMemoryWriter, CoalesceExplicitMultiple) {
MINIDUMP_MEMORY_DESCRIPTOR expect_memory_descriptors[4] = {};
uint8_t values[arraysize(expect_memory_descriptors)] = {};
expect_memory_descriptors[0].StartOfMemoryRange = 0;
expect_memory_descriptors[0].Memory.DataSize = 1000;
values[0] = 0x01;
expect_memory_descriptors[1].StartOfMemoryRange = 10000;
expect_memory_descriptors[1].Memory.DataSize = 2000;
values[1] = 0xf4;
expect_memory_descriptors[2].StartOfMemoryRange = 0x1111111111111111;
expect_memory_descriptors[2].Memory.DataSize = 1024;
values[2] = 0x99;
expect_memory_descriptors[3].StartOfMemoryRange = 0xfedcba9876543210;
expect_memory_descriptors[3].Memory.DataSize = 1024;
values[3] = 0x88;
struct {
uint64_t base;
size_t size;
uint8_t value;
} snapshots_to_add[] = {
// Various overlapping.
{0, 500, 0x01},
{0, 500, 0x01},
{250, 500, 0x01},
{600, 400, 0x01},
// Empty removed.
{0, 0, 0xbb},
{300, 0, 0xcc},
{1000, 0, 0xdd},
{12000, 0, 0xee},
// Abutting.
{10000, 500, 0xf4},
{10500, 500, 0xf4},
{11000, 1000, 0xf4},
// Large base addresses.
{ 0xfedcba9876543210, 1024, 0x88 },
{ 0x1111111111111111, 1024, 0x99 },
};
std::vector<std::unique_ptr<TestMemorySnapshot>> memory_snapshots_owner;
std::vector<const MemorySnapshot*> memory_snapshots;
for (const auto& to_add : snapshots_to_add) {
memory_snapshots_owner.push_back(std::make_unique<TestMemorySnapshot>());
TestMemorySnapshot* memory_snapshot = memory_snapshots_owner.back().get();
memory_snapshot->SetAddress(to_add.base);
memory_snapshot->SetSize(to_add.size);
memory_snapshot->SetValue(to_add.value);
memory_snapshots.push_back(memory_snapshot);
}
auto memory_list_writer = std::make_unique<MinidumpMemoryListWriter>();
memory_list_writer->AddFromSnapshot(memory_snapshots);
MinidumpFileWriter minidump_file_writer;
minidump_file_writer.AddStream(std::move(memory_list_writer));
StringFile string_file;
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
const MINIDUMP_MEMORY_LIST* memory_list = nullptr;
ASSERT_NO_FATAL_FAILURE(
GetMemoryListStream(string_file.string(), &memory_list, 1));
ASSERT_EQ(4u, memory_list->NumberOfMemoryRanges);
for (size_t index = 0; index < memory_list->NumberOfMemoryRanges; ++index) {
SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index));
ExpectMinidumpMemoryDescriptorAndContents(
&expect_memory_descriptors[index],
&memory_list->MemoryRanges[index],
string_file.string(),
values[index],
index == memory_list->NumberOfMemoryRanges - 1);
}
}
struct TestRange {
TestRange(uint64_t base, size_t size) : base(base), size(size) {}
uint64_t base;
size_t size;
};
// Parses a string spec to build a list of ranges suitable for CoalesceTest().
std::vector<TestRange> ParseCoalesceSpec(const char* spec) {
std::vector<TestRange> result;
enum { kNone, kSpace, kDot } state = kNone;
const char* range_started_at = nullptr;
for (const char* p = spec;; ++p) {
EXPECT_TRUE(*p == ' ' || *p == '.' || *p == 0);
if (*p == ' ' || *p == 0) {
if (state == kDot) {
result.push_back(
TestRange(range_started_at - spec, p - range_started_at));
}
state = kSpace;
range_started_at = nullptr;
} else if (*p == '.') {
if (state != kDot) {
range_started_at = p;
state = kDot;
}
}
if (*p == 0)
break;
}
return result;
}
TEST(MinidumpMemoryWriter, CoalesceSpecHelperParse) {
const auto empty = ParseCoalesceSpec("");
ASSERT_EQ(empty.size(), 0u);
const auto a = ParseCoalesceSpec("...");
ASSERT_EQ(a.size(), 1u);
EXPECT_EQ(a[0].base, 0u);
EXPECT_EQ(a[0].size, 3u);
const auto b = ParseCoalesceSpec(" ...");
ASSERT_EQ(b.size(), 1u);
EXPECT_EQ(b[0].base, 2u);
EXPECT_EQ(b[0].size, 3u);
const auto c = ParseCoalesceSpec(" ... ");
ASSERT_EQ(c.size(), 1u);
EXPECT_EQ(c[0].base, 2u);
EXPECT_EQ(c[0].size, 3u);
const auto d = ParseCoalesceSpec(" ... ....");
ASSERT_EQ(d.size(), 2u);
EXPECT_EQ(d[0].base, 2u);
EXPECT_EQ(d[0].size, 3u);
EXPECT_EQ(d[1].base, 7u);
EXPECT_EQ(d[1].size, 4u);
const auto e = ParseCoalesceSpec(" ... ...... ... ");
ASSERT_EQ(e.size(), 3u);
EXPECT_EQ(e[0].base, 2u);
EXPECT_EQ(e[0].size, 3u);
EXPECT_EQ(e[1].base, 7u);
EXPECT_EQ(e[1].size, 6u);
EXPECT_EQ(e[2].base, 14u);
EXPECT_EQ(e[2].size, 3u);
}
constexpr uint8_t kMemoryValue = 0xcd;
// Builds a coalesce test out of specs of ' ' and '.'. Tests that when the two
// ranges are added and coalesced, the result is equal to expected.
void CoalesceTest(const char* r1_spec,
const char* r2_spec,
const char* expected_spec) {
auto r1 = ParseCoalesceSpec(r1_spec);
auto r2 = ParseCoalesceSpec(r2_spec);
auto expected = ParseCoalesceSpec(expected_spec);
std::vector<MINIDUMP_MEMORY_DESCRIPTOR> expect_memory_descriptors;
for (const auto& range : expected) {
MINIDUMP_MEMORY_DESCRIPTOR mmd = {};
mmd.StartOfMemoryRange = range.base;
mmd.Memory.DataSize = static_cast<uint32_t>(range.size);
expect_memory_descriptors.push_back(mmd);
}
std::vector<std::unique_ptr<TestMemorySnapshot>> memory_snapshots_owner;
std::vector<const MemorySnapshot*> memory_snapshots;
const auto add_test_memory_snapshots = [&memory_snapshots_owner,
&memory_snapshots](
std::vector<TestRange> ranges) {
for (const auto& r : ranges) {
memory_snapshots_owner.push_back(std::make_unique<TestMemorySnapshot>());
TestMemorySnapshot* memory_snapshot = memory_snapshots_owner.back().get();
memory_snapshot->SetAddress(r.base);
memory_snapshot->SetSize(r.size);
memory_snapshot->SetValue(kMemoryValue);
memory_snapshots.push_back(memory_snapshot);
}
};
add_test_memory_snapshots(r1);
add_test_memory_snapshots(r2);
auto memory_list_writer = std::make_unique<MinidumpMemoryListWriter>();
memory_list_writer->AddFromSnapshot(memory_snapshots);
MinidumpFileWriter minidump_file_writer;
minidump_file_writer.AddStream(std::move(memory_list_writer));
StringFile string_file;
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
const MINIDUMP_MEMORY_LIST* memory_list = nullptr;
ASSERT_NO_FATAL_FAILURE(
GetMemoryListStream(string_file.string(), &memory_list, 1));
ASSERT_EQ(expected.size(), memory_list->NumberOfMemoryRanges);
for (size_t index = 0; index < memory_list->NumberOfMemoryRanges; ++index) {
SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index));
ExpectMinidumpMemoryDescriptorAndContents(
&expect_memory_descriptors[index],
&memory_list->MemoryRanges[index],
string_file.string(),
kMemoryValue,
index == memory_list->NumberOfMemoryRanges - 1);
}
}
TEST(MinidumpMemoryWriter, CoalescePairsVariousCases) {
// clang-format off
CoalesceTest(" .........",
" .......",
/* result */ " ..............");
CoalesceTest(" .......",
" .........",
/* result */ " ..............");
CoalesceTest(" ...",
" .........",
/* result */ " .........");
CoalesceTest(" .........",
" ......",
/* result */ " .........");
CoalesceTest(" ...",
" ........",
/* result */ " ........");
CoalesceTest(" ........",
" ...",
/* result */ " ........");
CoalesceTest(" ...",
" ........",
/* result */ " ........");
CoalesceTest(" ........",
" ...",
/* result */ " ........");
CoalesceTest(" ... ",
" ...",
/* result */ " ... ...");
CoalesceTest(" ...",
" ... ",
/* result */ " ... ...");
CoalesceTest("...",
".....",
/* result */ ".....");
CoalesceTest("...",
" ..",
/* result */ ".....");
CoalesceTest(" .....",
" ..",
/* result */ " .......");
CoalesceTest(" ......... ......",
" .......",
/* result */ " ..................");
CoalesceTest(" .......",
" ......... ......",
/* result */ " ..................");
CoalesceTest(" .....",
" ......... ......",
/* result */ " ......... ......");
CoalesceTest(" ......... ....... .... .",
" ......... ...... ....",
/* result */ " .......................... .......");
// clang-format on
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -173,7 +173,7 @@ void MinidumpThreadListWriter::AddThread(
if (memory_list_writer_) {
SnapshotMinidumpMemoryWriter* stack = thread->Stack();
if (stack) {
memory_list_writer_->AddExtraMemory(stack);
memory_list_writer_->AddNonOwnedMemory(stack);
}
}

View File

@ -545,7 +545,7 @@ void RunInitializeFromSnapshotTest(bool thread_id_collision) {
expect_threads[1].ThreadId = 11;
expect_threads[1].SuspendCount = 12;
expect_threads[1].Priority = 13;
expect_threads[1].Teb = 0xfedcba9876543210;
expect_threads[1].Teb = 0x1111111111111111;
expect_threads[1].ThreadContext.DataSize = sizeof(MinidumpContextType);
context_seeds[1] = 0x40000001;
tebs[1].StartOfMemoryRange = expect_threads[1].Teb;
@ -554,7 +554,7 @@ void RunInitializeFromSnapshotTest(bool thread_id_collision) {
expect_threads[2].ThreadId = 21;
expect_threads[2].SuspendCount = 22;
expect_threads[2].Priority = 23;
expect_threads[2].Teb = 0x1111111111111111;
expect_threads[2].Teb = 0xfedcba9876543210;
expect_threads[2].Stack.StartOfMemoryRange = 0x3000;
expect_threads[2].Stack.Memory.DataSize = 0x300;
expect_threads[2].ThreadContext.DataSize = sizeof(MinidumpContextType);

View File

@ -32,6 +32,7 @@ static_library("snapshot") {
"exception_snapshot.h",
"handle_snapshot.cc",
"handle_snapshot.h",
"memory_snapshot.cc",
"memory_snapshot.h",
"minidump/minidump_annotation_reader.cc",
"minidump/minidump_annotation_reader.h",
@ -279,6 +280,7 @@ source_set("snapshot_test") {
sources = [
"cpu_context_test.cc",
"memory_snapshot_test.cc",
"minidump/process_snapshot_minidump_test.cc",
]

View File

@ -64,5 +64,10 @@ bool MemorySnapshotLinux::Read(Delegate* delegate) const {
return delegate->MemorySnapshotDelegateRead(buffer.get(), size_);
}
const MemorySnapshot* MemorySnapshotLinux::MergeWithOtherSnapshot(
const MemorySnapshot* other) const {
return MergeWithOtherSnapshotImpl(this, other);
}
} // namespace internal
} // namespace crashpad

View File

@ -53,8 +53,15 @@ class MemorySnapshotLinux final : public MemorySnapshot {
uint64_t Address() const override;
size_t Size() const override;
bool Read(Delegate* delegate) const override;
const MemorySnapshot* MergeWithOtherSnapshot(
const MemorySnapshot* other) const override;
private:
template <class T>
friend const MemorySnapshot* MergeWithOtherSnapshotImpl(
const T* self,
const MemorySnapshot* other);
ProcessReader* process_reader_; // weak
uint64_t address_;
size_t size_;

View File

@ -66,5 +66,10 @@ bool MemorySnapshotMac::Read(Delegate* delegate) const {
return delegate->MemorySnapshotDelegateRead(buffer.get(), size_);
}
const MemorySnapshot* MemorySnapshotMac::MergeWithOtherSnapshot(
const MemorySnapshot* other) const {
return MergeWithOtherSnapshotImpl(this, other);
}
} // namespace internal
} // namespace crashpad

View File

@ -52,8 +52,15 @@ class MemorySnapshotMac final : public MemorySnapshot {
uint64_t Address() const override;
size_t Size() const override;
bool Read(Delegate* delegate) const override;
const MemorySnapshot* MergeWithOtherSnapshot(
const MemorySnapshot* other) const override;
private:
template <class T>
friend const MemorySnapshot* MergeWithOtherSnapshotImpl(
const T* self,
const MemorySnapshot* other);
ProcessReader* process_reader_; // weak
uint64_t address_;
uint64_t size_;

View File

@ -0,0 +1,95 @@
// Copyright 2018 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 "snapshot/memory_snapshot.h"
#include <algorithm>
#include "base/format_macros.h"
#include "base/strings/stringprintf.h"
#include "util/numeric/checked_range.h"
namespace crashpad {
namespace {
bool DetermineMergedRangeImpl(bool log,
const MemorySnapshot* a,
const MemorySnapshot* b,
CheckedRange<uint64_t, size_t>* merged) {
if (a->Size() == 0) {
LOG_IF(ERROR, log) << base::StringPrintf(
"invalid empty range at 0x%" PRIx64, a->Address());
return false;
}
if (b->Size() == 0) {
LOG_IF(ERROR, log) << base::StringPrintf(
"invalid empty range at 0x%" PRIx64, b->Address());
return false;
}
CheckedRange<uint64_t, size_t> range_a(a->Address(), a->Size());
if (!range_a.IsValid()) {
LOG_IF(ERROR, log) << base::StringPrintf("invalid range at 0x%" PRIx64
", size %" PRIuS,
range_a.base(),
range_a.size());
return false;
}
CheckedRange<uint64_t, size_t> range_b(b->Address(), b->Size());
if (!range_b.IsValid()) {
LOG_IF(ERROR, log) << base::StringPrintf("invalid range at 0x%" PRIx64
", size %" PRIuS,
range_b.base(),
range_b.size());
return false;
}
if (!range_a.OverlapsRange(range_b) && range_a.end() != range_b.base() &&
range_b.end() != range_a.base()) {
LOG_IF(ERROR, log) << base::StringPrintf(
"ranges not overlapping or abutting: (0x%" PRIx64 ", size %" PRIuS
") and (0x%" PRIx64 ", size %" PRIuS ")",
range_a.base(),
range_a.size(),
range_b.base(),
range_b.size());
return false;
}
if (merged) {
uint64_t base = std::min(range_a.base(), range_b.base());
uint64_t end = std::max(range_a.end(), range_b.end());
size_t size = static_cast<size_t>(end - base);
merged->SetRange(base, size);
}
return true;
}
} // namespace
bool LoggingDetermineMergedRange(const MemorySnapshot* a,
const MemorySnapshot* b,
CheckedRange<uint64_t, size_t>* merged) {
return DetermineMergedRangeImpl(true, a, b, merged);
}
bool DetermineMergedRange(const MemorySnapshot* a,
const MemorySnapshot* b,
CheckedRange<uint64_t, size_t>* merged) {
return DetermineMergedRangeImpl(false, a, b, merged);
}
} // namespace crashpad

View File

@ -18,6 +18,10 @@
#include <stdint.h>
#include <sys/types.h>
#include <memory>
#include "util/numeric/checked_range.h"
namespace crashpad {
//! \brief An abstract interface to a snapshot representing a region of memory
@ -70,8 +74,68 @@ class MemorySnapshot {
//! Delegate::MemorySnapshotDelegateRead(), which should be `true` on
//! success and `false` on failure.
virtual bool Read(Delegate* delegate) const = 0;
//! \brief Creates a new MemorySnapshot based on merging this one with \a
//! other.
//!
//! The ranges described by the two snapshots must either overlap or abut, and
//! must be of the same concrete type.
//!
//! \return A newly allocated MemorySnapshot representing the merged range, or
//! `nullptr` with an error logged.
virtual const MemorySnapshot* MergeWithOtherSnapshot(
const MemorySnapshot* other) const = 0;
};
//! \brief Given two memory snapshots, checks if they're overlapping or
//! abutting, and if so, returns the result of merging the two ranges.
//!
//! This function is useful to implement
//! MemorySnapshot::MergeWithOtherSnapshot().
//!
//! \param[in] a The first range. Must have Size() > 0.
//! \param[in] b The second range. Must have Size() > 0.
//! \param[out] merged The resulting merged range. May be `nullptr` if only a
//! characterization of the ranges is desired.
//!
//! \return `true` if the input ranges overlap or abut, with \a merged filled
//! out, otherwise, `false` with an error logged if \a log is `true`.
bool LoggingDetermineMergedRange(const MemorySnapshot* a,
const MemorySnapshot* b,
CheckedRange<uint64_t, size_t>* merged);
//! \brief The same as LoggingDetermineMergedRange but with no errors logged.
//!
//! \sa LoggingDetermineMergedRange
bool DetermineMergedRange(const MemorySnapshot* a,
const MemorySnapshot* b,
CheckedRange<uint64_t, size_t>* merged);
namespace internal {
//! \brief A standard implementation of MemorySnapshot::MergeWithOtherSnapshot()
//! for concrete MemorySnapshot implementations that use a
//! `process_reader_`.
template <class T>
const MemorySnapshot* MergeWithOtherSnapshotImpl(const T* self,
const MemorySnapshot* other) {
const T* other_as_memory_snapshot_concrete =
reinterpret_cast<const T*>(other);
if (self->process_reader_ !=
other_as_memory_snapshot_concrete->process_reader_) {
LOG(ERROR) << "different process_reader_ for snapshots";
return nullptr;
}
CheckedRange<uint64_t, size_t> merged(0, 0);
if (!LoggingDetermineMergedRange(self, other, &merged))
return nullptr;
std::unique_ptr<T> result(new T());
result->Initialize(self->process_reader_, merged.base(), merged.size());
return result.release();
}
} // namespace internal
} // namespace crashpad
#endif // CRASHPAD_SNAPSHOT_MEMORY_SNAPSHOT_H_

View File

@ -0,0 +1,152 @@
// Copyright 2018 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 "snapshot/memory_snapshot.h"
#include "base/macros.h"
#include "gtest/gtest.h"
#include "snapshot/test/test_memory_snapshot.h"
namespace crashpad {
namespace test {
namespace {
TEST(DetermineMergedRange, NonOverlapping) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(0);
a.SetSize(100);
b.SetAddress(200);
b.SetSize(50);
CheckedRange<uint64_t, size_t> range(0, 0);
EXPECT_FALSE(DetermineMergedRange(&a, &b, &range));
EXPECT_FALSE(DetermineMergedRange(&b, &a, &range));
a.SetSize(199);
EXPECT_FALSE(DetermineMergedRange(&a, &b, &range));
}
TEST(DetermineMergedRange, Empty) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(100);
a.SetSize(0);
b.SetAddress(200);
b.SetSize(20);
CheckedRange<uint64_t, size_t> range(0, 0);
// Empty are invalid.
EXPECT_FALSE(DetermineMergedRange(&a, &b, &range));
EXPECT_FALSE(DetermineMergedRange(&b, &a, &range));
EXPECT_FALSE(DetermineMergedRange(&a, &a, &range));
}
TEST(DetermineMergedRange, Abutting) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(0);
a.SetSize(10);
b.SetAddress(10);
b.SetSize(20);
CheckedRange<uint64_t, size_t> range(0, 0);
EXPECT_TRUE(DetermineMergedRange(&a, &b, &range));
EXPECT_EQ(0u, range.base());
EXPECT_EQ(30u, range.size());
EXPECT_TRUE(DetermineMergedRange(&b, &a, &range));
EXPECT_EQ(0u, range.base());
EXPECT_EQ(30u, range.size());
}
TEST(DetermineMergedRange, TypicalOverlapping) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(10);
a.SetSize(100);
b.SetAddress(50);
b.SetSize(100);
CheckedRange<uint64_t, size_t> range(0, 0);
EXPECT_TRUE(DetermineMergedRange(&a, &b, &range));
EXPECT_EQ(10u, range.base());
EXPECT_EQ(140u, range.size());
EXPECT_TRUE(DetermineMergedRange(&b, &a, &range));
EXPECT_EQ(10u, range.base());
EXPECT_EQ(140u, range.size());
}
TEST(DetermineMergedRange, OneFullyInsideAnother) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(20);
a.SetSize(100);
b.SetAddress(5);
b.SetSize(200);
CheckedRange<uint64_t, size_t> range(0, 0);
EXPECT_TRUE(DetermineMergedRange(&a, &b, &range));
EXPECT_EQ(5u, range.base());
EXPECT_EQ(200u, range.size());
EXPECT_TRUE(DetermineMergedRange(&b, &a, &range));
EXPECT_EQ(5u, range.base());
EXPECT_EQ(200u, range.size());
}
TEST(DetermineMergedRange, SameStart) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(5);
a.SetSize(100);
b.SetAddress(5);
b.SetSize(50);
CheckedRange<uint64_t, size_t> range(0, 0);
EXPECT_TRUE(DetermineMergedRange(&a, &b, &range));
EXPECT_EQ(5u, range.base());
EXPECT_EQ(100u, range.size());
EXPECT_TRUE(DetermineMergedRange(&b, &a, &range));
EXPECT_EQ(5u, range.base());
EXPECT_EQ(100u, range.size());
}
TEST(DetermineMergedRange, SameEnd) {
TestMemorySnapshot a;
TestMemorySnapshot b;
a.SetAddress(5);
a.SetSize(100);
b.SetAddress(70);
b.SetSize(35);
CheckedRange<uint64_t, size_t> range(0, 0);
EXPECT_TRUE(DetermineMergedRange(&a, &b, &range));
EXPECT_EQ(5u, range.base());
EXPECT_EQ(100u, range.size());
EXPECT_TRUE(DetermineMergedRange(&b, &a, &range));
EXPECT_EQ(5u, range.base());
EXPECT_EQ(100u, range.size());
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -108,6 +108,7 @@
'mac/system_snapshot_mac.h',
'mac/thread_snapshot_mac.cc',
'mac/thread_snapshot_mac.h',
'memory_snapshot.cc',
'memory_snapshot.h',
'minidump/minidump_annotation_reader.cc',
'minidump/minidump_annotation_reader.h',

View File

@ -53,6 +53,7 @@
'type': 'executable',
'dependencies': [
'crashpad_snapshot_test_both_dt_hash_styles',
'crashpad_snapshot_test_lib',
'crashpad_snapshot_test_module',
'crashpad_snapshot_test_module_large',
'crashpad_snapshot_test_module_small',
@ -72,6 +73,7 @@
'sources': [
'api/module_annotations_win_test.cc',
'cpu_context_test.cc',
'memory_snapshot_test.cc',
'crashpad_info_client_options_test.cc',
'crashpad_types/crashpad_info_reader_test.cc',
'crashpad_types/image_annotation_reader_test.cc',

View File

@ -14,6 +14,7 @@
#include "snapshot/test/test_memory_snapshot.h"
#include <memory>
#include <string>
namespace crashpad {
@ -43,5 +44,18 @@ bool TestMemorySnapshot::Read(Delegate* delegate) const {
return delegate->MemorySnapshotDelegateRead(&buffer[0], size_);
}
const MemorySnapshot* TestMemorySnapshot::MergeWithOtherSnapshot(
const MemorySnapshot* other) const {
CheckedRange<uint64_t, size_t> merged(0, 0);
if (!DetermineMergedRange(this, other, &merged))
return nullptr;
std::unique_ptr<TestMemorySnapshot> result(new TestMemorySnapshot());
result->SetAddress(merged.base());
result->SetSize(merged.size());
result->SetValue(value_);
return result.release();
}
} // namespace test
} // namespace crashpad

View File

@ -45,6 +45,8 @@ class TestMemorySnapshot final : public MemorySnapshot {
uint64_t Address() const override;
size_t Size() const override;
bool Read(Delegate* delegate) const override;
const MemorySnapshot* MergeWithOtherSnapshot(
const MemorySnapshot* other) const override;
private:
uint64_t address_;

View File

@ -16,7 +16,6 @@
#include "snapshot/win/memory_snapshot_win.h"
namespace crashpad {
namespace internal {
@ -67,5 +66,10 @@ bool MemorySnapshotWin::Read(Delegate* delegate) const {
return delegate->MemorySnapshotDelegateRead(buffer.get(), size_);
}
const MemorySnapshot* MemorySnapshotWin::MergeWithOtherSnapshot(
const MemorySnapshot* other) const {
return MergeWithOtherSnapshotImpl(this, other);
}
} // namespace internal
} // namespace crashpad

View File

@ -52,8 +52,15 @@ class MemorySnapshotWin final : public MemorySnapshot {
uint64_t Address() const override;
size_t Size() const override;
bool Read(Delegate* delegate) const override;
const MemorySnapshot* MergeWithOtherSnapshot(
const MemorySnapshot* other) const override;
private:
template <class T>
friend const MemorySnapshot* MergeWithOtherSnapshotImpl(
const T* self,
const MemorySnapshot* other);
ProcessReaderWin* process_reader_; // weak
uint64_t address_;
size_t size_;