xref: /llvm-project/llvm/include/llvm/Object/Minidump.h (revision 8129ba6c70aef54d87893adeb34e76f84fa69fe3)
1 //===- Minidump.h - Minidump object file implementation ---------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_OBJECT_MINIDUMP_H
10 #define LLVM_OBJECT_MINIDUMP_H
11 
12 #include "llvm/ADT/DenseMap.h"
13 #include "llvm/ADT/StringExtras.h"
14 #include "llvm/ADT/fallible_iterator.h"
15 #include "llvm/ADT/iterator.h"
16 #include "llvm/BinaryFormat/Minidump.h"
17 #include "llvm/Object/Binary.h"
18 #include "llvm/Support/Error.h"
19 
20 namespace llvm {
21 namespace object {
22 
23 /// A class providing access to the contents of a minidump file.
24 class MinidumpFile : public Binary {
25 public:
26   /// Construct a new MinidumpFile object from the given memory buffer. Returns
27   /// an error if this file cannot be identified as a minidump file, or if its
28   /// contents are badly corrupted (i.e. we cannot read the stream directory).
29   static Expected<std::unique_ptr<MinidumpFile>> create(MemoryBufferRef Source);
30 
31   static bool classof(const Binary *B) { return B->isMinidump(); }
32 
33   /// Returns the contents of the minidump header.
34   const minidump::Header &header() const { return Header; }
35 
36   /// Returns the list of streams (stream directory entries) in this file.
37   ArrayRef<minidump::Directory> streams() const { return Streams; }
38 
39   /// Returns the raw contents of the stream given by the directory entry.
40   ArrayRef<uint8_t> getRawStream(const minidump::Directory &Stream) const {
41     return getData().slice(Stream.Location.RVA, Stream.Location.DataSize);
42   }
43 
44   /// Returns the raw contents of the stream of the given type, or std::nullopt
45   /// if the file does not contain a stream of this type.
46   std::optional<ArrayRef<uint8_t>>
47   getRawStream(minidump::StreamType Type) const;
48 
49   /// Returns the raw contents of an object given by the LocationDescriptor. An
50   /// error is returned if the descriptor points outside of the minidump file.
51   Expected<ArrayRef<uint8_t>>
52   getRawData(minidump::LocationDescriptor Desc) const {
53     return getDataSlice(getData(), Desc.RVA, Desc.DataSize);
54   }
55 
56   /// Returns the minidump string at the given offset. An error is returned if
57   /// we fail to parse the string, or the string is invalid UTF16.
58   Expected<std::string> getString(size_t Offset) const;
59 
60   /// Returns the contents of the SystemInfo stream, cast to the appropriate
61   /// type. An error is returned if the file does not contain this stream, or
62   /// the stream is smaller than the size of the SystemInfo structure. The
63   /// internal consistency of the stream is not checked in any way.
64   Expected<const minidump::SystemInfo &> getSystemInfo() const {
65     return getStream<minidump::SystemInfo>(minidump::StreamType::SystemInfo);
66   }
67 
68   /// Returns the module list embedded in the ModuleList stream. An error is
69   /// returned if the file does not contain this stream, or if the stream is
70   /// not large enough to contain the number of modules declared in the stream
71   /// header. The consistency of the Module entries themselves is not checked in
72   /// any way.
73   Expected<ArrayRef<minidump::Module>> getModuleList() const {
74     return getListStream<minidump::Module>(minidump::StreamType::ModuleList);
75   }
76 
77   /// Returns the thread list embedded in the ThreadList stream. An error is
78   /// returned if the file does not contain this stream, or if the stream is
79   /// not large enough to contain the number of threads declared in the stream
80   /// header. The consistency of the Thread entries themselves is not checked in
81   /// any way.
82   Expected<ArrayRef<minidump::Thread>> getThreadList() const {
83     return getListStream<minidump::Thread>(minidump::StreamType::ThreadList);
84   }
85 
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.
101   Expected<const minidump::ExceptionStream &> getExceptionStream() const {
102     auto it = getExceptionStreams();
103     if (it.begin() == it.end())
104       return createError("No exception streams");
105     return *it.begin();
106   }
107 
108   /// Returns the list of descriptors embedded in the MemoryList stream. The
109   /// descriptors provide the content of interesting regions of memory at the
110   /// time the minidump was taken. An error is returned if the file does not
111   /// contain this stream, or if the stream is not large enough to contain the
112   /// number of memory descriptors declared in the stream header. The
113   /// consistency of the MemoryDescriptor entries themselves is not checked in
114   /// any way.
115   Expected<ArrayRef<minidump::MemoryDescriptor>> getMemoryList() const {
116     return getListStream<minidump::MemoryDescriptor>(
117         minidump::StreamType::MemoryList);
118   }
119 
120   /// Returns the header to the memory 64 list stream. An error is returned if
121   /// the file does not contain this stream.
122   Expected<minidump::Memory64ListHeader> getMemoryList64Header() const {
123     return getStream<minidump::Memory64ListHeader>(
124         minidump::StreamType::Memory64List);
125   }
126 
127   class MemoryInfoIterator
128       : public iterator_facade_base<MemoryInfoIterator,
129                                     std::forward_iterator_tag,
130                                     minidump::MemoryInfo> {
131   public:
132     MemoryInfoIterator(ArrayRef<uint8_t> Storage, size_t Stride)
133         : Storage(Storage), Stride(Stride) {
134       assert(Storage.size() % Stride == 0);
135     }
136 
137     bool operator==(const MemoryInfoIterator &R) const {
138       return Storage.size() == R.Storage.size();
139     }
140 
141     const minidump::MemoryInfo &operator*() const {
142       assert(Storage.size() >= sizeof(minidump::MemoryInfo));
143       return *reinterpret_cast<const minidump::MemoryInfo *>(Storage.data());
144     }
145 
146     MemoryInfoIterator &operator++() {
147       Storage = Storage.drop_front(Stride);
148       return *this;
149     }
150 
151   private:
152     ArrayRef<uint8_t> Storage;
153     size_t Stride;
154   };
155 
156   /// Class the provides an iterator over the memory64 memory ranges. Only the
157   /// the first descriptor is validated as readable beforehand.
158   class Memory64Iterator {
159   public:
160     static Memory64Iterator
161     begin(ArrayRef<uint8_t> Storage,
162           ArrayRef<minidump::MemoryDescriptor_64> Descriptors) {
163       return Memory64Iterator(Storage, Descriptors);
164     }
165 
166     static Memory64Iterator end() { return Memory64Iterator(); }
167 
168     bool operator==(const Memory64Iterator &R) const {
169       return IsEnd == R.IsEnd;
170     }
171 
172     bool operator!=(const Memory64Iterator &R) const { return !(*this == R); }
173 
174     const std::pair<minidump::MemoryDescriptor_64, ArrayRef<uint8_t>> &
175     operator*() {
176       return Current;
177     }
178 
179     const std::pair<minidump::MemoryDescriptor_64, ArrayRef<uint8_t>> *
180     operator->() {
181       return &Current;
182     }
183 
184     Error inc() {
185       if (Descriptors.empty()) {
186         IsEnd = true;
187         return Error::success();
188       }
189 
190       // Drop front gives us an array ref, so we need to call .front() as well.
191       const minidump::MemoryDescriptor_64 &Descriptor = Descriptors.front();
192       if (Descriptor.DataSize > Storage.size()) {
193         IsEnd = true;
194         return make_error<GenericBinaryError>(
195             "Memory64 Descriptor exceeds end of file.",
196             object_error::unexpected_eof);
197       }
198 
199       ArrayRef<uint8_t> Content = Storage.take_front(Descriptor.DataSize);
200       Current = std::make_pair(Descriptor, Content);
201 
202       Storage = Storage.drop_front(Descriptor.DataSize);
203       Descriptors = Descriptors.drop_front();
204 
205       return Error::success();
206     }
207 
208   private:
209     // This constructor expects that the first descriptor is readable.
210     Memory64Iterator(ArrayRef<uint8_t> Storage,
211                      ArrayRef<minidump::MemoryDescriptor_64> Descriptors)
212         : Storage(Storage), Descriptors(Descriptors), IsEnd(false) {
213       assert(!Descriptors.empty() &&
214              Storage.size() >= Descriptors.front().DataSize);
215       minidump::MemoryDescriptor_64 Descriptor = Descriptors.front();
216       ArrayRef<uint8_t> Content = Storage.take_front(Descriptor.DataSize);
217       Current = std::make_pair(Descriptor, Content);
218       this->Descriptors = Descriptors.drop_front();
219       this->Storage = Storage.drop_front(Descriptor.DataSize);
220     }
221 
222     Memory64Iterator()
223         : Storage(ArrayRef<uint8_t>()),
224           Descriptors(ArrayRef<minidump::MemoryDescriptor_64>()), IsEnd(true) {}
225 
226     std::pair<minidump::MemoryDescriptor_64, ArrayRef<uint8_t>> Current;
227     ArrayRef<uint8_t> Storage;
228     ArrayRef<minidump::MemoryDescriptor_64> Descriptors;
229     bool IsEnd;
230   };
231 
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 
262   using FallibleMemory64Iterator = llvm::fallible_iterator<Memory64Iterator>;
263 
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 
270   /// Returns an iterator that pairs each descriptor with it's respective
271   /// content from the Memory64List stream. An error is returned if the file
272   /// does not contain a Memory64List stream, or if the descriptor data is
273   /// unreadable.
274   iterator_range<FallibleMemory64Iterator> getMemory64List(Error &Err) const;
275 
276   /// Returns the list of descriptors embedded in the MemoryInfoList stream. The
277   /// descriptors provide properties (e.g. permissions) of interesting regions
278   /// of memory at the time the minidump was taken. An error is returned if the
279   /// file does not contain this stream, or if the stream is not large enough to
280   /// contain the number of memory descriptors declared in the stream header.
281   /// The consistency of the MemoryInfoList entries themselves is not checked
282   /// in any way.
283   Expected<iterator_range<MemoryInfoIterator>> getMemoryInfoList() const;
284 
285 private:
286   static Error createError(StringRef Str) {
287     return make_error<GenericBinaryError>(Str, object_error::parse_failed);
288   }
289 
290   static Error createEOFError() {
291     return make_error<GenericBinaryError>("Unexpected EOF",
292                                           object_error::unexpected_eof);
293   }
294 
295   /// Return a slice of the given data array, with bounds checking.
296   static Expected<ArrayRef<uint8_t>>
297   getDataSlice(ArrayRef<uint8_t> Data, uint64_t Offset, uint64_t Size);
298 
299   /// Return the slice of the given data array as an array of objects of the
300   /// given type. The function checks that the input array is large enough to
301   /// contain the correct number of objects of the given type.
302   template <typename T>
303   static Expected<ArrayRef<T>> getDataSliceAs(ArrayRef<uint8_t> Data,
304                                               uint64_t Offset, uint64_t Count);
305 
306   MinidumpFile(MemoryBufferRef Source, const minidump::Header &Header,
307                ArrayRef<minidump::Directory> Streams,
308                DenseMap<minidump::StreamType, std::size_t> StreamMap,
309                std::vector<minidump::Directory> ExceptionStreams)
310       : Binary(ID_Minidump, Source), Header(Header), Streams(Streams),
311         StreamMap(std::move(StreamMap)),
312         ExceptionStreams(std::move(ExceptionStreams)) {}
313 
314   ArrayRef<uint8_t> getData() const {
315     return arrayRefFromStringRef(Data.getBuffer());
316   }
317 
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 
324   /// Return the stream of the given type, cast to the appropriate type. Checks
325   /// that the stream is large enough to hold an object of this type.
326   template <typename T>
327   Expected<const T &> getStream(minidump::StreamType Stream) const;
328 
329   /// Return the contents of a stream which contains a list of fixed-size items,
330   /// prefixed by the list size.
331   template <typename T>
332   Expected<ArrayRef<T>> getListStream(minidump::StreamType Stream) const;
333 
334   const minidump::Header &Header;
335   ArrayRef<minidump::Directory> Streams;
336   DenseMap<minidump::StreamType, std::size_t> StreamMap;
337   std::vector<minidump::Directory> ExceptionStreams;
338 };
339 
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 
349 template <typename T>
350 Expected<const T &> MinidumpFile::getStream(minidump::StreamType Type) const {
351   if (std::optional<ArrayRef<uint8_t>> Stream = getRawStream(Type)) {
352     if (Stream->size() >= sizeof(T))
353       return *reinterpret_cast<const T *>(Stream->data());
354     return createEOFError();
355   }
356   return createError("No such stream");
357 }
358 
359 template <typename T>
360 Expected<ArrayRef<T>> MinidumpFile::getDataSliceAs(ArrayRef<uint8_t> Data,
361                                                    uint64_t Offset,
362                                                    uint64_t Count) {
363   // Check for overflow.
364   if (Count > std::numeric_limits<uint64_t>::max() / sizeof(T))
365     return createEOFError();
366   Expected<ArrayRef<uint8_t>> Slice =
367       getDataSlice(Data, Offset, sizeof(T) * Count);
368   if (!Slice)
369     return Slice.takeError();
370 
371   return ArrayRef<T>(reinterpret_cast<const T *>(Slice->data()), Count);
372 }
373 
374 template <typename T>
375 Expected<ArrayRef<T>>
376 MinidumpFile::getListStream(minidump::StreamType Type) const {
377   std::optional<ArrayRef<uint8_t>> Stream = getRawStream(Type);
378   if (!Stream)
379     return createError("No such stream");
380   auto ExpectedSize = getDataSliceAs<support::ulittle32_t>(*Stream, 0, 1);
381   if (!ExpectedSize)
382     return ExpectedSize.takeError();
383 
384   size_t ListSize = ExpectedSize.get()[0];
385 
386   size_t ListOffset = 4;
387   // Some producers insert additional padding bytes to align the list to an
388   // 8-byte boundary. Check for that by comparing the list size with the overall
389   // stream size.
390   if (ListOffset + sizeof(T) * ListSize < Stream->size())
391     ListOffset = 8;
392 
393   return getDataSliceAs<T>(*Stream, ListOffset, ListSize);
394 }
395 
396 } // end namespace object
397 } // end namespace llvm
398 
399 #endif // LLVM_OBJECT_MINIDUMP_H
400