| //===- Minidump.cpp - Minidump object file implementation -----------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/Object/Minidump.h" |
| #include "llvm/Object/Error.h" |
| #include "llvm/Support/ConvertUTF.h" |
| |
| using namespace llvm; |
| using namespace llvm::object; |
| using namespace llvm::minidump; |
| |
| std::optional<ArrayRef<uint8_t>> |
| MinidumpFile::getRawStream(minidump::StreamType Type) const { |
| auto It = StreamMap.find(Type); |
| if (It != StreamMap.end()) |
| return getRawStream(Streams[It->second]); |
| return std::nullopt; |
| } |
| |
| Expected<std::string> MinidumpFile::getString(size_t Offset) const { |
| // Minidump strings consist of a 32-bit length field, which gives the size of |
| // the string in *bytes*. This is followed by the actual string encoded in |
| // UTF16. |
| auto ExpectedSize = |
| getDataSliceAs<support::ulittle32_t>(getData(), Offset, 1); |
| if (!ExpectedSize) |
| return ExpectedSize.takeError(); |
| size_t Size = (*ExpectedSize)[0]; |
| if (Size % 2 != 0) |
| return createError("String size not even"); |
| Size /= 2; |
| if (Size == 0) |
| return ""; |
| |
| Offset += sizeof(support::ulittle32_t); |
| auto ExpectedData = |
| getDataSliceAs<support::ulittle16_t>(getData(), Offset, Size); |
| if (!ExpectedData) |
| return ExpectedData.takeError(); |
| |
| SmallVector<UTF16, 32> WStr(Size); |
| copy(*ExpectedData, WStr.begin()); |
| |
| std::string Result; |
| if (!convertUTF16ToUTF8String(WStr, Result)) |
| return createError("String decoding failed"); |
| |
| return Result; |
| } |
| |
| Expected<iterator_range<MinidumpFile::MemoryInfoIterator>> |
| MinidumpFile::getMemoryInfoList() const { |
| std::optional<ArrayRef<uint8_t>> Stream = |
| getRawStream(StreamType::MemoryInfoList); |
| if (!Stream) |
| return createError("No such stream"); |
| auto ExpectedHeader = |
| getDataSliceAs<minidump::MemoryInfoListHeader>(*Stream, 0, 1); |
| if (!ExpectedHeader) |
| return ExpectedHeader.takeError(); |
| const minidump::MemoryInfoListHeader &H = ExpectedHeader.get()[0]; |
| Expected<ArrayRef<uint8_t>> Data = |
| getDataSlice(*Stream, H.SizeOfHeader, H.SizeOfEntry * H.NumberOfEntries); |
| if (!Data) |
| return Data.takeError(); |
| return make_range(MemoryInfoIterator(*Data, H.SizeOfEntry), |
| MemoryInfoIterator({}, H.SizeOfEntry)); |
| } |
| |
| template <typename T> |
| Expected<ArrayRef<T>> MinidumpFile::getListStream(StreamType Type) const { |
| std::optional<ArrayRef<uint8_t>> Stream = getRawStream(Type); |
| if (!Stream) |
| return createError("No such stream"); |
| auto ExpectedSize = getDataSliceAs<support::ulittle32_t>(*Stream, 0, 1); |
| if (!ExpectedSize) |
| return ExpectedSize.takeError(); |
| |
| size_t ListSize = ExpectedSize.get()[0]; |
| |
| size_t ListOffset = 4; |
| // Some producers insert additional padding bytes to align the list to an |
| // 8-byte boundary. Check for that by comparing the list size with the overall |
| // stream size. |
| if (ListOffset + sizeof(T) * ListSize < Stream->size()) |
| ListOffset = 8; |
| |
| return getDataSliceAs<T>(*Stream, ListOffset, ListSize); |
| } |
| template Expected<ArrayRef<Module>> |
| MinidumpFile::getListStream(StreamType) const; |
| template Expected<ArrayRef<Thread>> |
| MinidumpFile::getListStream(StreamType) const; |
| template Expected<ArrayRef<MemoryDescriptor>> |
| MinidumpFile::getListStream(StreamType) const; |
| |
| Expected<ArrayRef<uint8_t>> |
| MinidumpFile::getDataSlice(ArrayRef<uint8_t> Data, size_t Offset, size_t Size) { |
| // Check for overflow. |
| if (Offset + Size < Offset || Offset + Size < Size || |
| Offset + Size > Data.size()) |
| return createEOFError(); |
| return Data.slice(Offset, Size); |
| } |
| |
| Expected<std::unique_ptr<MinidumpFile>> |
| MinidumpFile::create(MemoryBufferRef Source) { |
| ArrayRef<uint8_t> Data = arrayRefFromStringRef(Source.getBuffer()); |
| auto ExpectedHeader = getDataSliceAs<minidump::Header>(Data, 0, 1); |
| if (!ExpectedHeader) |
| return ExpectedHeader.takeError(); |
| |
| const minidump::Header &Hdr = (*ExpectedHeader)[0]; |
| if (Hdr.Signature != Header::MagicSignature) |
| return createError("Invalid signature"); |
| if ((Hdr.Version & 0xffff) != Header::MagicVersion) |
| return createError("Invalid version"); |
| |
| auto ExpectedStreams = getDataSliceAs<Directory>(Data, Hdr.StreamDirectoryRVA, |
| Hdr.NumberOfStreams); |
| if (!ExpectedStreams) |
| return ExpectedStreams.takeError(); |
| |
| DenseMap<StreamType, std::size_t> StreamMap; |
| for (const auto &StreamDescriptor : llvm::enumerate(*ExpectedStreams)) { |
| StreamType Type = StreamDescriptor.value().Type; |
| const LocationDescriptor &Loc = StreamDescriptor.value().Location; |
| |
| Expected<ArrayRef<uint8_t>> Stream = |
| getDataSlice(Data, Loc.RVA, Loc.DataSize); |
| if (!Stream) |
| return Stream.takeError(); |
| |
| if (Type == StreamType::Unused && Loc.DataSize == 0) { |
| // Ignore dummy streams. This is technically ill-formed, but a number of |
| // existing minidumps seem to contain such streams. |
| continue; |
| } |
| |
| if (Type == DenseMapInfo<StreamType>::getEmptyKey() || |
| Type == DenseMapInfo<StreamType>::getTombstoneKey()) |
| return createError("Cannot handle one of the minidump streams"); |
| |
| // Update the directory map, checking for duplicate stream types. |
| if (!StreamMap.try_emplace(Type, StreamDescriptor.index()).second) |
| return createError("Duplicate stream type"); |
| } |
| |
| return std::unique_ptr<MinidumpFile>( |
| new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap))); |
| } |