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