From 6587b1348ff7e9c87061c698a857cfa5155037d2 Mon Sep 17 00:00:00 2001 From: Pavel Labath Date: Thu, 21 Mar 2019 09:18:59 +0000 Subject: [PATCH] [Object] Add basic minidump support Summary: This patch adds basic support for reading minidump files. It contains the definitions of various important minidump data structures (header, stream directory), and of one minidump stream (SystemInfo). The ability to read other streams will be added in follow-up patches. However, all streams can be read even now as raw data, which means lldb's minidump support (where this code is taken from) can be immediately rebased on top of this patch as soon as it lands. As we don't have any support for generating minidump files (yet), this tests the code via unit tests with some small handcrafted binaries in the form of c char arrays. Reviewers: Bigcheese, jhenderson, zturner Subscribers: srhines, dschuff, mgorny, fedor.sergeev, lemo, clayborg, JDevlieghere, aprantl, lldb-commits, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D59291 llvm-svn: 356652 --- include/llvm/BinaryFormat/Magic.h | 1 + include/llvm/BinaryFormat/Minidump.h | 147 ++++++++++ .../llvm/BinaryFormat/MinidumpConstants.def | 107 ++++++++ include/llvm/Object/Binary.h | 6 +- include/llvm/Object/Minidump.h | 121 +++++++++ lib/BinaryFormat/CMakeLists.txt | 1 + lib/BinaryFormat/Magic.cpp | 5 +- lib/BinaryFormat/Minidump.cpp | 14 + lib/Object/Binary.cpp | 3 + lib/Object/CMakeLists.txt | 1 + lib/Object/Minidump.cpp | 77 ++++++ lib/Object/ObjectFile.cpp | 1 + lib/Object/SymbolicFile.cpp | 1 + unittests/Object/CMakeLists.txt | 2 + unittests/Object/MinidumpTest.cpp | 256 ++++++++++++++++++ 15 files changed, 741 insertions(+), 2 deletions(-) create mode 100644 include/llvm/BinaryFormat/Minidump.h create mode 100644 include/llvm/BinaryFormat/MinidumpConstants.def create mode 100644 include/llvm/Object/Minidump.h create mode 100644 lib/BinaryFormat/Minidump.cpp create mode 100644 lib/Object/Minidump.cpp create mode 100644 unittests/Object/MinidumpTest.cpp diff --git a/include/llvm/BinaryFormat/Magic.h b/include/llvm/BinaryFormat/Magic.h index 05af2c367c8..091f63201e4 100644 --- a/include/llvm/BinaryFormat/Magic.h +++ b/include/llvm/BinaryFormat/Magic.h @@ -39,6 +39,7 @@ struct file_magic { macho_dsym_companion, ///< Mach-O dSYM companion file macho_kext_bundle, ///< Mach-O kext bundle file macho_universal_binary, ///< Mach-O universal binary + minidump, ///< Windows minidump file coff_cl_gl_object, ///< Microsoft cl.exe's intermediate code file coff_object, ///< COFF object file coff_import_library, ///< COFF import library diff --git a/include/llvm/BinaryFormat/Minidump.h b/include/llvm/BinaryFormat/Minidump.h new file mode 100644 index 00000000000..1e2dd972830 --- /dev/null +++ b/include/llvm/BinaryFormat/Minidump.h @@ -0,0 +1,147 @@ +//===- Minidump.h - Minidump constants and structures -----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This header constants and data structures pertaining to the Windows Minidump +// core file format. +// +// Reference: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms679293(v=vs.85).aspx +// https://chromium.googlesource.com/breakpad/breakpad/ +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_BINARYFORMAT_MINIDUMP_H +#define LLVM_BINARYFORMAT_MINIDUMP_H + +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/Support/Endian.h" + +namespace llvm { +namespace minidump { + +/// The minidump header is the first part of a minidump file. It identifies the +/// file as a minidump file, and gives the location of the stream directory. +struct Header { + static constexpr uint32_t MagicSignature = 0x504d444d; // PMDM + static constexpr uint16_t MagicVersion = 0xa793; + + support::ulittle32_t Signature; + // The high 16 bits of version field are implementation specific. The low 16 + // bits should be MagicVersion. + support::ulittle32_t Version; + support::ulittle32_t NumberOfStreams; + support::ulittle32_t StreamDirectoryRVA; + support::ulittle32_t Checksum; + support::ulittle32_t TimeDateStamp; + support::ulittle64_t Flags; +}; +static_assert(sizeof(Header) == 32, ""); + +/// The type of a minidump stream identifies its contents. Streams numbers after +/// LastReserved are for application-defined data streams. +enum class StreamType : uint32_t { +#define HANDLE_MDMP_STREAM_TYPE(CODE, NAME) NAME = CODE, +#include "llvm/BinaryFormat/MinidumpConstants.def" + Unused = 0, + LastReserved = 0x0000ffff, +}; + +/// Specifies the location (and size) of various objects in the minidump file. +/// The location is relative to the start of the file. +struct LocationDescriptor { + support::ulittle32_t DataSize; + support::ulittle32_t RVA; +}; +static_assert(sizeof(LocationDescriptor) == 8, ""); + +/// 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. +struct Directory { + support::little_t Type; + LocationDescriptor Location; +}; +static_assert(sizeof(Directory) == 12, ""); + +/// The processor architecture of the system that generated this minidump. Used +/// in the ProcessorArch field of the SystemInfo stream. +enum class ProcessorArchitecture : uint16_t { +#define HANDLE_MDMP_ARCH(CODE, NAME) NAME = CODE, +#include "llvm/BinaryFormat/MinidumpConstants.def" +}; + +/// The OS Platform of the system that generated this minidump. Used in the +/// PlatformId field of the SystemInfo stream. +enum class OSPlatform : uint32_t { +#define HANDLE_MDMP_PLATFORM(CODE, NAME) NAME = CODE, +#include "llvm/BinaryFormat/MinidumpConstants.def" +}; + +/// Detailed information about the processor of the system that generated this +/// minidump. Its interpretation depends on the ProcessorArchitecture enum. +union CPUInfo { + struct X86Info { + char VendorID[12]; // cpuid 0: ebx, edx, ecx + support::ulittle32_t VersionInfo; // cpuid 1: eax + support::ulittle32_t FeatureInfo; // cpuid 1: edx + support::ulittle32_t AMDExtendedFeatures; // cpuid 0x80000001, ebx + } X86; + struct ArmInfo { + support::ulittle32_t CPUID; + support::ulittle32_t ElfHWCaps; // linux specific, 0 otherwise + } Arm; + struct OtherInfo { + uint8_t ProcessorFeatures[16]; + } Other; +}; +static_assert(sizeof(CPUInfo) == 24, ""); + +/// The SystemInfo stream, containing various information about the system where +/// this minidump was generated. +struct SystemInfo { + support::little_t ProcessorArch; + support::ulittle16_t ProcessorLevel; + support::ulittle16_t ProcessorRevision; + + uint8_t NumberOfProcessors; + uint8_t ProductType; + + support::ulittle32_t MajorVersion; + support::ulittle32_t MinorVersion; + support::ulittle32_t BuildNumber; + support::little_t PlatformId; + support::ulittle32_t CSDVersionRVA; + + support::ulittle16_t SuiteMask; + support::ulittle16_t Reserved; + + CPUInfo CPU; +}; +static_assert(sizeof(SystemInfo) == 56, ""); + +} // namespace minidump + +template <> struct DenseMapInfo { + static minidump::StreamType getEmptyKey() { return minidump::StreamType(-1); } + + static minidump::StreamType getTombstoneKey() { + return minidump::StreamType(-2); + } + + static unsigned getHashValue(minidump::StreamType Val) { + return DenseMapInfo::getHashValue(static_cast(Val)); + } + + static bool isEqual(minidump::StreamType LHS, minidump::StreamType RHS) { + return LHS == RHS; + } +}; + +} // namespace llvm + +#endif // LLVM_BINARYFORMAT_MINIDUMP_H diff --git a/include/llvm/BinaryFormat/MinidumpConstants.def b/include/llvm/BinaryFormat/MinidumpConstants.def new file mode 100644 index 00000000000..d4f13dd9921 --- /dev/null +++ b/include/llvm/BinaryFormat/MinidumpConstants.def @@ -0,0 +1,107 @@ +//===- MinidumpConstants.def - Iteration over minidump constants-*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if !(defined HANDLE_MDMP_STREAM_TYPE || defined HANDLE_MDMP_ARCH || \ + defined HANDLE_MDMP_PLATFORM) +#error "Missing HANDLE_MDMP definition" +#endif + +#ifndef HANDLE_MDMP_STREAM_TYPE +#define HANDLE_MDMP_STREAM_TYPE(CODE, NAME) +#endif + +#ifndef HANDLE_MDMP_ARCH +#define HANDLE_MDMP_ARCH(CODE, NAME) +#endif + +#ifndef HANDLE_MDMP_PLATFORM +#define HANDLE_MDMP_PLATFORM(CODE, NAME) +#endif + +HANDLE_MDMP_STREAM_TYPE(0x0003, ThreadList) +HANDLE_MDMP_STREAM_TYPE(0x0004, ModuleList) +HANDLE_MDMP_STREAM_TYPE(0x0005, MemoryList) +HANDLE_MDMP_STREAM_TYPE(0x0006, Exception) +HANDLE_MDMP_STREAM_TYPE(0x0007, SystemInfo) +HANDLE_MDMP_STREAM_TYPE(0x0008, ThreadExList) +HANDLE_MDMP_STREAM_TYPE(0x0009, Memory64List) +HANDLE_MDMP_STREAM_TYPE(0x000a, CommentA) +HANDLE_MDMP_STREAM_TYPE(0x000b, CommentW) +HANDLE_MDMP_STREAM_TYPE(0x000c, HandleData) +HANDLE_MDMP_STREAM_TYPE(0x000d, FunctionTable) +HANDLE_MDMP_STREAM_TYPE(0x000e, UnloadedModuleList) +HANDLE_MDMP_STREAM_TYPE(0x000f, MiscInfo) +HANDLE_MDMP_STREAM_TYPE(0x0010, MemoryInfoList) +HANDLE_MDMP_STREAM_TYPE(0x0011, ThreadInfoList) +HANDLE_MDMP_STREAM_TYPE(0x0012, HandleOperationList) +HANDLE_MDMP_STREAM_TYPE(0x0013, Token) +HANDLE_MDMP_STREAM_TYPE(0x0014, JavascriptData) +HANDLE_MDMP_STREAM_TYPE(0x0015, SystemMemoryInfo) +HANDLE_MDMP_STREAM_TYPE(0x0016, ProcessVMCounters) +// Breakpad extension types. 0x4767 = "Gg" +HANDLE_MDMP_STREAM_TYPE(0x47670001, BreakpadInfo) +HANDLE_MDMP_STREAM_TYPE(0x47670002, AssertionInfo) +// These are additional minidump stream values which are specific to the linux +// breakpad implementation. +HANDLE_MDMP_STREAM_TYPE(0x47670003, LinuxCPUInfo) // /proc/cpuinfo +HANDLE_MDMP_STREAM_TYPE(0x47670004, LinuxProcStatus) // /proc/$x/status +HANDLE_MDMP_STREAM_TYPE(0x47670005, LinuxLSBRelease) // /etc/lsb-release +HANDLE_MDMP_STREAM_TYPE(0x47670006, LinuxCMDLine) // /proc/$x/cmdline +HANDLE_MDMP_STREAM_TYPE(0x47670007, LinuxEnviron) // /proc/$x/environ +HANDLE_MDMP_STREAM_TYPE(0x47670008, LinuxAuxv) // /proc/$x/auxv +HANDLE_MDMP_STREAM_TYPE(0x47670009, LinuxMaps) // /proc/$x/maps +HANDLE_MDMP_STREAM_TYPE(0x4767000A, LinuxDSODebug) +HANDLE_MDMP_STREAM_TYPE(0x4767000B, LinuxProcStat) // /proc/$x/stat +HANDLE_MDMP_STREAM_TYPE(0x4767000C, LinuxProcUptime) // uptime +HANDLE_MDMP_STREAM_TYPE(0x4767000D, LinuxProcFD) // /proc/$x/fd +// Facebook-defined stream types +HANDLE_MDMP_STREAM_TYPE(0xFACE1CA7, FacebookLogcat) +HANDLE_MDMP_STREAM_TYPE(0xFACECAFA, FacebookAppCustomData) +HANDLE_MDMP_STREAM_TYPE(0xFACECAFB, FacebookBuildID) +HANDLE_MDMP_STREAM_TYPE(0xFACECAFC, FacebookAppVersionName) +HANDLE_MDMP_STREAM_TYPE(0xFACECAFD, FacebookJavaStack) +HANDLE_MDMP_STREAM_TYPE(0xFACECAFE, FacebookDalvikInfo) +HANDLE_MDMP_STREAM_TYPE(0xFACECAFF, FacebookUnwindSymbols) +HANDLE_MDMP_STREAM_TYPE(0xFACECB00, FacebookDumpErrorLog) +HANDLE_MDMP_STREAM_TYPE(0xFACECCCC, FacebookAppStateLog) +HANDLE_MDMP_STREAM_TYPE(0xFACEDEAD, FacebookAbortReason) +HANDLE_MDMP_STREAM_TYPE(0xFACEE000, FacebookThreadName) + +HANDLE_MDMP_ARCH(0x0000, X86) // PROCESSOR_ARCHITECTURE_INTEL +HANDLE_MDMP_ARCH(0x0001, MIPS) // PROCESSOR_ARCHITECTURE_MIPS +HANDLE_MDMP_ARCH(0x0002, Alpha) // PROCESSOR_ARCHITECTURE_ALPHA +HANDLE_MDMP_ARCH(0x0003, PPC) // PROCESSOR_ARCHITECTURE_PPC +HANDLE_MDMP_ARCH(0x0004, SHX) // PROCESSOR_ARCHITECTURE_SHX (Super-H) +HANDLE_MDMP_ARCH(0x0005, ARM) // PROCESSOR_ARCHITECTURE_ARM +HANDLE_MDMP_ARCH(0x0006, IA64) // PROCESSOR_ARCHITECTURE_IA64 +HANDLE_MDMP_ARCH(0x0007, Alpha64) // PROCESSOR_ARCHITECTURE_ALPHA64 +HANDLE_MDMP_ARCH(0x0008, MSIL) // PROCESSOR_ARCHITECTURE_MSIL +HANDLE_MDMP_ARCH(0x0009, AMD64) // PROCESSOR_ARCHITECTURE_AMD64 +HANDLE_MDMP_ARCH(0x000a, X86Win64) // PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 +HANDLE_MDMP_ARCH(0x8001, SPARC) // Breakpad-defined value for SPARC +HANDLE_MDMP_ARCH(0x8002, PPC64) // Breakpad-defined value for PPC64 +HANDLE_MDMP_ARCH(0x8003, ARM64) // Breakpad-defined value for ARM64 +HANDLE_MDMP_ARCH(0x8004, MIPS64) // Breakpad-defined value for MIPS64 + +HANDLE_MDMP_PLATFORM(0x0000, Win32S) // Win32 on Windows 3.1 +HANDLE_MDMP_PLATFORM(0x0001, Win32Windows) // Windows 95-98-Me +HANDLE_MDMP_PLATFORM(0x0002, Win32NT) // Windows NT, 2000+ +HANDLE_MDMP_PLATFORM(0x0003, Win32CE) // Windows CE, Windows Mobile, "Handheld" +// Breakpad-defined values. +HANDLE_MDMP_PLATFORM(0x8000, Unix) // Generic Unix-ish +HANDLE_MDMP_PLATFORM(0x8101, MacOSX) // Mac OS X/Darwin +HANDLE_MDMP_PLATFORM(0x8102, IOS) // iOS +HANDLE_MDMP_PLATFORM(0x8201, Linux) // Linux +HANDLE_MDMP_PLATFORM(0x8202, Solaris) // Solaris +HANDLE_MDMP_PLATFORM(0x8203, Android) // Android +HANDLE_MDMP_PLATFORM(0x8204, PS3) // PS3 +HANDLE_MDMP_PLATFORM(0x8205, NaCl) // Native Client (NaCl) + +#undef HANDLE_MDMP_STREAM_TYPE +#undef HANDLE_MDMP_ARCH +#undef HANDLE_MDMP_PLATFORM diff --git a/include/llvm/Object/Binary.h b/include/llvm/Object/Binary.h index 8ce2d5aa402..4d5ca8cc96a 100644 --- a/include/llvm/Object/Binary.h +++ b/include/llvm/Object/Binary.h @@ -41,7 +41,9 @@ protected: ID_Archive, ID_MachOUniversalBinary, ID_COFFImportFile, - ID_IR, // LLVM IR + ID_IR, // LLVM IR + + ID_Minidump, ID_WinRes, // Windows resource (.res) file. @@ -127,6 +129,8 @@ public: return TypeID == ID_IR; } + bool isMinidump() const { return TypeID == ID_Minidump; } + bool isLittleEndian() const { return !(TypeID == ID_ELF32B || TypeID == ID_ELF64B || TypeID == ID_MachO32B || TypeID == ID_MachO64B); diff --git a/include/llvm/Object/Minidump.h b/include/llvm/Object/Minidump.h new file mode 100644 index 00000000000..bb7d95bd546 --- /dev/null +++ b/include/llvm/Object/Minidump.h @@ -0,0 +1,121 @@ +//===- Minidump.h - Minidump object file implementation ---------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_OBJECT_MINIDUMP_H +#define LLVM_OBJECT_MINIDUMP_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/BinaryFormat/Minidump.h" +#include "llvm/Object/Binary.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace object { + +/// A class providing access to the contents of a minidump file. +class MinidumpFile : public Binary { +public: + /// Construct a new MinidumpFile object from the given memory buffer. Returns + /// an error if this file cannot be identified as a minidump file, or if its + /// contents are badly corrupted (i.e. we cannot read the stream directory). + static Expected> create(MemoryBufferRef Source); + + static bool classof(const Binary *B) { return B->isMinidump(); } + + /// Returns the contents of the minidump header. + const minidump::Header &header() const { return Header; } + + /// Returns the list of streams (stream directory entries) in this file. + ArrayRef streams() const { return Streams; } + + /// Returns the raw contents of the stream given by the directory entry. + ArrayRef getRawStream(const minidump::Directory &Stream) const { + return getData().slice(Stream.Location.RVA, Stream.Location.DataSize); + } + + /// Returns the raw contents of the stream of the given type, or None if the + /// file does not contain a stream of this type. + Optional> getRawStream(minidump::StreamType Type) const; + + /// Returns the contents of the SystemInfo stream, cast to the appropriate + /// type. An error is returned if the file does not contain this stream, or + /// the stream is smaller than the size of the SystemInfo structure. The + /// internal consistency of the stream is not checked in any way. + Expected getSystemInfo() const { + return getStream(minidump::StreamType::SystemInfo); + } + +private: + static Error createError(StringRef Str, + object_error Err = object_error::parse_failed) { + return make_error(Str, Err); + } + + static Error createEOFError() { + return createError("Unexpected EOF", object_error::unexpected_eof); + } + + /// Return a slice of the given data array, with bounds checking. + static Expected> getDataSlice(ArrayRef Data, + size_t Offset, size_t Size); + + /// Return the slice of the given data array as an array of objects of the + /// given type. The function checks that the input array is large enough to + /// contain the correct number of objects of the given type. + template + static Expected> getDataSliceAs(ArrayRef Data, + size_t Offset, size_t Count); + + MinidumpFile(MemoryBufferRef Source, const minidump::Header &Header, + ArrayRef Streams, + DenseMap StreamMap) + : Binary(ID_Minidump, Source), Header(Header), Streams(Streams), + StreamMap(std::move(StreamMap)) {} + + ArrayRef getData() const { + return arrayRefFromStringRef(Data.getBuffer()); + } + + /// Return the stream of the given type, cast to the appropriate type. Checks + /// that the stream is large enough to hold an object of this type. + template + Expected getStream(minidump::StreamType Stream) const; + + const minidump::Header &Header; + ArrayRef Streams; + DenseMap StreamMap; +}; + +template +Expected MinidumpFile::getStream(minidump::StreamType Stream) const { + if (auto OptionalStream = getRawStream(Stream)) { + if (OptionalStream->size() >= sizeof(T)) + return *reinterpret_cast(OptionalStream->data()); + return createError("Malformed stream", object_error::unexpected_eof); + } + return createError("No such stream", object_error::invalid_section_index); +} + +template +Expected> MinidumpFile::getDataSliceAs(ArrayRef Data, + size_t Offset, + size_t Count) { + // Check for overflow. + if (Count > std::numeric_limits::max() / sizeof(T)) + return createEOFError(); + auto ExpectedArray = getDataSlice(Data, Offset, sizeof(T) * Count); + if (!ExpectedArray) + return ExpectedArray.takeError(); + return ArrayRef(reinterpret_cast(ExpectedArray->data()), Count); +} + +} // end namespace object +} // end namespace llvm + +#endif // LLVM_OBJECT_MINIDUMP_H diff --git a/lib/BinaryFormat/CMakeLists.txt b/lib/BinaryFormat/CMakeLists.txt index 98ed8472af7..5d792a6abca 100644 --- a/lib/BinaryFormat/CMakeLists.txt +++ b/lib/BinaryFormat/CMakeLists.txt @@ -2,6 +2,7 @@ add_llvm_library(LLVMBinaryFormat AMDGPUMetadataVerifier.cpp Dwarf.cpp Magic.cpp + Minidump.cpp MsgPackDocument.cpp MsgPackDocumentYAML.cpp MsgPackReader.cpp diff --git a/lib/BinaryFormat/Magic.cpp b/lib/BinaryFormat/Magic.cpp index 5e9d6e73257..85f944fa6ab 100644 --- a/lib/BinaryFormat/Magic.cpp +++ b/lib/BinaryFormat/Magic.cpp @@ -181,7 +181,8 @@ file_magic llvm::identify_magic(StringRef Magic) { return file_magic::coff_object; break; - case 'M': // Possible MS-DOS stub on Windows PE file or MSF/PDB file. + case 'M': // Possible MS-DOS stub on Windows PE file, MSF/PDB file or a + // Minidump file. if (startswith(Magic, "MZ") && Magic.size() >= 0x3c + 4) { uint32_t off = read32le(Magic.data() + 0x3c); // PE/COFF file, either EXE or DLL. @@ -191,6 +192,8 @@ file_magic llvm::identify_magic(StringRef Magic) { } if (Magic.startswith("Microsoft C/C++ MSF 7.00\r\n")) return file_magic::pdb; + if (startswith(Magic, "MDMP")) + return file_magic::minidump; break; case 0x64: // x86-64 or ARM64 Windows. diff --git a/lib/BinaryFormat/Minidump.cpp b/lib/BinaryFormat/Minidump.cpp new file mode 100644 index 00000000000..b618fb15701 --- /dev/null +++ b/lib/BinaryFormat/Minidump.cpp @@ -0,0 +1,14 @@ +//===-- Minidump.cpp - Minidump constants and structures ---------*- C++-*-===// +// +// 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/BinaryFormat/Minidump.h" + +using namespace llvm::minidump; + +constexpr uint32_t Header::MagicSignature; +constexpr uint16_t Header::MagicVersion; diff --git a/lib/Object/Binary.cpp b/lib/Object/Binary.cpp index 87b2c1278c0..e1063a64b14 100644 --- a/lib/Object/Binary.cpp +++ b/lib/Object/Binary.cpp @@ -16,6 +16,7 @@ #include "llvm/Object/Archive.h" #include "llvm/Object/Error.h" #include "llvm/Object/MachOUniversal.h" +#include "llvm/Object/Minidump.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Object/WindowsResource.h" #include "llvm/Support/Error.h" @@ -81,6 +82,8 @@ Expected> object::createBinary(MemoryBufferRef Buffer, case file_magic::coff_cl_gl_object: // Unrecognized object file format. return errorCodeToError(object_error::invalid_file_type); + case file_magic::minidump: + return MinidumpFile::create(Buffer); } llvm_unreachable("Unexpected Binary File Type"); } diff --git a/lib/Object/CMakeLists.txt b/lib/Object/CMakeLists.txt index fd5e7707c54..a0ac0046c0b 100644 --- a/lib/Object/CMakeLists.txt +++ b/lib/Object/CMakeLists.txt @@ -13,6 +13,7 @@ add_llvm_library(LLVMObject IRSymtab.cpp MachOObjectFile.cpp MachOUniversal.cpp + Minidump.cpp ModuleSymbolTable.cpp Object.cpp ObjectFile.cpp diff --git a/lib/Object/Minidump.cpp b/lib/Object/Minidump.cpp new file mode 100644 index 00000000000..d0d49b4e8d3 --- /dev/null +++ b/lib/Object/Minidump.cpp @@ -0,0 +1,77 @@ +//===- Minidump.cpp - Minidump object file implementation -----------------===// +// +// 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/Object/Error.h" + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::minidump; + +Optional> +MinidumpFile::getRawStream(minidump::StreamType Type) const { + auto It = StreamMap.find(Type); + if (It != StreamMap.end()) + return getRawStream(Streams[It->second]); + return None; +} + +Expected> +MinidumpFile::getDataSlice(ArrayRef Data, size_t Offset, size_t Size) { + // Check for overflow. + if (Offset + Size < Offset || Offset + Size < Size || + Offset + Size > Data.size()) + return createEOFError(); + return Data.slice(Offset, Size); +} + +Expected> +MinidumpFile::create(MemoryBufferRef Source) { + ArrayRef Data = arrayRefFromStringRef(Source.getBuffer()); + auto ExpectedHeader = getDataSliceAs(Data, 0, 1); + if (!ExpectedHeader) + return ExpectedHeader.takeError(); + + const minidump::Header &Hdr = (*ExpectedHeader)[0]; + if (Hdr.Signature != Header::MagicSignature) + return createError("Invalid signature"); + if ((Hdr.Version & 0xffff) != Header::MagicVersion) + return createError("Invalid version"); + + auto ExpectedStreams = getDataSliceAs(Data, Hdr.StreamDirectoryRVA, + Hdr.NumberOfStreams); + if (!ExpectedStreams) + return ExpectedStreams.takeError(); + + DenseMap StreamMap; + for (const auto &Stream : llvm::enumerate(*ExpectedStreams)) { + StreamType Type = Stream.value().Type; + const LocationDescriptor &Loc = Stream.value().Location; + + auto ExpectedStream = getDataSlice(Data, Loc.RVA, Loc.DataSize); + if (!ExpectedStream) + return ExpectedStream.takeError(); + + if (Type == StreamType::Unused && Loc.DataSize == 0) { + // Ignore dummy streams. This is technically ill-formed, but a number of + // existing minidumps seem to contain such streams. + continue; + } + + if (Type == DenseMapInfo::getEmptyKey() || + Type == DenseMapInfo::getTombstoneKey()) + 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) + return createError("Duplicate stream type"); + } + + return std::unique_ptr( + new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap))); +} diff --git a/lib/Object/ObjectFile.cpp b/lib/Object/ObjectFile.cpp index 441ef54bd84..cf095db8e8c 100644 --- a/lib/Object/ObjectFile.cpp +++ b/lib/Object/ObjectFile.cpp @@ -127,6 +127,7 @@ ObjectFile::createObjectFile(MemoryBufferRef Object, file_magic Type) { case file_magic::macho_universal_binary: case file_magic::windows_resource: case file_magic::pdb: + case file_magic::minidump: return errorCodeToError(object_error::invalid_file_type); case file_magic::elf: case file_magic::elf_relocatable: diff --git a/lib/Object/SymbolicFile.cpp b/lib/Object/SymbolicFile.cpp index 8ee82567710..675f2091518 100644 --- a/lib/Object/SymbolicFile.cpp +++ b/lib/Object/SymbolicFile.cpp @@ -52,6 +52,7 @@ SymbolicFile::createSymbolicFile(MemoryBufferRef Object, file_magic Type, case file_magic::macho_universal_binary: case file_magic::windows_resource: case file_magic::pdb: + case file_magic::minidump: return errorCodeToError(object_error::invalid_file_type); case file_magic::elf: case file_magic::elf_executable: diff --git a/unittests/Object/CMakeLists.txt b/unittests/Object/CMakeLists.txt index e1376bffbc0..e0fc6ce8893 100644 --- a/unittests/Object/CMakeLists.txt +++ b/unittests/Object/CMakeLists.txt @@ -3,7 +3,9 @@ set(LLVM_LINK_COMPONENTS ) add_llvm_unittest(ObjectTests + MinidumpTest.cpp SymbolSizeTest.cpp SymbolicFileTest.cpp ) +target_link_libraries(ObjectTests PRIVATE LLVMTestingSupport) diff --git a/unittests/Object/MinidumpTest.cpp b/unittests/Object/MinidumpTest.cpp new file mode 100644 index 00000000000..6ebb2a98952 --- /dev/null +++ b/unittests/Object/MinidumpTest.cpp @@ -0,0 +1,256 @@ +//===- 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> create(ArrayRef Data) { + return MinidumpFile::create( + MemoryBufferRef(toStringRef(Data), "Test buffer")); +} + +TEST(MinidumpFile, BasicInterface) { + // A very simple minidump file which contains just a single stream. + auto ExpectedFile = + create({ // 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, 0, 0, 0, // RVA + // Stream + 'C', 'P', 'U', 'I', 'N', 'F', 'O'}); + 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()); +} + +// Use the input from the previous test, but corrupt it in various ways +TEST(MinidumpFile, create_ErrorCases) { + // File too short + EXPECT_THAT_EXPECTED(create({'M', 'D', 'M', 'P'}), Failed()); + + // Wrong Signature + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Wrong Version + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Stream directory after EOF + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Truncated stream directory + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Stream0 after EOF + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Truncated Stream0 + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Duplicate Stream + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); + + // Stream matching one of the DenseMapInfo magic values + EXPECT_THAT_EXPECTED( + create({ // 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'}), + Failed()); +} + +TEST(MinidumpFile, IngoresDummyStreams) { + auto ExpectedFile = create({ + // 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 + }); + 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) { + auto ExpectedFile = create({ + // 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 + }); + 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); +}