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, MemoryList, ModuleList, RawContent, @@ -137,6 +138,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) @@ -198,6 +219,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::MemoryList: DataEnd = layout(File, cast(S)); break; 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 @@ -69,6 +69,8 @@ Stream::StreamKind Stream::getKind(StreamType Type) { switch (Type) { + case StreamType::Exception: + return StreamKind::Exception; case StreamType::MemoryList: return StreamKind::MemoryList; case StreamType::ModuleList: @@ -93,6 +95,8 @@ std::unique_ptr Stream::create(StreamType Type) { StreamKind Kind = getKind(Type); switch (Kind) { + case StreamKind::Exception: + return std::make_unique(); case StreamKind::MemoryList: return std::make_unique(); case StreamKind::ModuleList: @@ -326,6 +330,132 @@ 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 making Exception's ThreadContext required + // when the Exception stream is processed first). + IO.mapRequired("Thread Context", Stream.ThreadContext); +} + +namespace { + +// The minidump Exception object has an array of parameters called +// ExceptionInformation. It is allocated as a fixed-size array +// of length Exception::MaxParameters, but the Exception object +// also has a field NumberParameters that indicates how many of +// the parameters are actually significant in that instance. +// This ExceptionInformation type is a helper type which wraps +// the ExceptionInformation and for which we define SequenceTraits +// so that the YAML encoding is a list of parameters whose length +// is mapped to the NumberParameters field. It is a RAII/RRID +// type which commits the changes when it goes out of scope, +// similar to yaml::MappingNormalization. +struct ExceptionInformation { + using value_type = yaml::Hex64; + + // We need a reference to the Exception object for its + // NumberParameters and ExceptionInformation fields. + minidump::Exception &Exception; + + // We need a copy of the parameters to yamlize them as hex + value_type Parameters[Exception::MaxParameters]; + size_t NumberParameters; + + // Because we keep a copy of the parameters, which is what we + // directly map with the yaml::IO, we need to know whether we + // are inputting or outputting yaml to be able to copy the + // parameters in the constructor/destructor appropriately. + bool Outputting; + + explicit ExceptionInformation(minidump::Exception &Exception, bool Outputting) + : Exception(Exception), Outputting(Outputting) { + if (Outputting) { + // Populate the local copy of Parameters from the Exception record. + NumberParameters = Exception.NumberParameters; + // Make sure invalid input doesn't corrupt memory. + std::size_t NumSafeParameters = + std::max(NumberParameters, minidump::Exception::MaxParameters); + support::ulittle64_t *Begin = Exception.ExceptionInformation; + support::ulittle64_t *End = Begin + NumSafeParameters; + std::copy(Begin, End, Parameters); + } else { + // Initialize NumberParameters to zero and it will be + // updated in SequenceTraits::element. + NumberParameters = 0; + } + } + + ~ExceptionInformation() { + if (!Outputting) { + // Commit the local copy of Parameters to the Exception record. + Exception.NumberParameters = NumberParameters; + // Make sure invalid input doesn't corrupt memory. + std::size_t NumSafeParameters = + std::max(NumberParameters, minidump::Exception::MaxParameters); + value_type *Begin = Parameters; + value_type *End = Parameters + NumSafeParameters; + std::copy(Begin, End, Exception.ExceptionInformation); + } + } + +private: + // Neither copyable nor movable. + ExceptionInformation() = delete; + ExceptionInformation(const ExceptionInformation &Other) = delete; + ExceptionInformation(ExceptionInformation &&Other) = delete; + ExceptionInformation &operator=(const ExceptionInformation &Other) = delete; + ExceptionInformation &operator=(ExceptionInformation &&Other) = delete; +}; +} + +namespace llvm { +namespace yaml { + +template <> struct SequenceTraits { + static size_t size(IO &io, ExceptionInformation &seq) { + return seq.Exception.NumberParameters; + } + + static ExceptionInformation::value_type & + element(IO &io, ExceptionInformation &seq, size_t index) { + if (index >= seq.NumberParameters) { + seq.NumberParameters = index + 1; + } + if (index > minidump::Exception::MaxParameters) { + io.setError("Too many exception parameters"); + return seq.Parameters[minidump::Exception::MaxParameters - 1]; + } + return seq.Parameters[index]; + } + + static const bool flow = true; +}; +} // yaml +} // llvm + +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); + ExceptionInformation Info(Exception, IO.outputting()); + IO.mapRequired("Exception Information", Info); +} + +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; @@ -336,6 +466,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::MemoryList: streamMapping(IO, llvm::cast(*S)); break; @@ -362,6 +495,7 @@ switch (S->Kind) { case MinidumpYAML::Stream::StreamKind::RawContent: return streamValidate(cast(*S)); + case MinidumpYAML::Stream::StreamKind::Exception: case MinidumpYAML::Stream::StreamKind::MemoryList: case MinidumpYAML::Stream::StreamKind::ModuleList: case MinidumpYAML::Stream::StreamKind::SystemInfo: @@ -430,6 +564,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,107 @@ (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 + Exception Information: [ 0x22, 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 + Exception Information: [ ] + 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 + Exception Information: [ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 ] + 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()); +}