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 | |