Skip to content

[Minidump] Support multiple exceptions in a minidump #107319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 75 additions & 8 deletions llvm/include/llvm/Object/Minidump.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,26 @@ 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.
/// 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 first exception stream in the file. 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() const {
return getStream<minidump::ExceptionStream>(
minidump::StreamType::Exception);
auto it = getExceptionStreams();
if (it.begin() == it.end())
return createError("No exception streams");
return *it.begin();
}

/// Returns the list of descriptors embedded in the MemoryList stream. The
Expand Down Expand Up @@ -216,8 +229,44 @@ class MinidumpFile : public Binary {
bool IsEnd;
};

class ExceptionStreamsIterator {
public:
ExceptionStreamsIterator(ArrayRef<minidump::Directory> Streams,
const MinidumpFile *File)
: Streams(Streams), File(File) {}

bool operator==(const ExceptionStreamsIterator &R) const {
return Streams.size() == R.Streams.size();
}

bool operator!=(const ExceptionStreamsIterator &R) const {
return !(*this == R);
}

Expected<const minidump::ExceptionStream &> operator*() {
return File->getExceptionStream(Streams.front());
}

ExceptionStreamsIterator &operator++() {
if (!Streams.empty())
Streams = Streams.drop_front();

return *this;
}

private:
ArrayRef<minidump::Directory> Streams;
const MinidumpFile *File;
};

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
Expand Down Expand Up @@ -256,14 +305,22 @@ 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>
Expand All @@ -277,8 +334,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)) {
Expand Down
17 changes: 16 additions & 1 deletion llvm/lib/Object/Minidump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(ExceptionStreams, this),
ExceptionStreamsIterator({}, this));
}

Expected<iterator_range<MinidumpFile::MemoryInfoIterator>>
MinidumpFile::getMemoryInfoList() const {
std::optional<ArrayRef<uint8_t>> Stream =
Expand Down Expand Up @@ -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;
Expand All @@ -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");
Expand All @@ -153,7 +167,8 @@ 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),
std::move(ExceptionStreams)));
}

iterator_range<MinidumpFile::FallibleMemory64Iterator>
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/ObjectYAML/MinidumpYAML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
11 changes: 8 additions & 3 deletions llvm/unittests/Object/MinidumpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ TEST(MinidumpFile, getMemoryInfoList) {
0x0001000908000000u));
}

TEST(MinidumpFile, getExceptionStream) {
TEST(MinidumpFile, getExceptionStreams) {
std::vector<uint8_t> Data{
// Header
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
Expand Down Expand Up @@ -751,8 +751,11 @@ TEST(MinidumpFile, getExceptionStream) {
auto ExpectedFile = create(Data);
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
const MinidumpFile &File = **ExpectedFile;
Expected<const minidump::ExceptionStream &> ExpectedStream =
File.getExceptionStream();

auto ExceptionStreams = File.getExceptionStreams();
ASSERT_NE(ExceptionStreams.begin(), ExceptionStreams.end());
auto ExceptionIterator = ExceptionStreams.begin();
Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
EXPECT_EQ(0x04030201u, ExpectedStream->ThreadId);
const minidump::Exception &Exception = ExpectedStream->ExceptionRecord;
Expand All @@ -767,4 +770,6 @@ TEST(MinidumpFile, getExceptionStream) {
}
EXPECT_EQ(0x84838281, ExpectedStream->ThreadContext.DataSize);
EXPECT_EQ(0x88878685, ExpectedStream->ThreadContext.RVA);
++ExceptionIterator;
ASSERT_EQ(ExceptionIterator, ExceptionStreams.end());
}
61 changes: 53 additions & 8 deletions llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ 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());

Expand Down Expand Up @@ -205,9 +206,9 @@ 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;
Expand Down Expand Up @@ -261,8 +262,9 @@ 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());

Expand Down Expand Up @@ -312,8 +314,9 @@ 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());

Expand Down Expand Up @@ -399,3 +402,45 @@ TEST(MinidumpYAML, MemoryRegion_64bit) {

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);
}
Loading