diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/postmortem/minidump-new/linux-x86_64.yaml b/lldb/packages/Python/lldbsuite/test/functionalities/postmortem/minidump-new/linux-x86_64.yaml --- a/lldb/packages/Python/lldbsuite/test/functionalities/postmortem/minidump-new/linux-x86_64.yaml +++ b/lldb/packages/Python/lldbsuite/test/functionalities/postmortem/minidump-new/linux-x86_64.yaml @@ -14,7 +14,10 @@ Module Name: '/tmp/test/linux-x86_64' CodeView Record: 4C457042E35C283BC327C28762DB788BF5A4078BE2351448 - Type: Exception - Content: DD740000000000000B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D0040000F8310000 + Thread ID: 0x000074DD + Exception Record: + Exception Code: 0x0000000B + Thread Context: 00000000 - Type: SystemInfo Processor Arch: AMD64 Processor Level: 6 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, @@ -103,6 +104,25 @@ using ThreadListStream = detail::ListStream; using MemoryListStream = detail::ListStream; +/// ExceptionStream minidump stream. +struct ExceptionStream : public Stream { + minidump::ExceptionStream MDExceptionStream; + yaml::BinaryRef ThreadContext; + + ExceptionStream() + : Stream(StreamKind::Exception, minidump::StreamType::Exception), + MDExceptionStream({}) {} + + explicit ExceptionStream(const minidump::ExceptionStream &MDExceptionStream, + ArrayRef ThreadContext) + : Stream(StreamKind::Exception, minidump::StreamType::Exception), + MDExceptionStream(MDExceptionStream), ThreadContext(ThreadContext) {} + + static bool classof(const Stream *S) { + return S->Kind == StreamKind::Exception; + } +}; + /// A structure containing the list of MemoryInfo entries comprising a /// MemoryInfoList stream. struct MemoryInfoListStream : public Stream { @@ -239,6 +259,7 @@ LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::minidump::CPUInfo::ArmInfo) LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::minidump::CPUInfo::OtherInfo) LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::minidump::CPUInfo::X86Info) +LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::minidump::Exception) LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::minidump::MemoryInfo) LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::minidump::VSFixedFileInfo) 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 @@ -69,6 +69,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 +97,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: @@ -274,8 +278,7 @@ mapRequiredHex(IO, "Base of Image", M.Entry.BaseOfImage); mapRequiredHex(IO, "Size of Image", M.Entry.SizeOfImage); mapOptionalHex(IO, "Checksum", M.Entry.Checksum, 0); - IO.mapOptional("Time Date Stamp", M.Entry.TimeDateStamp, - support::ulittle32_t(0)); + mapOptional(IO, "Time Date Stamp", M.Entry.TimeDateStamp, 0); IO.mapRequired("Module Name", M.Name); IO.mapOptional("Version Info", M.Entry.VersionInfo, VSFixedFileInfo()); IO.mapRequired("CodeView Record", M.CvRecord); @@ -367,6 +370,32 @@ 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); + 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); + mapOptional(IO, "Number of Parameters", Exception.NumberParameters, 0); + + for (size_t Index = 0; Index < Exception.MaxParameters; ++Index) { + SmallString<16> Name("Parameter "); + Twine(Index).toVector(Name); + 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); + } +} + void yaml::MappingTraits>::mapping( yaml::IO &IO, std::unique_ptr &S) { StreamType Type; @@ -377,6 +406,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 +438,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: @@ -429,6 +462,18 @@ Stream::create(const Directory &StreamDesc, const object::MinidumpFile &File) { StreamKind Kind = getKind(StreamDesc.Type); switch (Kind) { + case StreamKind::Exception: { + Expected ExpectedExceptionStream = + File.getExceptionStream(); + if (!ExpectedExceptionStream) + return ExpectedExceptionStream.takeError(); + Expected> ExpectedThreadContext = + File.getRawData(ExpectedExceptionStream->ThreadContext); + if (!ExpectedThreadContext) + return ExpectedThreadContext.takeError(); + return std::make_unique(*ExpectedExceptionStream, + *ExpectedThreadContext); + } case StreamKind::MemoryInfoList: { if (auto ExpectedList = File.getMemoryInfoList()) return std::make_unique(*ExpectedList); diff --git a/llvm/test/tools/obj2yaml/basic-minidump.yaml b/llvm/test/tools/obj2yaml/basic-minidump.yaml --- a/llvm/test/tools/obj2yaml/basic-minidump.yaml +++ b/llvm/test/tools/obj2yaml/basic-minidump.yaml @@ -51,6 +51,17 @@ Stack: Start of Memory Range: 0x6C6D6E6F70717273 Content: '7475767778797A7B' + - Type: Exception + Thread ID: 0x7 + Exception Record: + Exception Code: 0x10 + Exception Flags: 0x5 + Exception Record: 0x0102030405060708 + Exception Address: 0x0A0B0C0D0E0F1011 + Number of Parameters: 2 + Parameter 0: 0x22 + Parameter 1: 0x24 + Thread Context: '8182838485868788' - Type: MemoryList Memory Ranges: - Start of Memory Range: 0x7C7D7E7F80818283 @@ -129,6 +140,17 @@ # CHECK-NEXT: Stack: # CHECK-NEXT: Start of Memory Range: 0x6C6D6E6F70717273 # CHECK-NEXT: Content: 7475767778797A7B +# CHECK-NEXT: - Type: Exception +# CHECK-NEXT: Thread ID: 0x00000007 +# CHECK-NEXT: Exception Record: +# CHECK-NEXT: Exception Code: 0x00000010 +# CHECK-NEXT: Exception Flags: 0x00000005 +# CHECK-NEXT: Exception Record: 0x0102030405060708 +# CHECK-NEXT: Exception Address: 0x0A0B0C0D0E0F1011 +# CHECK-NEXT: Number of Parameters: 2 +# CHECK-NEXT: Parameter 0: 0x0000000000000022 +# CHECK-NEXT: Parameter 1: 0x0000000000000024 +# CHECK-NEXT: Thread Context: '8182838485868788' # CHECK-NEXT: - Type: MemoryList # CHECK-NEXT: Memory Ranges: # CHECK-NEXT: - Start of Memory Range: 0x7C7D7E7F80818283 diff --git a/llvm/test/tools/yaml2obj/minidump-exception-missing-parameter.yaml b/llvm/test/tools/yaml2obj/minidump-exception-missing-parameter.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/yaml2obj/minidump-exception-missing-parameter.yaml @@ -0,0 +1,21 @@ +# RUN: not yaml2obj %s -o /dev/null 2>&1 | FileCheck %s + +## Test that we report an error for an ExceptionStream where the specified +## number of parameters is greater than the number of ExceptionInformation +## elements listed. + +--- !minidump +Streams: + - Type: Exception + Thread ID: 0x7 + Exception Record: + Exception Code: 0x23 + Exception Flags: 0x5 + Exception Record: 0x0102030405060708 + Exception Address: 0x0a0b0c0d0e0f1011 + Number of Parameters: 4 + Parameter 0: 0x99 + Parameter 1: 0x23 + Parameter 2: 0x42 +# CHECK: error: missing required key 'Parameter 3' + Thread Context: 3DeadBeefDefacedABadCafe)"); 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,200 @@ (ArrayRef{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), makeArrayRef(SysInfo.CPU.Other.ProcessorFeatures)); } + +// Test that we can parse a normal-looking ExceptionStream. +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 of 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 that we can parse an exception stream with no ExceptionInformation. +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 that we can parse an ExceptionStream where the stated number of +// parameters is greater than the actual size of the ExceptionInformation +// array. +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 of 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 + 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(0x8u, Stream.ThreadId); + const minidump::Exception &Exception = Stream.ExceptionRecord; + EXPECT_EQ(0x0u, Exception.ExceptionCode); + EXPECT_EQ(0x0u, Exception.ExceptionFlags); + EXPECT_EQ(0x00u, Exception.ExceptionRecord); + EXPECT_EQ(0x0u, Exception.ExceptionAddress); + EXPECT_EQ(16u, Exception.NumberParameters); + EXPECT_EQ(0x0u, Exception.ExceptionInformation[0]); + for (int Index = 1; Index < 15; ++Index) { + EXPECT_EQ(0x110u - Index * 0x11, Exception.ExceptionInformation[Index]); + } + + 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 that we can parse an ExceptionStream where the number of +// ExceptionInformation parameters provided is greater than the +// specified Number of Parameters. +TEST(MinidumpYAML, ExceptionStream_ExtraParameter) { + 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 of Parameters: 2 + Parameter 0: 0x99 + Parameter 1: 0x23 + Parameter 2: 0x42 + 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(0x99u, Exception.ExceptionInformation[0]); + EXPECT_EQ(0x23u, Exception.ExceptionInformation[1]); + EXPECT_EQ(0x42u, Exception.ExceptionInformation[2]); + + 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); +}