Object/minidump: Add support for the MemoryInfoList stream

Summary:
This patch adds the definitions of the constants and structures
necessary to interpret the MemoryInfoList minidump stream, as well as
the object::MinidumpFile interface to access the stream.

While the code is fairly simple, there is one important deviation from
the other minidump streams, which is worth calling out explicitly.
Unlike other "List" streams, the size of the records inside
MemoryInfoList stream is not known statically. Instead it is described
in the stream header. This makes it impossible to return
ArrayRef<MemoryInfo> from the accessor method, as it is done with other
streams. Instead, I create an iterator class, which can be parameterized
by the runtime size of the structure, and return
iterator_range<iterator> instead.

Reviewers: amccarth, jhenderson, clayborg

Subscribers: JosephTremoulet, zturner, markmentovai, lldb-commits, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D68210

llvm-svn: 374051
This commit is contained in:
Pavel Labath 2019-10-08 14:15:32 +00:00
parent 534c86d172
commit 6e0b1ce48e
5 changed files with 363 additions and 30 deletions

View File

@ -18,6 +18,7 @@
#ifndef LLVM_BINARYFORMAT_MINIDUMP_H
#define LLVM_BINARYFORMAT_MINIDUMP_H
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/Support/Endian.h"
@ -67,6 +68,42 @@ struct MemoryDescriptor {
};
static_assert(sizeof(MemoryDescriptor) == 16, "");
struct MemoryInfoListHeader {
support::ulittle32_t SizeOfHeader;
support::ulittle32_t SizeOfEntry;
support::ulittle64_t NumberOfEntries;
};
static_assert(sizeof(MemoryInfoListHeader) == 16, "");
enum class MemoryProtection : uint32_t {
#define HANDLE_MDMP_PROTECT(CODE, NAME, NATIVENAME) NAME = CODE,
#include "llvm/BinaryFormat/MinidumpConstants.def"
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/0xffffffffu),
};
enum class MemoryState : uint32_t {
#define HANDLE_MDMP_MEMSTATE(CODE, NAME, NATIVENAME) NAME = CODE,
#include "llvm/BinaryFormat/MinidumpConstants.def"
};
enum class MemoryType : uint32_t {
#define HANDLE_MDMP_MEMTYPE(CODE, NAME, NATIVENAME) NAME = CODE,
#include "llvm/BinaryFormat/MinidumpConstants.def"
};
struct MemoryInfo {
support::ulittle64_t BaseAddress;
support::ulittle64_t AllocationBase;
support::little_t<MemoryProtection> AllocationProtect;
support::ulittle32_t Reserved0;
support::ulittle64_t RegionSize;
support::little_t<MemoryState> State;
support::little_t<MemoryProtection> Protect;
support::little_t<MemoryType> Type;
support::ulittle32_t Reserved1;
};
static_assert(sizeof(MemoryInfo) == 48, "");
/// Specifies the location and type of a single stream in the minidump file. The
/// minidump stream directory is an array of entries of this type, with its size
/// given by Header.NumberOfStreams.

View File

@ -6,8 +6,9 @@
//
//===----------------------------------------------------------------------===//
#if !(defined HANDLE_MDMP_STREAM_TYPE || defined HANDLE_MDMP_ARCH || \
defined HANDLE_MDMP_PLATFORM)
#if !(defined(HANDLE_MDMP_STREAM_TYPE) || defined(HANDLE_MDMP_ARCH) || \
defined(HANDLE_MDMP_PLATFORM) || defined(HANDLE_MDMP_PROTECT) || \
defined(HANDLE_MDMP_MEMSTATE) || defined(HANDLE_MDMP_MEMTYPE))
#error "Missing HANDLE_MDMP definition"
#endif
@ -23,6 +24,18 @@
#define HANDLE_MDMP_PLATFORM(CODE, NAME)
#endif
#ifndef HANDLE_MDMP_PROTECT
#define HANDLE_MDMP_PROTECT(CODE, NAME, NATIVENAME)
#endif
#ifndef HANDLE_MDMP_MEMSTATE
#define HANDLE_MDMP_MEMSTATE(CODE, NAME, NATIVENAME)
#endif
#ifndef HANDLE_MDMP_MEMTYPE
#define HANDLE_MDMP_MEMTYPE(CODE, NAME, NATIVENAME)
#endif
HANDLE_MDMP_STREAM_TYPE(0x0003, ThreadList)
HANDLE_MDMP_STREAM_TYPE(0x0004, ModuleList)
HANDLE_MDMP_STREAM_TYPE(0x0005, MemoryList)
@ -102,6 +115,30 @@ HANDLE_MDMP_PLATFORM(0x8203, Android) // Android
HANDLE_MDMP_PLATFORM(0x8204, PS3) // PS3
HANDLE_MDMP_PLATFORM(0x8205, NaCl) // Native Client (NaCl)
HANDLE_MDMP_PROTECT(0x01, NoAccess, PAGE_NO_ACCESS)
HANDLE_MDMP_PROTECT(0x02, ReadOnly, PAGE_READ_ONLY)
HANDLE_MDMP_PROTECT(0x04, ReadWrite, PAGE_READ_WRITE)
HANDLE_MDMP_PROTECT(0x08, WriteCopy, PAGE_WRITE_COPY)
HANDLE_MDMP_PROTECT(0x10, Execute, PAGE_EXECUTE)
HANDLE_MDMP_PROTECT(0x20, ExecuteRead, PAGE_EXECUTE_READ)
HANDLE_MDMP_PROTECT(0x40, ExecuteReadWrite, PAGE_EXECUTE_READ_WRITE)
HANDLE_MDMP_PROTECT(0x80, ExeciteWriteCopy, PAGE_EXECUTE_WRITE_COPY)
HANDLE_MDMP_PROTECT(0x100, Guard, PAGE_GUARD)
HANDLE_MDMP_PROTECT(0x200, NoCache, PAGE_NOCACHE)
HANDLE_MDMP_PROTECT(0x400, WriteCombine, PAGE_WRITECOMBINE)
HANDLE_MDMP_PROTECT(0x40000000, TargetsInvalid, PAGE_TARGETS_INVALID)
HANDLE_MDMP_MEMSTATE(0x01000, Commit, MEM_COMMIT)
HANDLE_MDMP_MEMSTATE(0x02000, Reserve, MEM_RESERVE)
HANDLE_MDMP_MEMSTATE(0x10000, Free, MEM_FREE)
HANDLE_MDMP_MEMTYPE(0x0020000, Private, MEM_PRIVATE)
HANDLE_MDMP_MEMTYPE(0x0040000, Mapped, MEM_MAPPED)
HANDLE_MDMP_MEMTYPE(0x1000000, Image, MEM_IMAGE)
#undef HANDLE_MDMP_STREAM_TYPE
#undef HANDLE_MDMP_ARCH
#undef HANDLE_MDMP_PLATFORM
#undef HANDLE_MDMP_PROTECT
#undef HANDLE_MDMP_MEMSTATE
#undef HANDLE_MDMP_MEMTYPE

View File

@ -11,6 +11,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/iterator.h"
#include "llvm/BinaryFormat/Minidump.h"
#include "llvm/Object/Binary.h"
#include "llvm/Support/Error.h"
@ -80,16 +81,56 @@ public:
return getListStream<minidump::Thread>(minidump::StreamType::ThreadList);
}
/// Returns the list of memory ranges embedded in the MemoryList stream. An
/// error is returned if the file does not contain this stream, or if the
/// stream is not large enough to contain the number of memory descriptors
/// declared in the stream header. The consistency of the MemoryDescriptor
/// entries themselves is not checked in any way.
/// Returns the list of descriptors embedded in the MemoryList stream. The
/// descriptors provide the content of interesting regions of memory at the
/// time the minidump was taken. An error is returned if the file does not
/// contain this stream, or if the stream is not large enough to contain the
/// number of memory descriptors declared in the stream header. The
/// consistency of the MemoryDescriptor entries themselves is not checked in
/// any way.
Expected<ArrayRef<minidump::MemoryDescriptor>> getMemoryList() const {
return getListStream<minidump::MemoryDescriptor>(
minidump::StreamType::MemoryList);
}
class MemoryInfoIterator
: public iterator_facade_base<MemoryInfoIterator,
std::forward_iterator_tag,
minidump::MemoryInfo> {
public:
MemoryInfoIterator(ArrayRef<uint8_t> Storage, size_t Stride)
: Storage(Storage), Stride(Stride) {
assert(Storage.size() % Stride == 0);
}
bool operator==(const MemoryInfoIterator &R) const {
return Storage.size() == R.Storage.size();
}
const minidump::MemoryInfo &operator*() const {
assert(Storage.size() >= sizeof(minidump::MemoryInfo));
return *reinterpret_cast<const minidump::MemoryInfo *>(Storage.data());
}
MemoryInfoIterator &operator++() {
Storage = Storage.drop_front(Stride);
return *this;
}
private:
ArrayRef<uint8_t> Storage;
size_t Stride;
};
/// Returns the list of descriptors embedded in the MemoryInfoList stream. The
/// descriptors provide properties (e.g. permissions) of interesting regions
/// of memory at the time the minidump was taken. An error is returned if the
/// file does not contain this stream, or if the stream is not large enough to
/// contain the number of memory descriptors declared in the stream header.
/// The consistency of the MemoryInfoList entries themselves is not checked
/// in any way.
Expected<iterator_range<MemoryInfoIterator>> getMemoryInfoList() const;
private:
static Error createError(StringRef Str) {
return make_error<GenericBinaryError>(Str, object_error::parse_failed);
@ -137,10 +178,10 @@ private:
};
template <typename T>
Expected<const T &> MinidumpFile::getStream(minidump::StreamType Stream) const {
if (auto OptionalStream = getRawStream(Stream)) {
if (OptionalStream->size() >= sizeof(T))
return *reinterpret_cast<const T *>(OptionalStream->data());
Expected<const T &> MinidumpFile::getStream(minidump::StreamType Type) const {
if (Optional<ArrayRef<uint8_t>> Stream = getRawStream(Type)) {
if (Stream->size() >= sizeof(T))
return *reinterpret_cast<const T *>(Stream->data());
return createEOFError();
}
return createError("No such stream");
@ -153,10 +194,11 @@ Expected<ArrayRef<T>> MinidumpFile::getDataSliceAs(ArrayRef<uint8_t> Data,
// Check for overflow.
if (Count > std::numeric_limits<size_t>::max() / sizeof(T))
return createEOFError();
auto ExpectedArray = getDataSlice(Data, Offset, sizeof(T) * Count);
if (!ExpectedArray)
return ExpectedArray.takeError();
return ArrayRef<T>(reinterpret_cast<const T *>(ExpectedArray->data()), Count);
Expected<ArrayRef<uint8_t>> Slice =
getDataSlice(Data, Offset, sizeof(T) * Count);
if (!Slice)
return Slice.takeError();
return ArrayRef<T>(reinterpret_cast<const T *>(Slice->data()), Count);
}
} // end namespace object

View File

@ -53,13 +53,30 @@ Expected<std::string> MinidumpFile::getString(size_t Offset) const {
return Result;
}
template <typename T>
Expected<ArrayRef<T>> MinidumpFile::getListStream(StreamType Stream) const {
auto OptionalStream = getRawStream(Stream);
if (!OptionalStream)
Expected<iterator_range<MinidumpFile::MemoryInfoIterator>>
MinidumpFile::getMemoryInfoList() const {
Optional<ArrayRef<uint8_t>> Stream = getRawStream(StreamType::MemoryInfoList);
if (!Stream)
return createError("No such stream");
auto ExpectedSize =
getDataSliceAs<support::ulittle32_t>(*OptionalStream, 0, 1);
auto ExpectedHeader =
getDataSliceAs<minidump::MemoryInfoListHeader>(*Stream, 0, 1);
if (!ExpectedHeader)
return ExpectedHeader.takeError();
const minidump::MemoryInfoListHeader &H = ExpectedHeader.get()[0];
Expected<ArrayRef<uint8_t>> Data =
getDataSlice(*Stream, H.SizeOfHeader, H.SizeOfEntry * H.NumberOfEntries);
if (!Data)
return Data.takeError();
return make_range(MemoryInfoIterator(*Data, H.SizeOfEntry),
MemoryInfoIterator({}, H.SizeOfEntry));
}
template <typename T>
Expected<ArrayRef<T>> MinidumpFile::getListStream(StreamType Type) const {
Optional<ArrayRef<uint8_t>> Stream = getRawStream(Type);
if (!Stream)
return createError("No such stream");
auto ExpectedSize = getDataSliceAs<support::ulittle32_t>(*Stream, 0, 1);
if (!ExpectedSize)
return ExpectedSize.takeError();
@ -69,10 +86,10 @@ Expected<ArrayRef<T>> MinidumpFile::getListStream(StreamType Stream) const {
// Some producers insert additional padding bytes to align the list to an
// 8-byte boundary. Check for that by comparing the list size with the overall
// stream size.
if (ListOffset + sizeof(T) * ListSize < OptionalStream->size())
if (ListOffset + sizeof(T) * ListSize < Stream->size())
ListOffset = 8;
return getDataSliceAs<T>(*OptionalStream, ListOffset, ListSize);
return getDataSliceAs<T>(*Stream, ListOffset, ListSize);
}
template Expected<ArrayRef<Module>>
MinidumpFile::getListStream(StreamType) const;
@ -109,13 +126,14 @@ MinidumpFile::create(MemoryBufferRef Source) {
return ExpectedStreams.takeError();
DenseMap<StreamType, std::size_t> StreamMap;
for (const auto &Stream : llvm::enumerate(*ExpectedStreams)) {
StreamType Type = Stream.value().Type;
const LocationDescriptor &Loc = Stream.value().Location;
for (const auto &StreamDescriptor : llvm::enumerate(*ExpectedStreams)) {
StreamType Type = StreamDescriptor.value().Type;
const LocationDescriptor &Loc = StreamDescriptor.value().Location;
auto ExpectedStream = getDataSlice(Data, Loc.RVA, Loc.DataSize);
if (!ExpectedStream)
return ExpectedStream.takeError();
Expected<ArrayRef<uint8_t>> Stream =
getDataSlice(Data, Loc.RVA, Loc.DataSize);
if (!Stream)
return Stream.takeError();
if (Type == StreamType::Unused && Loc.DataSize == 0) {
// Ignore dummy streams. This is technically ill-formed, but a number of
@ -128,7 +146,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
return createError("Cannot handle one of the minidump streams");
// Update the directory map, checking for duplicate stream types.
if (!StreamMap.try_emplace(Type, Stream.index()).second)
if (!StreamMap.try_emplace(Type, StreamDescriptor.index()).second)
return createError("Duplicate stream type");
}

View File

@ -511,3 +511,202 @@ TEST(MinidumpFile, getMemoryList) {
EXPECT_EQ(0x00090807u, MD.Memory.RVA);
}
}
TEST(MinidumpFile, getMemoryInfoList) {
std::vector<uint8_t> OneEntry{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 64, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
16, 0, 0, 0, 48, 0, 0, 0, // SizeOfHeader, SizeOfEntry
1, 0, 0, 0, 0, 0, 0, 0, // NumberOfEntries
// MemoryInfo
0, 1, 2, 3, 4, 5, 6, 7, // BaseAddress
8, 9, 0, 1, 2, 3, 4, 5, // AllocationBase
16, 0, 0, 0, 6, 7, 8, 9, // AllocationProtect, Reserved0
0, 1, 2, 3, 4, 5, 6, 7, // RegionSize
0, 16, 0, 0, 32, 0, 0, 0, // State, Protect
0, 0, 2, 0, 8, 9, 0, 1, // Type, Reserved1
};
// Same as before, but the list header is larger.
std::vector<uint8_t> BiggerHeader{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 68, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
20, 0, 0, 0, 48, 0, 0, 0, // SizeOfHeader, SizeOfEntry
1, 0, 0, 0, 0, 0, 0, 0, // NumberOfEntries
0, 0, 0, 0, // ???
// MemoryInfo
0, 1, 2, 3, 4, 5, 6, 7, // BaseAddress
8, 9, 0, 1, 2, 3, 4, 5, // AllocationBase
16, 0, 0, 0, 6, 7, 8, 9, // AllocationProtect, Reserved0
0, 1, 2, 3, 4, 5, 6, 7, // RegionSize
0, 16, 0, 0, 32, 0, 0, 0, // State, Protect
0, 0, 2, 0, 8, 9, 0, 1, // Type, Reserved1
};
// Same as before, but the entry is larger.
std::vector<uint8_t> BiggerEntry{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 68, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
16, 0, 0, 0, 52, 0, 0, 0, // SizeOfHeader, SizeOfEntry
1, 0, 0, 0, 0, 0, 0, 0, // NumberOfEntries
// MemoryInfo
0, 1, 2, 3, 4, 5, 6, 7, // BaseAddress
8, 9, 0, 1, 2, 3, 4, 5, // AllocationBase
16, 0, 0, 0, 6, 7, 8, 9, // AllocationProtect, Reserved0
0, 1, 2, 3, 4, 5, 6, 7, // RegionSize
0, 16, 0, 0, 32, 0, 0, 0, // State, Protect
0, 0, 2, 0, 8, 9, 0, 1, // Type, Reserved1
0, 0, 0, 0, // ???
};
for (ArrayRef<uint8_t> Data : {OneEntry, BiggerHeader, BiggerEntry}) {
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
auto ExpectedInfo = File.getMemoryInfoList();
ASSERT_THAT_EXPECTED(ExpectedInfo, Succeeded());
ASSERT_EQ(1u, std::distance(ExpectedInfo->begin(), ExpectedInfo->end()));
const MemoryInfo &Info = *ExpectedInfo.get().begin();
EXPECT_EQ(0x0706050403020100u, Info.BaseAddress);
EXPECT_EQ(0x0504030201000908u, Info.AllocationBase);
EXPECT_EQ(MemoryProtection::Execute, Info.AllocationProtect);
EXPECT_EQ(0x09080706u, Info.Reserved0);
EXPECT_EQ(0x0706050403020100u, Info.RegionSize);
EXPECT_EQ(MemoryState::Commit, Info.State);
EXPECT_EQ(MemoryProtection::ExecuteRead, Info.Protect);
EXPECT_EQ(MemoryType::Private, Info.Type);
EXPECT_EQ(0x01000908u, Info.Reserved1);
}
// Header does not fit into the stream.
std::vector<uint8_t> HeaderTooBig{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 15, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
16, 0, 0, 0, 48, 0, 0, 0, // SizeOfHeader, SizeOfEntry
1, 0, 0, 0, 0, 0, 0, // ???
};
Expected<std::unique_ptr<MinidumpFile>> File = create(HeaderTooBig);
ASSERT_THAT_EXPECTED(File, Succeeded());
EXPECT_THAT_EXPECTED(File.get()->getMemoryInfoList(), Failed<BinaryError>());
// Header fits into the stream, but it is too small to contain the required
// entries.
std::vector<uint8_t> HeaderTooSmall{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 15, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
15, 0, 0, 0, 48, 0, 0, 0, // SizeOfHeader, SizeOfEntry
1, 0, 0, 0, 0, 0, 0, // ???
};
File = create(HeaderTooSmall);
ASSERT_THAT_EXPECTED(File, Succeeded());
EXPECT_THAT_EXPECTED(File.get()->getMemoryInfoList(), Failed<BinaryError>());
std::vector<uint8_t> EntryTooBig{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 64, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
16, 0, 0, 0, 49, 0, 0, 0, // SizeOfHeader, SizeOfEntry
1, 0, 0, 0, 0, 0, 0, 0, // NumberOfEntries
// MemoryInfo
0, 1, 2, 3, 4, 5, 6, 7, // BaseAddress
8, 9, 0, 1, 2, 3, 4, 5, // AllocationBase
16, 0, 0, 0, 6, 7, 8, 9, // AllocationProtect, Reserved0
0, 1, 2, 3, 4, 5, 6, 7, // RegionSize
0, 16, 0, 0, 32, 0, 0, 0, // State, Protect
0, 0, 2, 0, 8, 9, 0, 1, // Type, Reserved1
};
File = create(EntryTooBig);
ASSERT_THAT_EXPECTED(File, Succeeded());
EXPECT_THAT_EXPECTED(File.get()->getMemoryInfoList(), Failed<BinaryError>());
std::vector<uint8_t> ThreeEntries{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
32, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
0, 0, 0, 0, 0, 0, 0, 0, // Flags
// Stream Directory
16, 0, 0, 0, 160, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryInfoListHeader
16, 0, 0, 0, 48, 0, 0, 0, // SizeOfHeader, SizeOfEntry
3, 0, 0, 0, 0, 0, 0, 0, // NumberOfEntries
// MemoryInfo
0, 1, 2, 3, 0, 0, 0, 0, // BaseAddress
0, 0, 0, 0, 0, 0, 0, 0, // AllocationBase
0, 0, 0, 0, 0, 0, 0, 0, // AllocationProtect, Reserved0
0, 0, 0, 0, 0, 0, 0, 0, // RegionSize
0, 0, 0, 0, 0, 0, 0, 0, // State, Protect
0, 0, 0, 0, 0, 0, 0, 0, // Type, Reserved1
0, 0, 4, 5, 6, 7, 0, 0, // BaseAddress
0, 0, 0, 0, 0, 0, 0, 0, // AllocationBase
0, 0, 0, 0, 0, 0, 0, 0, // AllocationProtect, Reserved0
0, 0, 0, 0, 0, 0, 0, 0, // RegionSize
0, 0, 0, 0, 0, 0, 0, 0, // State, Protect
0, 0, 0, 0, 0, 0, 0, 0, // Type, Reserved1
0, 0, 0, 8, 9, 0, 1, 0, // BaseAddress
0, 0, 0, 0, 0, 0, 0, 0, // AllocationBase
0, 0, 0, 0, 0, 0, 0, 0, // AllocationProtect, Reserved0
0, 0, 0, 0, 0, 0, 0, 0, // RegionSize
0, 0, 0, 0, 0, 0, 0, 0, // State, Protect
0, 0, 0, 0, 0, 0, 0, 0, // Type, Reserved1
};
File = create(ThreeEntries);
ASSERT_THAT_EXPECTED(File, Succeeded());
auto ExpectedInfo = File.get()->getMemoryInfoList();
ASSERT_THAT_EXPECTED(ExpectedInfo, Succeeded());
EXPECT_THAT(to_vector<3>(map_range(*ExpectedInfo,
[](const MemoryInfo &Info) -> uint64_t {
return Info.BaseAddress;
})),
testing::ElementsAre(0x0000000003020100u, 0x0000070605040000u,
0x0001000908000000u));
}