| 1 | //===- Minidump.cpp - Minidump object file implementation -----------------===// |
| 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 | #include "llvm/Object/Minidump.h" |
| 10 | #include "llvm/Support/ConvertUTF.h" |
| 11 | |
| 12 | using namespace llvm; |
| 13 | using namespace llvm::object; |
| 14 | using namespace llvm::minidump; |
| 15 | |
| 16 | std::optional<ArrayRef<uint8_t>> |
| 17 | MinidumpFile::getRawStream(minidump::StreamType Type) const { |
| 18 | auto It = StreamMap.find(Val: Type); |
| 19 | if (It != StreamMap.end()) |
| 20 | return getRawStream(Stream: Streams[It->second]); |
| 21 | return std::nullopt; |
| 22 | } |
| 23 | |
| 24 | Expected<std::string> MinidumpFile::getString(size_t Offset) const { |
| 25 | // Minidump strings consist of a 32-bit length field, which gives the size of |
| 26 | // the string in *bytes*. This is followed by the actual string encoded in |
| 27 | // UTF16. |
| 28 | auto ExpectedSize = |
| 29 | getDataSliceAs<support::ulittle32_t>(Data: getData(), Offset, Count: 1); |
| 30 | if (!ExpectedSize) |
| 31 | return ExpectedSize.takeError(); |
| 32 | size_t Size = (*ExpectedSize)[0]; |
| 33 | if (Size % 2 != 0) |
| 34 | return createError(Str: "String size not even" ); |
| 35 | Size /= 2; |
| 36 | if (Size == 0) |
| 37 | return "" ; |
| 38 | |
| 39 | Offset += sizeof(support::ulittle32_t); |
| 40 | auto ExpectedData = |
| 41 | getDataSliceAs<support::ulittle16_t>(Data: getData(), Offset, Count: Size); |
| 42 | if (!ExpectedData) |
| 43 | return ExpectedData.takeError(); |
| 44 | |
| 45 | SmallVector<UTF16, 32> WStr(Size); |
| 46 | copy(Range&: *ExpectedData, Out: WStr.begin()); |
| 47 | |
| 48 | std::string Result; |
| 49 | if (!convertUTF16ToUTF8String(Src: WStr, Out&: Result)) |
| 50 | return createError(Str: "String decoding failed" ); |
| 51 | |
| 52 | return Result; |
| 53 | } |
| 54 | |
| 55 | iterator_range<llvm::object::MinidumpFile::ExceptionStreamsIterator> |
| 56 | MinidumpFile::getExceptionStreams() const { |
| 57 | return make_range(x: ExceptionStreamsIterator(ExceptionStreams, this), |
| 58 | y: ExceptionStreamsIterator({}, this)); |
| 59 | } |
| 60 | |
| 61 | Expected<iterator_range<MinidumpFile::MemoryInfoIterator>> |
| 62 | MinidumpFile::getMemoryInfoList() const { |
| 63 | std::optional<ArrayRef<uint8_t>> Stream = |
| 64 | getRawStream(Type: StreamType::MemoryInfoList); |
| 65 | if (!Stream) |
| 66 | return createError(Str: "No such stream" ); |
| 67 | auto = |
| 68 | getDataSliceAs<minidump::MemoryInfoListHeader>(Data: *Stream, Offset: 0, Count: 1); |
| 69 | if (!ExpectedHeader) |
| 70 | return ExpectedHeader.takeError(); |
| 71 | const minidump::MemoryInfoListHeader &H = ExpectedHeader.get()[0]; |
| 72 | Expected<ArrayRef<uint8_t>> Data = |
| 73 | getDataSlice(Data: *Stream, Offset: H.SizeOfHeader, Size: H.SizeOfEntry * H.NumberOfEntries); |
| 74 | if (!Data) |
| 75 | return Data.takeError(); |
| 76 | return make_range(x: MemoryInfoIterator(*Data, H.SizeOfEntry), |
| 77 | y: MemoryInfoIterator({}, H.SizeOfEntry)); |
| 78 | } |
| 79 | |
| 80 | Expected<ArrayRef<uint8_t>> MinidumpFile::getDataSlice(ArrayRef<uint8_t> Data, |
| 81 | uint64_t Offset, |
| 82 | uint64_t Size) { |
| 83 | // Check for overflow. |
| 84 | if (Offset + Size < Offset || Offset + Size < Size || |
| 85 | Offset + Size > Data.size()) |
| 86 | return createEOFError(); |
| 87 | return Data.slice(N: Offset, M: Size); |
| 88 | } |
| 89 | |
| 90 | Expected<std::unique_ptr<MinidumpFile>> |
| 91 | MinidumpFile::create(MemoryBufferRef Source) { |
| 92 | ArrayRef<uint8_t> Data = arrayRefFromStringRef(Input: Source.getBuffer()); |
| 93 | auto = getDataSliceAs<minidump::Header>(Data, Offset: 0, Count: 1); |
| 94 | if (!ExpectedHeader) |
| 95 | return ExpectedHeader.takeError(); |
| 96 | |
| 97 | const minidump::Header &Hdr = (*ExpectedHeader)[0]; |
| 98 | if (Hdr.Signature != Header::MagicSignature) |
| 99 | return createError(Str: "Invalid signature" ); |
| 100 | if ((Hdr.Version & 0xffff) != Header::MagicVersion) |
| 101 | return createError(Str: "Invalid version" ); |
| 102 | |
| 103 | auto ExpectedStreams = getDataSliceAs<Directory>(Data, Offset: Hdr.StreamDirectoryRVA, |
| 104 | Count: Hdr.NumberOfStreams); |
| 105 | if (!ExpectedStreams) |
| 106 | return ExpectedStreams.takeError(); |
| 107 | |
| 108 | DenseMap<StreamType, std::size_t> StreamMap; |
| 109 | std::vector<Directory> ExceptionStreams; |
| 110 | for (const auto &StreamDescriptor : llvm::enumerate(First&: *ExpectedStreams)) { |
| 111 | StreamType Type = StreamDescriptor.value().Type; |
| 112 | const LocationDescriptor &Loc = StreamDescriptor.value().Location; |
| 113 | |
| 114 | Expected<ArrayRef<uint8_t>> Stream = |
| 115 | getDataSlice(Data, Offset: Loc.RVA, Size: Loc.DataSize); |
| 116 | if (!Stream) |
| 117 | return Stream.takeError(); |
| 118 | |
| 119 | if (Type == StreamType::Unused && Loc.DataSize == 0) { |
| 120 | // Ignore dummy streams. This is technically ill-formed, but a number of |
| 121 | // existing minidumps seem to contain such streams. |
| 122 | continue; |
| 123 | } |
| 124 | |
| 125 | // Exceptions can be treated as a special case of streams. Other streams |
| 126 | // represent a list of entities, but exceptions are unique per stream. |
| 127 | if (Type == StreamType::Exception) { |
| 128 | ExceptionStreams.push_back(x: StreamDescriptor.value()); |
| 129 | continue; |
| 130 | } |
| 131 | |
| 132 | if (Type == DenseMapInfo<StreamType>::getEmptyKey() || |
| 133 | Type == DenseMapInfo<StreamType>::getTombstoneKey()) |
| 134 | return createError(Str: "Cannot handle one of the minidump streams" ); |
| 135 | |
| 136 | // Update the directory map, checking for duplicate stream types. |
| 137 | if (!StreamMap.try_emplace(Key: Type, Args: StreamDescriptor.index()).second) |
| 138 | return createError(Str: "Duplicate stream type" ); |
| 139 | } |
| 140 | |
| 141 | return std::unique_ptr<MinidumpFile>( |
| 142 | new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap), |
| 143 | std::move(ExceptionStreams))); |
| 144 | } |
| 145 | |
| 146 | iterator_range<MinidumpFile::FallibleMemory64Iterator> |
| 147 | MinidumpFile::getMemory64List(Error &Err) const { |
| 148 | ErrorAsOutParameter ErrAsOutParam(Err); |
| 149 | auto end = FallibleMemory64Iterator::end(I: Memory64Iterator::end()); |
| 150 | Expected<minidump::Memory64ListHeader> = getMemoryList64Header(); |
| 151 | if (!ListHeader) { |
| 152 | Err = ListHeader.takeError(); |
| 153 | return make_range(x: end, y: end); |
| 154 | } |
| 155 | |
| 156 | std::optional<ArrayRef<uint8_t>> Stream = |
| 157 | getRawStream(Type: StreamType::Memory64List); |
| 158 | if (!Stream) { |
| 159 | Err = createError(Str: "No such stream" ); |
| 160 | return make_range(x: end, y: end); |
| 161 | } |
| 162 | |
| 163 | Expected<ArrayRef<minidump::MemoryDescriptor_64>> Descriptors = |
| 164 | getDataSliceAs<minidump::MemoryDescriptor_64>( |
| 165 | Data: *Stream, Offset: sizeof(Memory64ListHeader), |
| 166 | Count: ListHeader->NumberOfMemoryRanges); |
| 167 | |
| 168 | if (!Descriptors) { |
| 169 | Err = Descriptors.takeError(); |
| 170 | return make_range(x: end, y: end); |
| 171 | } |
| 172 | |
| 173 | if (!Descriptors->empty() && |
| 174 | ListHeader->BaseRVA + Descriptors->front().DataSize > getData().size()) { |
| 175 | Err = createError(Str: "Memory64List header RVA out of range" ); |
| 176 | return make_range(x: end, y: end); |
| 177 | } |
| 178 | |
| 179 | return make_range(x: FallibleMemory64Iterator::itr( |
| 180 | I: Memory64Iterator::begin( |
| 181 | Storage: getData().slice(N: ListHeader->BaseRVA), Descriptors: *Descriptors), |
| 182 | Err), |
| 183 | y: FallibleMemory64Iterator::end(I: Memory64Iterator::end())); |
| 184 | } |
| 185 | |