[llvm] [Minidump] Support multiple exceptions in a minidump (PR #107319)
Jacob Lalonde via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 4 15:12:50 PDT 2024
https://github.com/Jlalond created https://github.com/llvm/llvm-project/pull/107319
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.
>From 70f94e152ba2a6826a3c4f8071b5e0ee2bb81d2d Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Wed, 4 Sep 2024 14:06:04 -0700
Subject: [PATCH 1/3] Cherry-pick llvm only changes from minidump multiple
exceptions PR.
---
llvm/include/llvm/Object/Minidump.h | 39 +++++++++++++++++++++++-----
llvm/lib/Object/Minidump.cpp | 31 ++++++++++++++++++++++
llvm/lib/ObjectYAML/MinidumpYAML.cpp | 2 +-
3 files changed, 64 insertions(+), 8 deletions(-)
diff --git a/llvm/include/llvm/Object/Minidump.h b/llvm/include/llvm/Object/Minidump.h
index 65ad6b171c2dc1..eb6f4a2714340a 100644
--- a/llvm/include/llvm/Object/Minidump.h
+++ b/llvm/include/llvm/Object/Minidump.h
@@ -83,15 +83,25 @@ 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 contents of the Exception streams. An error is returned if
+ /// any of the streams are smaller than the size of the ExceptionStream
+ /// structure. The internal consistency of the stream is not checked in any
+ /// way.
+ Expected<std::vector<const minidump::ExceptionStream *>>
+ getExceptionStreams() const;
+
/// Returns the list of descriptors embedded in the MemoryList stream. The
/// descriptors provide the content of interesting regions of memory at the
/// time the minidump was taken. An error is returned if the file does not
@@ -264,6 +274,12 @@ class MinidumpFile : public Binary {
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>
@@ -279,6 +295,15 @@ class MinidumpFile : public Binary {
DenseMap<minidump::StreamType, std::size_t> StreamMap;
};
+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..cd78f1b10ca17a 100644
--- a/llvm/lib/Object/Minidump.cpp
+++ b/llvm/lib/Object/Minidump.cpp
@@ -53,6 +53,27 @@ Expected<std::string> MinidumpFile::getString(size_t Offset) const {
return Result;
}
+Expected<std::vector<const minidump::ExceptionStream *>>
+MinidumpFile::getExceptionStreams() const {
+
+ std::vector<const minidump::ExceptionStream *> exceptionStreamList;
+ for (const auto &directory : Streams) {
+ if (directory.Type == StreamType::Exception) {
+ llvm::Expected<minidump::ExceptionStream *> ExpectedStream =
+ getStreamFromDirectory<minidump::ExceptionStream *>(directory);
+ if (!ExpectedStream)
+ return ExpectedStream.takeError();
+
+ exceptionStreamList.push_back(ExpectedStream.get());
+ }
+ }
+
+ if (exceptionStreamList.empty())
+ return createError("No exception streams found");
+
+ return exceptionStreamList;
+}
+
Expected<iterator_range<MinidumpFile::MemoryInfoIterator>>
MinidumpFile::getMemoryInfoList() const {
std::optional<ArrayRef<uint8_t>> Stream =
@@ -128,6 +149,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
return ExpectedStreams.takeError();
DenseMap<StreamType, std::size_t> StreamMap;
+ std::vector<std::size_t> ExceptionStreams;
for (const auto &StreamDescriptor : llvm::enumerate(*ExpectedStreams)) {
StreamType Type = StreamDescriptor.value().Type;
const LocationDescriptor &Loc = StreamDescriptor.value().Location;
@@ -143,6 +165,15 @@ MinidumpFile::create(MemoryBufferRef Source) {
continue;
}
+ // We treat exceptions differently here because the LLDB minidump
+ // makes some assumptions about uniqueness, all the streams other than
+ // exceptions are lists. But exceptions are not a list, they are single
+ // streams that point back to their thread So we will omit them here, and
+ // will find them when needed in the MinidumpFile.
+ if (Type == StreamType::Exception) {
+ continue;
+ }
+
if (Type == DenseMapInfo<StreamType>::getEmptyKey() ||
Type == DenseMapInfo<StreamType>::getTombstoneKey())
return createError("Cannot handle one of the minidump streams");
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 =
>From a33bb5902f99f3e0ec3ac01f9dca7c1781af6404 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Wed, 4 Sep 2024 14:45:58 -0700
Subject: [PATCH 2/3] Add minidump exception iterator
---
llvm/include/llvm/Object/Minidump.h | 76 +++++++++++++++++++++++++----
llvm/lib/Object/Minidump.cpp | 27 +++-------
2 files changed, 73 insertions(+), 30 deletions(-)
diff --git a/llvm/include/llvm/Object/Minidump.h b/llvm/include/llvm/Object/Minidump.h
index eb6f4a2714340a..3df0ba6c0656ae 100644
--- a/llvm/include/llvm/Object/Minidump.h
+++ b/llvm/include/llvm/Object/Minidump.h
@@ -95,13 +95,6 @@ class MinidumpFile : public Binary {
return getStreamFromDirectory<minidump::ExceptionStream>(Directory);
}
- /// Returns the contents of the Exception streams. An error is returned if
- /// any of the streams are smaller than the size of the ExceptionStream
- /// structure. The internal consistency of the stream is not checked in any
- /// way.
- Expected<std::vector<const minidump::ExceptionStream *>>
- getExceptionStreams() const;
-
/// Returns the list of descriptors embedded in the MemoryList stream. The
/// descriptors provide the content of interesting regions of memory at the
/// time the minidump was taken. An error is returned if the file does not
@@ -226,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); }
+
+ const Expected<const minidump::ExceptionStream &>
+ operator*() {
+ return ReadCurrent();
+ }
+
+ const 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
@@ -266,9 +322,10 @@ 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());
@@ -293,6 +350,7 @@ 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>
diff --git a/llvm/lib/Object/Minidump.cpp b/llvm/lib/Object/Minidump.cpp
index cd78f1b10ca17a..200aa5720ec2ca 100644
--- a/llvm/lib/Object/Minidump.cpp
+++ b/llvm/lib/Object/Minidump.cpp
@@ -53,25 +53,10 @@ Expected<std::string> MinidumpFile::getString(size_t Offset) const {
return Result;
}
-Expected<std::vector<const minidump::ExceptionStream *>>
+iterator_range<llvm::object::MinidumpFile::ExceptionStreamsIterator>
MinidumpFile::getExceptionStreams() const {
-
- std::vector<const minidump::ExceptionStream *> exceptionStreamList;
- for (const auto &directory : Streams) {
- if (directory.Type == StreamType::Exception) {
- llvm::Expected<minidump::ExceptionStream *> ExpectedStream =
- getStreamFromDirectory<minidump::ExceptionStream *>(directory);
- if (!ExpectedStream)
- return ExpectedStream.takeError();
-
- exceptionStreamList.push_back(ExpectedStream.get());
- }
- }
-
- if (exceptionStreamList.empty())
- return createError("No exception streams found");
-
- return exceptionStreamList;
+ return make_range(ExceptionStreamsIterator::begin(ExceptionStreams, this),
+ ExceptionStreamsIterator::end());
}
Expected<iterator_range<MinidumpFile::MemoryInfoIterator>>
@@ -149,7 +134,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
return ExpectedStreams.takeError();
DenseMap<StreamType, std::size_t> StreamMap;
- std::vector<std::size_t> ExceptionStreams;
+ std::vector<Directory> ExceptionStreams;
for (const auto &StreamDescriptor : llvm::enumerate(*ExpectedStreams)) {
StreamType Type = StreamDescriptor.value().Type;
const LocationDescriptor &Loc = StreamDescriptor.value().Location;
@@ -171,7 +156,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
// streams that point back to their thread So we will omit them here, and
// will find them when needed in the MinidumpFile.
if (Type == StreamType::Exception) {
- continue;
+ ExceptionStreams.push_back(StreamDescriptor.value());
}
if (Type == DenseMapInfo<StreamType>::getEmptyKey() ||
@@ -184,7 +169,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>
>From c67ad4098a5ffe220eae82d53d8b0e391318cae2 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Wed, 4 Sep 2024 15:08:56 -0700
Subject: [PATCH 3/3] Migrate the unit tests, and add a basic test to ensure we
can read multiple exception streams
---
llvm/include/llvm/Object/Minidump.h | 4 +-
llvm/lib/Object/Minidump.cpp | 8 +--
llvm/unittests/Object/MinidumpTest.cpp | 6 +-
.../unittests/ObjectYAML/MinidumpYAMLTest.cpp | 66 ++++++++++++++++---
4 files changed, 67 insertions(+), 17 deletions(-)
diff --git a/llvm/include/llvm/Object/Minidump.h b/llvm/include/llvm/Object/Minidump.h
index 3df0ba6c0656ae..cf3d88889e900e 100644
--- a/llvm/include/llvm/Object/Minidump.h
+++ b/llvm/include/llvm/Object/Minidump.h
@@ -236,12 +236,12 @@ class ExceptionStreamsIterator {
bool operator!=(const ExceptionStreamsIterator &R) const { return !(*this == R); }
- const Expected<const minidump::ExceptionStream &>
+ Expected<const minidump::ExceptionStream &>
operator*() {
return ReadCurrent();
}
- const Expected<const minidump::ExceptionStream &>
+ Expected<const minidump::ExceptionStream &>
operator->() {
return ReadCurrent();
}
diff --git a/llvm/lib/Object/Minidump.cpp b/llvm/lib/Object/Minidump.cpp
index 200aa5720ec2ca..f334cd2a9a2e71 100644
--- a/llvm/lib/Object/Minidump.cpp
+++ b/llvm/lib/Object/Minidump.cpp
@@ -150,13 +150,11 @@ MinidumpFile::create(MemoryBufferRef Source) {
continue;
}
- // We treat exceptions differently here because the LLDB minidump
- // makes some assumptions about uniqueness, all the streams other than
- // exceptions are lists. But exceptions are not a list, they are single
- // streams that point back to their thread So we will omit them here, and
- // will find them when needed in the MinidumpFile.
+ // 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() ||
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);
}
More information about the llvm-commits
mailing list