llvm-project/llvm/unittests/Object/MinidumpTest.cpp
Pavel Labath 6e0b1ce48e 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
2019-10-08 14:15:32 +00:00

713 lines
33 KiB
C++

//===- MinidumpTest.cpp - Tests for Minidump.cpp --------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/Object/Minidump.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::object;
using namespace minidump;
static Expected<std::unique_ptr<MinidumpFile>> create(ArrayRef<uint8_t> Data) {
return MinidumpFile::create(
MemoryBufferRef(toStringRef(Data), "Test buffer"));
}
TEST(MinidumpFile, BasicInterface) {
std::vector<uint8_t> Data{ // Header
'M', 'D', 'M', 'P', // Signature
0x93, 0xa7, 0, 0, // Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
// A very simple minidump file which contains just a single stream.
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
const Header &H = File.header();
EXPECT_EQ(Header::MagicSignature, H.Signature);
EXPECT_EQ(Header::MagicVersion, H.Version);
EXPECT_EQ(1u, H.NumberOfStreams);
EXPECT_EQ(0x20u, H.StreamDirectoryRVA);
EXPECT_EQ(0x03020100u, H.Checksum);
EXPECT_EQ(0x07060504u, H.TimeDateStamp);
EXPECT_EQ(uint64_t(0x0504030201000908), H.Flags);
ASSERT_EQ(1u, File.streams().size());
const Directory &Stream0 = File.streams()[0];
EXPECT_EQ(StreamType::LinuxCPUInfo, Stream0.Type);
EXPECT_EQ(7u, Stream0.Location.DataSize);
EXPECT_EQ(0x2cu, Stream0.Location.RVA);
EXPECT_EQ("CPUINFO", toStringRef(File.getRawStream(Stream0)));
EXPECT_EQ("CPUINFO",
toStringRef(*File.getRawStream(StreamType::LinuxCPUInfo)));
EXPECT_THAT_EXPECTED(File.getSystemInfo(), Failed<BinaryError>());
}
// Use the input from the previous test, but corrupt it in various ways
TEST(MinidumpFile, create_ErrorCases) {
std::vector<uint8_t> FileTooShort{'M', 'D', 'M', 'P'};
EXPECT_THAT_EXPECTED(create(FileTooShort), Failed<BinaryError>());
std::vector<uint8_t> WrongSignature{
// Header
'!', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(WrongSignature), Failed<BinaryError>());
std::vector<uint8_t> WrongVersion{
// Header
'M', 'D', 'M', 'P', 0x39, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(WrongVersion), Failed<BinaryError>());
std::vector<uint8_t> DirectoryAfterEOF{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 1, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(DirectoryAfterEOF), Failed<BinaryError>());
std::vector<uint8_t> TruncatedDirectory{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 1, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(TruncatedDirectory), Failed<BinaryError>());
std::vector<uint8_t> Stream0AfterEOF{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x2c, 1, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(Stream0AfterEOF), Failed<BinaryError>());
std::vector<uint8_t> Stream0Truncated{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 8, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(Stream0Truncated), Failed<BinaryError>());
std::vector<uint8_t> DuplicateStream{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
2, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x40, 0, 0, 0, // RVA
// Stream
3, 0, 0x67, 0x47, 7, 0, 0, 0, // Type, DataSize,
0x40, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(DuplicateStream), Failed<BinaryError>());
std::vector<uint8_t> DenseMapInfoConflict{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
0xff, 0xff, 0xff, 0xff, 7, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// Stream
'C', 'P', 'U', 'I', 'N', 'F', 'O'};
EXPECT_THAT_EXPECTED(create(DenseMapInfoConflict), Failed<BinaryError>());
}
TEST(MinidumpFile, IngoresDummyStreams) {
std::vector<uint8_t> TwoDummyStreams{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
2, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
0, 0, 0, 0, 0, 0, 0, 0, // Type, DataSize,
0x20, 0, 0, 0, // RVA
0, 0, 0, 0, 0, 0, 0, 0, // Type, DataSize,
0x20, 0, 0, 0, // RVA
};
auto ExpectedFile = create(TwoDummyStreams);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
ASSERT_EQ(2u, File.streams().size());
EXPECT_EQ(StreamType::Unused, File.streams()[0].Type);
EXPECT_EQ(StreamType::Unused, File.streams()[1].Type);
EXPECT_EQ(None, File.getRawStream(StreamType::Unused));
}
TEST(MinidumpFile, getSystemInfo) {
std::vector<uint8_t> Data{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
1, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
7, 0, 0, 0, 56, 0, 0, 0, // Type, DataSize,
0x2c, 0, 0, 0, // RVA
// SystemInfo
0, 0, 1, 2, // ProcessorArch, ProcessorLevel
3, 4, 5, 6, // ProcessorRevision, NumberOfProcessors, ProductType
7, 8, 9, 0, 1, 2, 3, 4, // MajorVersion, MinorVersion
5, 6, 7, 8, 2, 0, 0, 0, // BuildNumber, PlatformId
1, 2, 3, 4, 5, 6, 7, 8, // CSDVersionRVA, SuiteMask, Reserved
'L', 'L', 'V', 'M', 'L', 'L', 'V', 'M', 'L', 'L', 'V', 'M', // VendorID
1, 2, 3, 4, 5, 6, 7, 8, // VersionInfo, FeatureInfo
9, 0, 1, 2, // AMDExtendedFeatures
};
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
auto ExpectedInfo = File.getSystemInfo();
ASSERT_THAT_EXPECTED(ExpectedInfo, Succeeded());
const SystemInfo &Info = *ExpectedInfo;
EXPECT_EQ(ProcessorArchitecture::X86, Info.ProcessorArch);
EXPECT_EQ(0x0201, Info.ProcessorLevel);
EXPECT_EQ(0x0403, Info.ProcessorRevision);
EXPECT_EQ(5, Info.NumberOfProcessors);
EXPECT_EQ(6, Info.ProductType);
EXPECT_EQ(0x00090807u, Info.MajorVersion);
EXPECT_EQ(0x04030201u, Info.MinorVersion);
EXPECT_EQ(0x08070605u, Info.BuildNumber);
EXPECT_EQ(OSPlatform::Win32NT, Info.PlatformId);
EXPECT_EQ(0x04030201u, Info.CSDVersionRVA);
EXPECT_EQ(0x0605u, Info.SuiteMask);
EXPECT_EQ(0x0807u, Info.Reserved);
EXPECT_EQ("LLVMLLVMLLVM", llvm::StringRef(Info.CPU.X86.VendorID,
sizeof(Info.CPU.X86.VendorID)));
EXPECT_EQ(0x04030201u, Info.CPU.X86.VersionInfo);
EXPECT_EQ(0x08070605u, Info.CPU.X86.FeatureInfo);
EXPECT_EQ(0x02010009u, Info.CPU.X86.AMDExtendedFeatures);
}
TEST(MinidumpFile, getString) {
std::vector<uint8_t> ManyStrings{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
2, 0, 0, 0, // NumberOfStreams,
0x20, 0, 0, 0, // StreamDirectoryRVA
0, 1, 2, 3, 4, 5, 6, 7, // Checksum, TimeDateStamp
8, 9, 0, 1, 2, 3, 4, 5, // Flags
// Stream Directory
0, 0, 0, 0, 0, 0, 0, 0, // Type, DataSize,
0x20, 0, 0, 0, // RVA
1, 0, 0, 0, 0, 0, // String1 - odd length
0, 0, 1, 0, 0, 0, // String2 - too long
2, 0, 0, 0, 0, 0xd8, // String3 - invalid utf16
0, 0, 0, 0, 0, 0, // String4 - ""
2, 0, 0, 0, 'a', 0, // String5 - "a"
0, // Mis-align next string
2, 0, 0, 0, 'a', 0, // String6 - "a"
};
auto ExpectedFile = create(ManyStrings);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
EXPECT_THAT_EXPECTED(File.getString(44), Failed<BinaryError>());
EXPECT_THAT_EXPECTED(File.getString(50), Failed<BinaryError>());
EXPECT_THAT_EXPECTED(File.getString(56), Failed<BinaryError>());
EXPECT_THAT_EXPECTED(File.getString(62), HasValue(""));
EXPECT_THAT_EXPECTED(File.getString(68), HasValue("a"));
EXPECT_THAT_EXPECTED(File.getString(75), HasValue("a"));
// Check the case when the size field does not fit into the remaining data.
EXPECT_THAT_EXPECTED(File.getString(ManyStrings.size() - 2),
Failed<BinaryError>());
}
TEST(MinidumpFile, getModuleList) {
std::vector<uint8_t> OneModule{
// 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
4, 0, 0, 0, 112, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// ModuleList
1, 0, 0, 0, // NumberOfModules
1, 2, 3, 4, 5, 6, 7, 8, // BaseOfImage
9, 0, 1, 2, 3, 4, 5, 6, // SizeOfImage, Checksum
7, 8, 9, 0, 1, 2, 3, 4, // TimeDateStamp, ModuleNameRVA
0, 0, 0, 0, 0, 0, 0, 0, // Signature, StructVersion
0, 0, 0, 0, 0, 0, 0, 0, // FileVersion
0, 0, 0, 0, 0, 0, 0, 0, // ProductVersion
0, 0, 0, 0, 0, 0, 0, 0, // FileFlagsMask, FileFlags
0, 0, 0, 0, // FileOS
0, 0, 0, 0, 0, 0, 0, 0, // FileType, FileSubType
0, 0, 0, 0, 0, 0, 0, 0, // FileDate
1, 2, 3, 4, 5, 6, 7, 8, // CvRecord
9, 0, 1, 2, 3, 4, 5, 6, // MiscRecord
7, 8, 9, 0, 1, 2, 3, 4, // Reserved0
5, 6, 7, 8, 9, 0, 1, 2, // Reserved1
};
// Same as before, but with a padded module list.
std::vector<uint8_t> PaddedModule{
// 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
4, 0, 0, 0, 116, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// ModuleList
1, 0, 0, 0, // NumberOfModules
0, 0, 0, 0, // Padding
1, 2, 3, 4, 5, 6, 7, 8, // BaseOfImage
9, 0, 1, 2, 3, 4, 5, 6, // SizeOfImage, Checksum
7, 8, 9, 0, 1, 2, 3, 4, // TimeDateStamp, ModuleNameRVA
0, 0, 0, 0, 0, 0, 0, 0, // Signature, StructVersion
0, 0, 0, 0, 0, 0, 0, 0, // FileVersion
0, 0, 0, 0, 0, 0, 0, 0, // ProductVersion
0, 0, 0, 0, 0, 0, 0, 0, // FileFlagsMask, FileFlags
0, 0, 0, 0, // FileOS
0, 0, 0, 0, 0, 0, 0, 0, // FileType, FileSubType
0, 0, 0, 0, 0, 0, 0, 0, // FileDate
1, 2, 3, 4, 5, 6, 7, 8, // CvRecord
9, 0, 1, 2, 3, 4, 5, 6, // MiscRecord
7, 8, 9, 0, 1, 2, 3, 4, // Reserved0
5, 6, 7, 8, 9, 0, 1, 2, // Reserved1
};
for (ArrayRef<uint8_t> Data : {OneModule, PaddedModule}) {
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
Expected<ArrayRef<Module>> ExpectedModule = File.getModuleList();
ASSERT_THAT_EXPECTED(ExpectedModule, Succeeded());
ASSERT_EQ(1u, ExpectedModule->size());
const Module &M = ExpectedModule.get()[0];
EXPECT_EQ(0x0807060504030201u, M.BaseOfImage);
EXPECT_EQ(0x02010009u, M.SizeOfImage);
EXPECT_EQ(0x06050403u, M.Checksum);
EXPECT_EQ(0x00090807u, M.TimeDateStamp);
EXPECT_EQ(0x04030201u, M.ModuleNameRVA);
EXPECT_EQ(0x04030201u, M.CvRecord.DataSize);
EXPECT_EQ(0x08070605u, M.CvRecord.RVA);
EXPECT_EQ(0x02010009u, M.MiscRecord.DataSize);
EXPECT_EQ(0x06050403u, M.MiscRecord.RVA);
EXPECT_EQ(0x0403020100090807u, M.Reserved0);
EXPECT_EQ(0x0201000908070605u, M.Reserved1);
}
std::vector<uint8_t> StreamTooShort{
// 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
4, 0, 0, 0, 111, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// ModuleList
1, 0, 0, 0, // NumberOfModules
1, 2, 3, 4, 5, 6, 7, 8, // BaseOfImage
9, 0, 1, 2, 3, 4, 5, 6, // SizeOfImage, Checksum
7, 8, 9, 0, 1, 2, 3, 4, // TimeDateStamp, ModuleNameRVA
0, 0, 0, 0, 0, 0, 0, 0, // Signature, StructVersion
0, 0, 0, 0, 0, 0, 0, 0, // FileVersion
0, 0, 0, 0, 0, 0, 0, 0, // ProductVersion
0, 0, 0, 0, 0, 0, 0, 0, // FileFlagsMask, FileFlags
0, 0, 0, 0, // FileOS
0, 0, 0, 0, 0, 0, 0, 0, // FileType, FileSubType
0, 0, 0, 0, 0, 0, 0, 0, // FileDate
1, 2, 3, 4, 5, 6, 7, 8, // CvRecord
9, 0, 1, 2, 3, 4, 5, 6, // MiscRecord
7, 8, 9, 0, 1, 2, 3, 4, // Reserved0
5, 6, 7, 8, 9, 0, 1, 2, // Reserved1
};
auto ExpectedFile = create(StreamTooShort);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
EXPECT_THAT_EXPECTED(File.getModuleList(), Failed<BinaryError>());
}
TEST(MinidumpFile, getThreadList) {
std::vector<uint8_t> OneThread{
// 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
3, 0, 0, 0, 52, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// ThreadList
1, 0, 0, 0, // NumberOfThreads
1, 2, 3, 4, 5, 6, 7, 8, // ThreadId, SuspendCount
9, 0, 1, 2, 3, 4, 5, 6, // PriorityClass, Priority
7, 8, 9, 0, 1, 2, 3, 4, // EnvironmentBlock
// Stack
5, 6, 7, 8, 9, 0, 1, 2, // StartOfMemoryRange
3, 4, 5, 6, 7, 8, 9, 0, // DataSize, RVA
// Context
1, 2, 3, 4, 5, 6, 7, 8, // DataSize, RVA
};
// Same as before, but with a padded thread list.
std::vector<uint8_t> PaddedThread{
// 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
3, 0, 0, 0, 56, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// ThreadList
1, 0, 0, 0, // NumberOfThreads
0, 0, 0, 0, // Padding
1, 2, 3, 4, 5, 6, 7, 8, // ThreadId, SuspendCount
9, 0, 1, 2, 3, 4, 5, 6, // PriorityClass, Priority
7, 8, 9, 0, 1, 2, 3, 4, // EnvironmentBlock
// Stack
5, 6, 7, 8, 9, 0, 1, 2, // StartOfMemoryRange
3, 4, 5, 6, 7, 8, 9, 0, // DataSize, RVA
// Context
1, 2, 3, 4, 5, 6, 7, 8, // DataSize, RVA
};
for (ArrayRef<uint8_t> Data : {OneThread, PaddedThread}) {
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
Expected<ArrayRef<Thread>> ExpectedThread = File.getThreadList();
ASSERT_THAT_EXPECTED(ExpectedThread, Succeeded());
ASSERT_EQ(1u, ExpectedThread->size());
const Thread &T = ExpectedThread.get()[0];
EXPECT_EQ(0x04030201u, T.ThreadId);
EXPECT_EQ(0x08070605u, T.SuspendCount);
EXPECT_EQ(0x02010009u, T.PriorityClass);
EXPECT_EQ(0x06050403u, T.Priority);
EXPECT_EQ(0x0403020100090807u, T.EnvironmentBlock);
EXPECT_EQ(0x0201000908070605u, T.Stack.StartOfMemoryRange);
EXPECT_EQ(0x06050403u, T.Stack.Memory.DataSize);
EXPECT_EQ(0x00090807u, T.Stack.Memory.RVA);
EXPECT_EQ(0x04030201u, T.Context.DataSize);
EXPECT_EQ(0x08070605u, T.Context.RVA);
}
}
TEST(MinidumpFile, getMemoryList) {
std::vector<uint8_t> OneRange{
// 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
5, 0, 0, 0, 20, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryDescriptor
1, 0, 0, 0, // NumberOfMemoryRanges
5, 6, 7, 8, 9, 0, 1, 2, // StartOfMemoryRange
3, 4, 5, 6, 7, 8, 9, 0, // DataSize, RVA
};
// Same as before, but with a padded memory list.
std::vector<uint8_t> PaddedRange{
// 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
5, 0, 0, 0, 24, 0, 0, 0, // Type, DataSize,
44, 0, 0, 0, // RVA
// MemoryDescriptor
1, 0, 0, 0, // NumberOfMemoryRanges
0, 0, 0, 0, // Padding
5, 6, 7, 8, 9, 0, 1, 2, // StartOfMemoryRange
3, 4, 5, 6, 7, 8, 9, 0, // DataSize, RVA
};
for (ArrayRef<uint8_t> Data : {OneRange, PaddedRange}) {
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
Expected<ArrayRef<MemoryDescriptor>> ExpectedRanges = File.getMemoryList();
ASSERT_THAT_EXPECTED(ExpectedRanges, Succeeded());
ASSERT_EQ(1u, ExpectedRanges->size());
const MemoryDescriptor &MD = ExpectedRanges.get()[0];
EXPECT_EQ(0x0201000908070605u, MD.StartOfMemoryRange);
EXPECT_EQ(0x06050403u, MD.Memory.DataSize);
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));
}