[llvm] [Minidump] Support multiple exceptions in a minidump (PR #107319)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 4 15:13:23 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-binary-utilities

Author: Jacob Lalonde (Jlalond)

<details>
<summary>Changes</summary>

A fork of #<!-- -->97470, splitting off the LLVM changes from the LLDB specific changes. This patch enables a minidump file to have multiple exceptions, exposed via an iterator of Expected streams.

---
Full diff: https://github.com/llvm/llvm-project/pull/107319.diff


5 Files Affected:

- (modified) llvm/include/llvm/Object/Minidump.h (+92-9) 
- (modified) llvm/lib/Object/Minidump.cpp (+15-1) 
- (modified) llvm/lib/ObjectYAML/MinidumpYAML.cpp (+1-1) 
- (modified) llvm/unittests/Object/MinidumpTest.cpp (+4-2) 
- (modified) llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp (+58-8) 


``````````diff
diff --git a/llvm/include/llvm/Object/Minidump.h b/llvm/include/llvm/Object/Minidump.h
index 65ad6b171c2dc1..cf3d88889e900e 100644
--- a/llvm/include/llvm/Object/Minidump.h
+++ b/llvm/include/llvm/Object/Minidump.h
@@ -83,13 +83,16 @@ class MinidumpFile : public Binary {
     return getListStream<minidump::Thread>(minidump::StreamType::ThreadList);
   }
 
-  /// Returns the contents of the Exception stream.  An error is returned if the
-  /// file does not contain this stream, or the stream is smaller than the size
-  /// of the ExceptionStream structure.  The internal consistency of the stream
-  /// is not checked in any way.
-  Expected<const minidump::ExceptionStream &> getExceptionStream() const {
-    return getStream<minidump::ExceptionStream>(
-        minidump::StreamType::Exception);
+  /// Returns the contents of the Exception stream. An error is returned if the
+  /// associated stream is smaller than the size of the ExceptionStream
+  /// structure. Or the directory supplied is not of kind exception stream.
+  Expected<const minidump::ExceptionStream &>
+  getExceptionStream(minidump::Directory Directory) const {
+    if (Directory.Type != minidump::StreamType::Exception) {
+      return createError("Not an exception stream");
+    }
+
+    return getStreamFromDirectory<minidump::ExceptionStream>(Directory);
   }
 
   /// Returns the list of descriptors embedded in the MemoryList stream. The
@@ -216,8 +219,71 @@ class MinidumpFile : public Binary {
     bool IsEnd;
   };
 
+class ExceptionStreamsIterator {
+  public:
+
+    static ExceptionStreamsIterator begin(ArrayRef<minidump::Directory> Streams, const MinidumpFile *File) {
+      return ExceptionStreamsIterator(Streams, File);
+    }
+
+    static ExceptionStreamsIterator end() {
+      return ExceptionStreamsIterator();
+    }
+
+    bool operator==(const ExceptionStreamsIterator &R) const {
+      return Streams.empty() && R.Streams.empty();
+    }
+
+    bool operator!=(const ExceptionStreamsIterator &R) const { return !(*this == R); }
+
+    Expected<const minidump::ExceptionStream &>
+    operator*() {
+      return ReadCurrent();
+    }
+
+    Expected<const minidump::ExceptionStream &>
+    operator->() {
+      return ReadCurrent();
+    }
+
+    ExceptionStreamsIterator &
+    operator++ () {
+      if (!Streams.empty())
+        Streams = Streams.drop_front();
+
+
+      return *this;
+    }
+
+  private:
+    ExceptionStreamsIterator(ArrayRef<minidump::Directory> Streams, const MinidumpFile *File)
+      : Streams(Streams), File(File) {}
+
+    ExceptionStreamsIterator() : Streams(ArrayRef<minidump::Directory>()), File(nullptr) {}
+
+    ArrayRef<minidump::Directory> Streams;
+    const MinidumpFile *File;
+
+    Expected<const minidump::ExceptionStream&> ReadCurrent() {
+      assert(!Streams.empty());
+      Expected<const minidump::ExceptionStream &> ExceptionStream =
+          File->getExceptionStream(Streams.front());
+      if (!ExceptionStream)
+        return ExceptionStream.takeError();
+      
+      return ExceptionStream;
+    }
+  };
+
   using FallibleMemory64Iterator = llvm::fallible_iterator<Memory64Iterator>;
 
+  /// Returns an iterator that reads each exception stream independently. The 
+  /// contents of the exception strema are not validated before being read, an 
+  /// error will be returned if the stream is not large enough to contain an
+  /// exception stream, or if the stream points beyond the end of the file.
+  iterator_range<ExceptionStreamsIterator>
+  getExceptionStreams() const;
+
   /// Returns an iterator that pairs each descriptor with it's respective
   /// content from the Memory64List stream. An error is returned if the file
   /// does not contain a Memory64List stream, or if the descriptor data is
@@ -256,14 +322,21 @@ class MinidumpFile : public Binary {
 
   MinidumpFile(MemoryBufferRef Source, const minidump::Header &Header,
                ArrayRef<minidump::Directory> Streams,
-               DenseMap<minidump::StreamType, std::size_t> StreamMap)
+               DenseMap<minidump::StreamType, std::size_t> StreamMap,
+               std::vector<minidump::Directory> ExceptionStreams)
       : Binary(ID_Minidump, Source), Header(Header), Streams(Streams),
-        StreamMap(std::move(StreamMap)) {}
+        StreamMap(std::move(StreamMap)), ExceptionStreams(std::move(ExceptionStreams)) {}
 
   ArrayRef<uint8_t> 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 <typename T>
+  Expected<const T &>
+  getStreamFromDirectory(minidump::Directory Directory) const;
+
   /// 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 <typename T>
@@ -277,8 +350,18 @@ class MinidumpFile : public Binary {
   const minidump::Header &Header;
   ArrayRef<minidump::Directory> Streams;
   DenseMap<minidump::StreamType, std::size_t> StreamMap;
+  std::vector<minidump::Directory> ExceptionStreams;
 };
 
+template <typename T>
+Expected<const T &>
+MinidumpFile::getStreamFromDirectory(minidump::Directory Directory) const {
+  ArrayRef<uint8_t> Stream = getRawStream(Directory);
+  if (Stream.size() >= sizeof(T))
+    return *reinterpret_cast<const T *>(Stream.data());
+  return createEOFError();
+}
+
 template <typename T>
 Expected<const T &> MinidumpFile::getStream(minidump::StreamType Type) const {
   if (std::optional<ArrayRef<uint8_t>> Stream = getRawStream(Type)) {
diff --git a/llvm/lib/Object/Minidump.cpp b/llvm/lib/Object/Minidump.cpp
index 93b2e2cecd1053..f334cd2a9a2e71 100644
--- a/llvm/lib/Object/Minidump.cpp
+++ b/llvm/lib/Object/Minidump.cpp
@@ -53,6 +53,12 @@ Expected<std::string> MinidumpFile::getString(size_t Offset) const {
   return Result;
 }
 
+iterator_range<llvm::object::MinidumpFile::ExceptionStreamsIterator>
+MinidumpFile::getExceptionStreams() const {
+  return make_range(ExceptionStreamsIterator::begin(ExceptionStreams, this),
+                    ExceptionStreamsIterator::end());
+}
+
 Expected<iterator_range<MinidumpFile::MemoryInfoIterator>>
 MinidumpFile::getMemoryInfoList() const {
   std::optional<ArrayRef<uint8_t>> Stream =
@@ -128,6 +134,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
     return ExpectedStreams.takeError();
 
   DenseMap<StreamType, std::size_t> StreamMap;
+  std::vector<Directory> ExceptionStreams;
   for (const auto &StreamDescriptor : llvm::enumerate(*ExpectedStreams)) {
     StreamType Type = StreamDescriptor.value().Type;
     const LocationDescriptor &Loc = StreamDescriptor.value().Location;
@@ -143,6 +150,13 @@ MinidumpFile::create(MemoryBufferRef Source) {
       continue;
     }
 
+    // Exceptions can be treated as a special case of streams. Other streams
+    // represent a list of entities, but exceptions are unique per stream.
+    if (Type == StreamType::Exception) {
+      ExceptionStreams.push_back(StreamDescriptor.value());
+      continue;
+    }
+
     if (Type == DenseMapInfo<StreamType>::getEmptyKey() ||
         Type == DenseMapInfo<StreamType>::getTombstoneKey())
       return createError("Cannot handle one of the minidump streams");
@@ -153,7 +167,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
   }
 
   return std::unique_ptr<MinidumpFile>(
-      new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap)));
+      new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap), ExceptionStreams));
 }
 
 iterator_range<MinidumpFile::FallibleMemory64Iterator>
diff --git a/llvm/lib/ObjectYAML/MinidumpYAML.cpp b/llvm/lib/ObjectYAML/MinidumpYAML.cpp
index 10b8676b5c4cfb..1818823180b7d4 100644
--- a/llvm/lib/ObjectYAML/MinidumpYAML.cpp
+++ b/llvm/lib/ObjectYAML/MinidumpYAML.cpp
@@ -499,7 +499,7 @@ Stream::create(const Directory &StreamDesc, const object::MinidumpFile &File) {
   switch (Kind) {
   case StreamKind::Exception: {
     Expected<const minidump::ExceptionStream &> ExpectedExceptionStream =
-        File.getExceptionStream();
+        File.getExceptionStream(StreamDesc);
     if (!ExpectedExceptionStream)
       return ExpectedExceptionStream.takeError();
     Expected<ArrayRef<uint8_t>> ExpectedThreadContext =
diff --git a/llvm/unittests/Object/MinidumpTest.cpp b/llvm/unittests/Object/MinidumpTest.cpp
index d2d9f115bb94a9..6dcdccc4dea81e 100644
--- a/llvm/unittests/Object/MinidumpTest.cpp
+++ b/llvm/unittests/Object/MinidumpTest.cpp
@@ -751,8 +751,10 @@ TEST(MinidumpFile, getExceptionStream) {
   auto ExpectedFile = create(Data);
   ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
   const MinidumpFile &File = **ExpectedFile;
-  Expected<const minidump::ExceptionStream &> ExpectedStream =
-      File.getExceptionStream();
+  auto ExceptionIterator =
+      File.getExceptionStreams().begin();
+
+  Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
   ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
   EXPECT_EQ(0x04030201u, ExpectedStream->ThreadId);
   const minidump::Exception &Exception = ExpectedStream->ExceptionRecord;
diff --git a/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp b/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp
index a8b8da925d21d9..6b2963902a24b8 100644
--- a/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp
+++ b/llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp
@@ -162,8 +162,10 @@ TEST(MinidumpYAML, ExceptionStream) {
 
   ASSERT_EQ(1u, File.streams().size());
 
-  Expected<const minidump::ExceptionStream &> ExpectedStream =
-      File.getExceptionStream();
+  auto ExceptionIterator =
+      File.getExceptionStreams().begin();
+
+  Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
 
   ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
 
@@ -205,9 +207,10 @@ TEST(MinidumpYAML, ExceptionStream_NoParameters) {
 
   ASSERT_EQ(1u, File.streams().size());
 
-  Expected<const minidump::ExceptionStream &> ExpectedStream =
-      File.getExceptionStream();
+  auto ExceptionIterator =
+      File.getExceptionStreams().begin();
 
+  Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
   ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
 
   const minidump::ExceptionStream &Stream = *ExpectedStream;
@@ -261,8 +264,10 @@ TEST(MinidumpYAML, ExceptionStream_TooManyParameters) {
 
   ASSERT_EQ(1u, File.streams().size());
 
-  Expected<const minidump::ExceptionStream &> ExpectedStream =
-      File.getExceptionStream();
+  auto ExceptionIterator =
+      File.getExceptionStreams().begin();
+
+  Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
 
   ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
 
@@ -312,8 +317,10 @@ TEST(MinidumpYAML, ExceptionStream_ExtraParameter) {
 
   ASSERT_EQ(1u, File.streams().size());
 
-  Expected<const minidump::ExceptionStream &> ExpectedStream =
-      File.getExceptionStream();
+  auto ExceptionIterator =
+      File.getExceptionStreams().begin();
+
+  Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
 
   ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
 
@@ -398,4 +405,47 @@ TEST(MinidumpYAML, MemoryRegion_64bit) {
   ASSERT_THAT(*DescTwoExpectedContentSlice, arrayRefFromStringRef("world"));
 
   ASSERT_EQ(Iterator, MemoryList.end());
+
+}
+
+// Test that we can parse multiple exception streams.
+TEST(MinidumpYAML, ExceptionStream_MultipleExceptions) {
+  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
+  - Type:            Exception
+    Thread ID:  0x5
+    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(2u, File.streams().size());
+
+  size_t count = 0;
+  for (auto exception_stream : File.getExceptionStreams()) {
+    count++;
+    ASSERT_THAT_EXPECTED(exception_stream, Succeeded());
+    ASSERT_THAT(0x23u, exception_stream->ExceptionRecord.ExceptionCode);
+  }
+
+  ASSERT_THAT(2u, count);
 }

``````````

</details>


https://github.com/llvm/llvm-project/pull/107319


More information about the llvm-commits mailing list