Index: include/llvm/BinaryFormat/Minidump.h =================================================================== --- include/llvm/BinaryFormat/Minidump.h +++ include/llvm/BinaryFormat/Minidump.h @@ -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 @@ }; 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 AllocationProtect; + support::ulittle32_t Reserved0; + support::ulittle64_t RegionSize; + support::little_t State; + support::little_t Protect; + support::little_t 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. Index: include/llvm/BinaryFormat/MinidumpConstants.def =================================================================== --- include/llvm/BinaryFormat/MinidumpConstants.def +++ include/llvm/BinaryFormat/MinidumpConstants.def @@ -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(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 Index: include/llvm/Object/Minidump.h =================================================================== --- include/llvm/Object/Minidump.h +++ include/llvm/Object/Minidump.h @@ -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 @@ return getListStream(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> getMemoryList() const { return getListStream( minidump::StreamType::MemoryList); } + class MemoryInfoIterator + : public iterator_facade_base { + public: + MemoryInfoIterator(ArrayRef 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(Storage.data()); + } + + MemoryInfoIterator &operator++() { + Storage = Storage.drop_front(Stride); + return *this; + } + + private: + ArrayRef 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> getMemoryInfoList() const; + private: static Error createError(StringRef Str) { return make_error(Str, object_error::parse_failed); Index: lib/Object/Minidump.cpp =================================================================== --- lib/Object/Minidump.cpp +++ lib/Object/Minidump.cpp @@ -53,6 +53,24 @@ return Result; } +Expected> +MinidumpFile::getMemoryInfoList() const { + auto OptionalStream = getRawStream(StreamType::MemoryInfoList); + if (!OptionalStream) + return createError("No such stream"); + auto ExpectedHeader = + getDataSliceAs(*OptionalStream, 0, 1); + if (!ExpectedHeader) + return ExpectedHeader.takeError(); + const minidump::MemoryInfoListHeader &H = ExpectedHeader.get()[0]; + auto ExpectedData = getDataSlice(*OptionalStream, H.SizeOfHeader, + H.SizeOfEntry * H.NumberOfEntries); + if (!ExpectedData) + return ExpectedData.takeError(); + return make_range(MemoryInfoIterator(*ExpectedData, H.SizeOfEntry), + MemoryInfoIterator({}, H.SizeOfEntry)); +} + template Expected> MinidumpFile::getListStream(StreamType Stream) const { auto OptionalStream = getRawStream(Stream); Index: unittests/Object/MinidumpTest.cpp =================================================================== --- unittests/Object/MinidumpTest.cpp +++ unittests/Object/MinidumpTest.cpp @@ -511,3 +511,180 @@ EXPECT_EQ(0x00090807u, MD.Memory.RVA); } } + +TEST(MinidumpFile, getMemoryInfoList) { + std::vector 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 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 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 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 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, 12, 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, // ??? + }; + EXPECT_THAT_EXPECTED(cantFail(create(HeaderTooBig))->getMemoryInfoList(), + Failed()); + + std::vector 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, 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 + }; + EXPECT_THAT_EXPECTED(cantFail(create(EntryTooBig))->getMemoryInfoList(), + Failed()); + + std::vector 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 + }; + auto ExpectedFile = create(ThreeEntries); + ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded()); + auto ExpectedInfo = ExpectedFile.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)); +}