Skip to content

Commit deba134

Browse files
authored
[Minidump] Support multiple exceptions in a minidump (#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.
1 parent 80cf21d commit deba134

File tree

5 files changed

+153
-21
lines changed

5 files changed

+153
-21
lines changed

llvm/include/llvm/Object/Minidump.h

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,26 @@ class MinidumpFile : public Binary {
8383
return getListStream<minidump::Thread>(minidump::StreamType::ThreadList);
8484
}
8585

86-
/// Returns the contents of the Exception stream. An error is returned if the
87-
/// file does not contain this stream, or the stream is smaller than the size
88-
/// of the ExceptionStream structure. The internal consistency of the stream
89-
/// is not checked in any way.
86+
/// Returns the contents of the Exception stream. An error is returned if the
87+
/// associated stream is smaller than the size of the ExceptionStream
88+
/// structure. Or the directory supplied is not of kind exception stream.
89+
Expected<const minidump::ExceptionStream &>
90+
getExceptionStream(minidump::Directory Directory) const {
91+
if (Directory.Type != minidump::StreamType::Exception) {
92+
return createError("Not an exception stream");
93+
}
94+
95+
return getStreamFromDirectory<minidump::ExceptionStream>(Directory);
96+
}
97+
98+
/// Returns the first exception stream in the file. An error is returned if
99+
/// the associated stream is smaller than the size of the ExceptionStream
100+
/// structure. Or the directory supplied is not of kind exception stream.
90101
Expected<const minidump::ExceptionStream &> getExceptionStream() const {
91-
return getStream<minidump::ExceptionStream>(
92-
minidump::StreamType::Exception);
102+
auto it = getExceptionStreams();
103+
if (it.begin() == it.end())
104+
return createError("No exception streams");
105+
return *it.begin();
93106
}
94107

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

232+
class ExceptionStreamsIterator {
233+
public:
234+
ExceptionStreamsIterator(ArrayRef<minidump::Directory> Streams,
235+
const MinidumpFile *File)
236+
: Streams(Streams), File(File) {}
237+
238+
bool operator==(const ExceptionStreamsIterator &R) const {
239+
return Streams.size() == R.Streams.size();
240+
}
241+
242+
bool operator!=(const ExceptionStreamsIterator &R) const {
243+
return !(*this == R);
244+
}
245+
246+
Expected<const minidump::ExceptionStream &> operator*() {
247+
return File->getExceptionStream(Streams.front());
248+
}
249+
250+
ExceptionStreamsIterator &operator++() {
251+
if (!Streams.empty())
252+
Streams = Streams.drop_front();
253+
254+
return *this;
255+
}
256+
257+
private:
258+
ArrayRef<minidump::Directory> Streams;
259+
const MinidumpFile *File;
260+
};
261+
219262
using FallibleMemory64Iterator = llvm::fallible_iterator<Memory64Iterator>;
220263

264+
/// Returns an iterator that reads each exception stream independently. The
265+
/// contents of the exception strema are not validated before being read, an
266+
/// error will be returned if the stream is not large enough to contain an
267+
/// exception stream, or if the stream points beyond the end of the file.
268+
iterator_range<ExceptionStreamsIterator> getExceptionStreams() const;
269+
221270
/// Returns an iterator that pairs each descriptor with it's respective
222271
/// content from the Memory64List stream. An error is returned if the file
223272
/// does not contain a Memory64List stream, or if the descriptor data is
@@ -256,14 +305,22 @@ class MinidumpFile : public Binary {
256305

257306
MinidumpFile(MemoryBufferRef Source, const minidump::Header &Header,
258307
ArrayRef<minidump::Directory> Streams,
259-
DenseMap<minidump::StreamType, std::size_t> StreamMap)
308+
DenseMap<minidump::StreamType, std::size_t> StreamMap,
309+
std::vector<minidump::Directory> ExceptionStreams)
260310
: Binary(ID_Minidump, Source), Header(Header), Streams(Streams),
261-
StreamMap(std::move(StreamMap)) {}
311+
StreamMap(std::move(StreamMap)),
312+
ExceptionStreams(std::move(ExceptionStreams)) {}
262313

263314
ArrayRef<uint8_t> getData() const {
264315
return arrayRefFromStringRef(Data.getBuffer());
265316
}
266317

318+
/// Return the stream of the given type, cast to the appropriate type. Checks
319+
/// that the stream is large enough to hold an object of this type.
320+
template <typename T>
321+
Expected<const T &>
322+
getStreamFromDirectory(minidump::Directory Directory) const;
323+
267324
/// Return the stream of the given type, cast to the appropriate type. Checks
268325
/// that the stream is large enough to hold an object of this type.
269326
template <typename T>
@@ -277,8 +334,18 @@ class MinidumpFile : public Binary {
277334
const minidump::Header &Header;
278335
ArrayRef<minidump::Directory> Streams;
279336
DenseMap<minidump::StreamType, std::size_t> StreamMap;
337+
std::vector<minidump::Directory> ExceptionStreams;
280338
};
281339

340+
template <typename T>
341+
Expected<const T &>
342+
MinidumpFile::getStreamFromDirectory(minidump::Directory Directory) const {
343+
ArrayRef<uint8_t> Stream = getRawStream(Directory);
344+
if (Stream.size() >= sizeof(T))
345+
return *reinterpret_cast<const T *>(Stream.data());
346+
return createEOFError();
347+
}
348+
282349
template <typename T>
283350
Expected<const T &> MinidumpFile::getStream(minidump::StreamType Type) const {
284351
if (std::optional<ArrayRef<uint8_t>> Stream = getRawStream(Type)) {

llvm/lib/Object/Minidump.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ Expected<std::string> MinidumpFile::getString(size_t Offset) const {
5353
return Result;
5454
}
5555

56+
iterator_range<llvm::object::MinidumpFile::ExceptionStreamsIterator>
57+
MinidumpFile::getExceptionStreams() const {
58+
return make_range(ExceptionStreamsIterator(ExceptionStreams, this),
59+
ExceptionStreamsIterator({}, this));
60+
}
61+
5662
Expected<iterator_range<MinidumpFile::MemoryInfoIterator>>
5763
MinidumpFile::getMemoryInfoList() const {
5864
std::optional<ArrayRef<uint8_t>> Stream =
@@ -128,6 +134,7 @@ MinidumpFile::create(MemoryBufferRef Source) {
128134
return ExpectedStreams.takeError();
129135

130136
DenseMap<StreamType, std::size_t> StreamMap;
137+
std::vector<Directory> ExceptionStreams;
131138
for (const auto &StreamDescriptor : llvm::enumerate(*ExpectedStreams)) {
132139
StreamType Type = StreamDescriptor.value().Type;
133140
const LocationDescriptor &Loc = StreamDescriptor.value().Location;
@@ -143,6 +150,13 @@ MinidumpFile::create(MemoryBufferRef Source) {
143150
continue;
144151
}
145152

153+
// Exceptions can be treated as a special case of streams. Other streams
154+
// represent a list of entities, but exceptions are unique per stream.
155+
if (Type == StreamType::Exception) {
156+
ExceptionStreams.push_back(StreamDescriptor.value());
157+
continue;
158+
}
159+
146160
if (Type == DenseMapInfo<StreamType>::getEmptyKey() ||
147161
Type == DenseMapInfo<StreamType>::getTombstoneKey())
148162
return createError("Cannot handle one of the minidump streams");
@@ -153,7 +167,8 @@ MinidumpFile::create(MemoryBufferRef Source) {
153167
}
154168

155169
return std::unique_ptr<MinidumpFile>(
156-
new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap)));
170+
new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap),
171+
std::move(ExceptionStreams)));
157172
}
158173

159174
iterator_range<MinidumpFile::FallibleMemory64Iterator>

llvm/lib/ObjectYAML/MinidumpYAML.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ Stream::create(const Directory &StreamDesc, const object::MinidumpFile &File) {
499499
switch (Kind) {
500500
case StreamKind::Exception: {
501501
Expected<const minidump::ExceptionStream &> ExpectedExceptionStream =
502-
File.getExceptionStream();
502+
File.getExceptionStream(StreamDesc);
503503
if (!ExpectedExceptionStream)
504504
return ExpectedExceptionStream.takeError();
505505
Expected<ArrayRef<uint8_t>> ExpectedThreadContext =

llvm/unittests/Object/MinidumpTest.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ TEST(MinidumpFile, getMemoryInfoList) {
711711
0x0001000908000000u));
712712
}
713713

714-
TEST(MinidumpFile, getExceptionStream) {
714+
TEST(MinidumpFile, getExceptionStreams) {
715715
std::vector<uint8_t> Data{
716716
// Header
717717
'M', 'D', 'M', 'P', 0x93, 0xa7, 0, 0, // Signature, Version
@@ -751,8 +751,11 @@ TEST(MinidumpFile, getExceptionStream) {
751751
auto ExpectedFile = create(Data);
752752
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
753753
const MinidumpFile &File = **ExpectedFile;
754-
Expected<const minidump::ExceptionStream &> ExpectedStream =
755-
File.getExceptionStream();
754+
755+
auto ExceptionStreams = File.getExceptionStreams();
756+
ASSERT_NE(ExceptionStreams.begin(), ExceptionStreams.end());
757+
auto ExceptionIterator = ExceptionStreams.begin();
758+
Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
756759
ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
757760
EXPECT_EQ(0x04030201u, ExpectedStream->ThreadId);
758761
const minidump::Exception &Exception = ExpectedStream->ExceptionRecord;
@@ -767,4 +770,6 @@ TEST(MinidumpFile, getExceptionStream) {
767770
}
768771
EXPECT_EQ(0x84838281, ExpectedStream->ThreadContext.DataSize);
769772
EXPECT_EQ(0x88878685, ExpectedStream->ThreadContext.RVA);
773+
++ExceptionIterator;
774+
ASSERT_EQ(ExceptionIterator, ExceptionStreams.end());
770775
}

llvm/unittests/ObjectYAML/MinidumpYAMLTest.cpp

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,9 @@ TEST(MinidumpYAML, ExceptionStream) {
162162

163163
ASSERT_EQ(1u, File.streams().size());
164164

165-
Expected<const minidump::ExceptionStream &> ExpectedStream =
166-
File.getExceptionStream();
165+
auto ExceptionIterator = File.getExceptionStreams().begin();
166+
167+
Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
167168

168169
ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
169170

@@ -205,9 +206,9 @@ TEST(MinidumpYAML, ExceptionStream_NoParameters) {
205206

206207
ASSERT_EQ(1u, File.streams().size());
207208

208-
Expected<const minidump::ExceptionStream &> ExpectedStream =
209-
File.getExceptionStream();
209+
auto ExceptionIterator = File.getExceptionStreams().begin();
210210

211+
Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
211212
ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
212213

213214
const minidump::ExceptionStream &Stream = *ExpectedStream;
@@ -261,8 +262,9 @@ TEST(MinidumpYAML, ExceptionStream_TooManyParameters) {
261262

262263
ASSERT_EQ(1u, File.streams().size());
263264

264-
Expected<const minidump::ExceptionStream &> ExpectedStream =
265-
File.getExceptionStream();
265+
auto ExceptionIterator = File.getExceptionStreams().begin();
266+
267+
Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
266268

267269
ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
268270

@@ -312,8 +314,9 @@ TEST(MinidumpYAML, ExceptionStream_ExtraParameter) {
312314

313315
ASSERT_EQ(1u, File.streams().size());
314316

315-
Expected<const minidump::ExceptionStream &> ExpectedStream =
316-
File.getExceptionStream();
317+
auto ExceptionIterator = File.getExceptionStreams().begin();
318+
319+
Expected<const ExceptionStream &> ExpectedStream = *ExceptionIterator;
317320

318321
ASSERT_THAT_EXPECTED(ExpectedStream, Succeeded());
319322

@@ -399,3 +402,45 @@ TEST(MinidumpYAML, MemoryRegion_64bit) {
399402

400403
ASSERT_EQ(Iterator, MemoryList.end());
401404
}
405+
406+
// Test that we can parse multiple exception streams.
407+
TEST(MinidumpYAML, ExceptionStream_MultipleExceptions) {
408+
SmallString<0> Storage;
409+
auto ExpectedFile = toBinary(Storage, R"(
410+
--- !minidump
411+
Streams:
412+
- Type: Exception
413+
Thread ID: 0x7
414+
Exception Record:
415+
Exception Code: 0x23
416+
Exception Flags: 0x5
417+
Exception Record: 0x0102030405060708
418+
Exception Address: 0x0a0b0c0d0e0f1011
419+
Number of Parameters: 2
420+
Parameter 0: 0x99
421+
Parameter 1: 0x23
422+
Parameter 2: 0x42
423+
Thread Context: 3DeadBeefDefacedABadCafe
424+
- Type: Exception
425+
Thread ID: 0x5
426+
Exception Record:
427+
Exception Code: 0x23
428+
Exception Flags: 0x5
429+
Exception Record: 0x0102030405060708
430+
Exception Address: 0x0a0b0c0d0e0f1011
431+
Thread Context: 3DeadBeefDefacedABadCafe)");
432+
433+
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
434+
object::MinidumpFile &File = **ExpectedFile;
435+
436+
ASSERT_EQ(2u, File.streams().size());
437+
438+
size_t count = 0;
439+
for (auto exception_stream : File.getExceptionStreams()) {
440+
count++;
441+
ASSERT_THAT_EXPECTED(exception_stream, Succeeded());
442+
ASSERT_THAT(0x23u, exception_stream->ExceptionRecord.ExceptionCode);
443+
}
444+
445+
ASSERT_THAT(2u, count);
446+
}

0 commit comments

Comments
 (0)