diff --git a/llvm/include/llvm/ObjectYAML/MinidumpYAML.h b/llvm/include/llvm/ObjectYAML/MinidumpYAML.h --- a/llvm/include/llvm/ObjectYAML/MinidumpYAML.h +++ b/llvm/include/llvm/ObjectYAML/MinidumpYAML.h @@ -26,6 +26,7 @@ /// from Types to Kinds is fixed and given by the static getKind function. struct Stream { enum class StreamKind { + Exception, MemoryInfoList, MemoryList, ModuleList, @@ -158,6 +159,26 @@ } }; +/// ExceptionStream minidump stream. +struct ExceptionStream : public Stream { + minidump::ExceptionStream MDExceptionStream; + yaml::BinaryRef ThreadContext; + + explicit ExceptionStream(const minidump::ExceptionStream &MDExceptionStream, + ArrayRef ThreadContext) + : Stream(StreamKind::Exception, minidump::StreamType::Exception), + MDExceptionStream(MDExceptionStream), ThreadContext(ThreadContext) {} + + ExceptionStream() + : Stream(StreamKind::Exception, minidump::StreamType::Exception) { + memset(&MDExceptionStream, 0, sizeof(minidump::ExceptionStream)); + } + + static bool classof(const Stream *S) { + return S->Kind == StreamKind::Exception; + } +}; + /// A StringRef, which is printed using YAML block notation. LLVM_YAML_STRONG_TYPEDEF(StringRef, BlockStringRef) @@ -219,6 +240,11 @@ static StringRef validate(IO &IO, std::unique_ptr &S); }; +template <> struct MappingTraits { + static void mapping(IO &IO, minidump::Exception &Exception); + static StringRef validate(IO &IO, minidump::Exception &Exception); +}; + template <> struct MappingContextTraits { static void mapping(IO &IO, minidump::MemoryDescriptor &Memory, BinaryRef &Content); diff --git a/llvm/lib/ObjectYAML/MinidumpEmitter.cpp b/llvm/lib/ObjectYAML/MinidumpEmitter.cpp --- a/llvm/lib/ObjectYAML/MinidumpEmitter.cpp +++ b/llvm/lib/ObjectYAML/MinidumpEmitter.cpp @@ -118,6 +118,23 @@ support::ulittle32_t(File.allocateBytes(Data))}; } +static size_t layout(BlobAllocator &File, MinidumpYAML::ExceptionStream &S) { + File.allocateObject(S.MDExceptionStream); + + size_t DataEnd = File.tell(); + + // Lay out the thread context data, (which is not a part of the stream). + // TODO: This usually (always?) matches the thread context of the + // corresponding thread, and may overlap memory regions as well. We could + // add a level of indirection to the MinidumpYAML format (like an array of + // Blobs that the LocationDescriptors index into) to be able to distinguish + // the cases where location descriptions overlap vs happen to reference + // identical data. + S.MDExceptionStream.ThreadContext = layout(File, S.ThreadContext); + + return DataEnd; +} + static void layout(BlobAllocator &File, MemoryListStream::entry_type &Range) { Range.Entry.Memory = layout(File, Range.Content); } @@ -158,6 +175,9 @@ Result.Location.RVA = File.tell(); Optional DataEnd; switch (S.Kind) { + case Stream::StreamKind::Exception: + DataEnd = layout(File, cast(S)); + break; case Stream::StreamKind::MemoryInfoList: { MemoryInfoListStream &InfoList = cast(S); File.allocateNewObject( diff --git a/llvm/lib/ObjectYAML/MinidumpYAML.cpp b/llvm/lib/ObjectYAML/MinidumpYAML.cpp --- a/llvm/lib/ObjectYAML/MinidumpYAML.cpp +++ b/llvm/lib/ObjectYAML/MinidumpYAML.cpp @@ -8,6 +8,7 @@ #include "llvm/ObjectYAML/MinidumpYAML.h" #include "llvm/Support/Allocator.h" +#include "llvm/Support/FormatVariadic.h" using namespace llvm; using namespace llvm::MinidumpYAML; @@ -69,6 +70,8 @@ Stream::StreamKind Stream::getKind(StreamType Type) { switch (Type) { + case StreamType::Exception: + return StreamKind::Exception; case StreamType::MemoryInfoList: return StreamKind::MemoryInfoList; case StreamType::MemoryList: @@ -95,6 +98,8 @@ std::unique_ptr Stream::create(StreamType Type) { StreamKind Kind = getKind(Type); switch (Kind) { + case StreamKind::Exception: + return std::make_unique(); case StreamKind::MemoryInfoList: return std::make_unique(); case StreamKind::MemoryList: @@ -367,6 +372,46 @@ IO.mapRequired("Threads", Stream.Entries); } +static void streamMapping(yaml::IO &IO, MinidumpYAML::ExceptionStream &Stream) { + mapRequiredHex(IO, "Thread ID", Stream.MDExceptionStream.ThreadId); + IO.mapRequired("Exception Record", Stream.MDExceptionStream.ExceptionRecord); + // TODO: We could provide a reasonable default for ThreadContext by searching + // the Thread stream for a thread with the given ID and using its Context. + // That would require a couple changes: + // 1. We'd need to pass the whole dump around as a context argument. + // 2. We'd need to ensure that the Thread stream got processed before + // the Exception stream (or make Exception's ThreadContext required + // when the Exception stream is processed before the Thread stream). + IO.mapRequired("Thread Context", Stream.ThreadContext); +} + +void yaml::MappingTraits::mapping( + yaml::IO &IO, minidump::Exception &Exception) { + mapRequiredHex(IO, "Exception Code", Exception.ExceptionCode); + mapOptionalHex(IO, "Exception Flags", Exception.ExceptionFlags, 0); + mapOptionalHex(IO, "Exception Record", Exception.ExceptionRecord, 0); + mapOptionalHex(IO, "Exception Address", Exception.ExceptionAddress, 0); + IO.mapOptional("Number Parameters", Exception.NumberParameters, + support::ulittle32_t(0u)); + + for (size_t Index = 0; Index < Exception.MaxParameters; ++Index) { + SmallString<16> Name = formatv("Parameter {0}", Index); + support::ulittle64_t &Field = Exception.ExceptionInformation[Index]; + + if (Index < Exception.NumberParameters) + mapRequiredHex(IO, Name.c_str(), Field); + else + mapOptionalHex(IO, Name.c_str(), Field, 0); + } +} + +StringRef yaml::MappingTraits::validate( + yaml::IO &IO, minidump::Exception &Exception) { + if (Exception.NumberParameters > Exception::MaxParameters) + return "Exception reports too many parameters"; + return ""; +} + void yaml::MappingTraits>::mapping( yaml::IO &IO, std::unique_ptr &S) { StreamType Type; @@ -377,6 +422,9 @@ if (!IO.outputting()) S = MinidumpYAML::Stream::create(Type); switch (S->Kind) { + case MinidumpYAML::Stream::StreamKind::Exception: + streamMapping(IO, llvm::cast(*S)); + break; case MinidumpYAML::Stream::StreamKind::MemoryInfoList: streamMapping(IO, llvm::cast(*S)); break; @@ -406,6 +454,7 @@ switch (S->Kind) { case MinidumpYAML::Stream::StreamKind::RawContent: return streamValidate(cast(*S)); + case MinidumpYAML::Stream::StreamKind::Exception: case MinidumpYAML::Stream::StreamKind::MemoryInfoList: case MinidumpYAML::Stream::StreamKind::MemoryList: case MinidumpYAML::Stream::StreamKind::ModuleList: @@ -481,6 +530,17 @@ return std::make_unique(*ExpectedInfo, std::move(*ExpectedCSDVersion)); } + case StreamKind::Exception: { + auto ExpectedExceptionStream = File.getExceptionStream(); + if (!ExpectedExceptionStream) + return ExpectedExceptionStream.takeError(); + auto ExpectedThreadContext = + File.getRawData(ExpectedExceptionStream->ThreadContext); + if (!ExpectedThreadContext) + return ExpectedThreadContext.takeError(); + return std::make_unique(*ExpectedExceptionStream, + *ExpectedThreadContext); + } case StreamKind::TextContent: return std::make_unique( StreamDesc.Type, toStringRef(File.getRawStream(StreamDesc))); diff --git a/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp b/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp --- a/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp +++ b/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp @@ -139,3 +139,150 @@ (ArrayRef{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), makeArrayRef(SysInfo.CPU.Other.ProcessorFeatures)); } + +TEST(MinidumpYAML, ExceptionStream) { + SmallString<0> Storage; + auto ExpectedFile = toBinary(Storage, R"( +--- !minidump +Streams: + - Type: Exception + Thread ID: 0x7 + Exception Record: + Exception Code: 0x23 + Exception Flags: 0x5 + Exception Record: 0x0102030405060708 + Exception Address: 0x0a0b0c0d0e0f1011 + Number Parameters: 2 + Parameter 0: 0x22 + Parameter 1: 0x24 + Thread Context: 3DeadBeefDefacedABadCafe)"); + ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded()); + object::MinidumpFile &File = **ExpectedFile; + + ASSERT_EQ(1u, File.streams().size()); + + Expected ExpectedStream = + File.getExceptionStream(); + + ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded()); + + const minidump::ExceptionStream &Stream = *ExpectedStream; + EXPECT_EQ(0x7u, Stream.ThreadId); + const minidump::Exception &Exception = Stream.ExceptionRecord; + EXPECT_EQ(0x23u, Exception.ExceptionCode); + EXPECT_EQ(0x5u, Exception.ExceptionFlags); + EXPECT_EQ(0x0102030405060708u, Exception.ExceptionRecord); + EXPECT_EQ(0x0a0b0c0d0e0f1011u, Exception.ExceptionAddress); + EXPECT_EQ(2u, Exception.NumberParameters); + EXPECT_EQ(0x22u, Exception.ExceptionInformation[0]); + EXPECT_EQ(0x24u, Exception.ExceptionInformation[1]); + + Expected> ExpectedContext = + File.getRawData(Stream.ThreadContext); + ASSERT_THAT_EXPECTED(ExpectedContext, Succeeded()); + EXPECT_EQ((ArrayRef{0x3d, 0xea, 0xdb, 0xee, 0xfd, 0xef, 0xac, 0xed, + 0xab, 0xad, 0xca, 0xfe}), + *ExpectedContext); +} + +TEST(MinidumpYAML, ExceptionStream_NoParameters) { + SmallString<0> Storage; + auto ExpectedFile = toBinary(Storage, R"( +--- !minidump +Streams: + - Type: Exception + Thread ID: 0x7 + Exception Record: + Exception Code: 0x23 + Exception Flags: 0x5 + Exception Record: 0x0102030405060708 + Exception Address: 0x0a0b0c0d0e0f1011 + Thread Context: 3DeadBeefDefacedABadCafe)"); + ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded()); + object::MinidumpFile &File = **ExpectedFile; + + ASSERT_EQ(1u, File.streams().size()); + + Expected ExpectedStream = + File.getExceptionStream(); + + ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded()); + + const minidump::ExceptionStream &Stream = *ExpectedStream; + EXPECT_EQ(0x7u, Stream.ThreadId); + const minidump::Exception &Exception = Stream.ExceptionRecord; + EXPECT_EQ(0x23u, Exception.ExceptionCode); + EXPECT_EQ(0x5u, Exception.ExceptionFlags); + EXPECT_EQ(0x0102030405060708u, Exception.ExceptionRecord); + EXPECT_EQ(0x0a0b0c0d0e0f1011u, Exception.ExceptionAddress); + EXPECT_EQ(0u, Exception.NumberParameters); + + Expected> ExpectedContext = + File.getRawData(Stream.ThreadContext); + ASSERT_THAT_EXPECTED(ExpectedContext, Succeeded()); + EXPECT_EQ((ArrayRef{0x3d, 0xea, 0xdb, 0xee, 0xfd, 0xef, 0xac, 0xed, + 0xab, 0xad, 0xca, 0xfe}), + *ExpectedContext); +} + +TEST(MinidumpYAML, ExceptionStream_TooManyParameters) { + SmallString<0> Storage; + auto ExpectedFile = toBinary(Storage, R"( +--- !minidump +Streams: + - Type: Exception + Thread ID: 0x8 + Exception Record: + Exception Code: 0 + Number Parameters: 16 + Parameter 0: 0x0 + Parameter 1: 0xff + Parameter 2: 0xee + Parameter 3: 0xdd + Parameter 4: 0xcc + Parameter 5: 0xbb + Parameter 6: 0xaa + Parameter 7: 0x99 + Parameter 8: 0x88 + Parameter 9: 0x77 + Parameter 10: 0x66 + Parameter 11: 0x55 + Parameter 12: 0x44 + Parameter 13: 0x33 + Parameter 14: 0x22 + Parameter 15: 0x11 + Thread Context: 3DeadBeefDefacedABadCafe)"); + ASSERT_FALSE(ExpectedFile); + auto Unhandled = + handleErrors(ExpectedFile.takeError(), [](const StringError &error) { + EXPECT_EQ(static_cast(std::errc::invalid_argument), + error.convertToErrorCode().value()); + }); + EXPECT_THAT_ERROR(std::move(Unhandled), Succeeded()); +} + +TEST(MinidumpYAML, ExceptionStream_MissingParameter) { + SmallString<0> Storage; + auto ExpectedFile = toBinary(Storage, R"( +--- !minidump +Streams: + - Type: Exception + Thread ID: 0x7 + Exception Record: + Exception Code: 0x23 + Exception Flags: 0x5 + Exception Record: 0x0102030405060708 + Exception Address: 0x0a0b0c0d0e0f1011 + NumberParameters: 4 + Parameter 0: 0x99 + Parameter 1: 0x23 + Parameter 2: 0x42 + Thread Context: 3DeadBeefDefacedABadCafe)"); + ASSERT_FALSE(ExpectedFile); + auto Unhandled = + handleErrors(ExpectedFile.takeError(), [](const StringError &error) { + EXPECT_EQ(static_cast(std::errc::invalid_argument), + error.convertToErrorCode().value()); + }); + EXPECT_THAT_ERROR(std::move(Unhandled), Succeeded()); +}