1 //===- yaml2minidump.cpp - Convert a YAML file to a minidump file ---------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "llvm/ObjectYAML/MinidumpYAML.h"
10 #include "llvm/ObjectYAML/yaml2obj.h"
11 #include "llvm/Support/ConvertUTF.h"
12 #include "llvm/Support/raw_ostream.h"
16 using namespace llvm::minidump
;
17 using namespace llvm::MinidumpYAML
;
20 /// A helper class to manage the placement of various structures into the final
21 /// minidump binary. Space for objects can be allocated via various allocate***
22 /// methods, while the final minidump file is written by calling the writeTo
23 /// method. The plain versions of allocation functions take a reference to the
24 /// data which is to be written (and hence the data must be available until
25 /// writeTo is called), while the "New" versions allocate the data in an
26 /// allocator-managed buffer, which is available until the allocator object is
27 /// destroyed. For both kinds of functions, it is possible to modify the
28 /// data for which the space has been "allocated" until the final writeTo call.
29 /// This is useful for "linking" the allocated structures via their offsets.
32 size_t tell() const { return NextOffset
; }
34 size_t allocateCallback(size_t Size
,
35 std::function
<void(raw_ostream
&)> Callback
) {
36 size_t Offset
= NextOffset
;
38 Callbacks
.push_back(std::move(Callback
));
42 size_t allocateBytes(ArrayRef
<uint8_t> Data
) {
43 return allocateCallback(
44 Data
.size(), [Data
](raw_ostream
&OS
) { OS
<< toStringRef(Data
); });
47 size_t allocateBytes(yaml::BinaryRef Data
) {
48 return allocateCallback(Data
.binary_size(), [Data
](raw_ostream
&OS
) {
49 Data
.writeAsBinary(OS
);
53 template <typename T
> size_t allocateArray(ArrayRef
<T
> Data
) {
54 return allocateBytes({reinterpret_cast<const uint8_t *>(Data
.data()),
55 sizeof(T
) * Data
.size()});
58 template <typename T
, typename RangeType
>
59 std::pair
<size_t, MutableArrayRef
<T
>>
60 allocateNewArray(const iterator_range
<RangeType
> &Range
);
62 template <typename T
> size_t allocateObject(const T
&Data
) {
63 return allocateArray(ArrayRef(Data
));
66 template <typename T
, typename
... Types
>
67 std::pair
<size_t, T
*> allocateNewObject(Types
&&... Args
) {
68 T
*Object
= new (Temporaries
.Allocate
<T
>()) T(std::forward
<Types
>(Args
)...);
69 return {allocateObject(*Object
), Object
};
72 size_t allocateString(StringRef Str
);
74 void writeTo(raw_ostream
&OS
) const;
77 size_t NextOffset
= 0;
79 BumpPtrAllocator Temporaries
;
80 std::vector
<std::function
<void(raw_ostream
&)>> Callbacks
;
84 template <typename T
, typename RangeType
>
85 std::pair
<size_t, MutableArrayRef
<T
>>
86 BlobAllocator::allocateNewArray(const iterator_range
<RangeType
> &Range
) {
87 size_t Num
= std::distance(Range
.begin(), Range
.end());
88 MutableArrayRef
<T
> Array(Temporaries
.Allocate
<T
>(Num
), Num
);
89 std::uninitialized_copy(Range
.begin(), Range
.end(), Array
.begin());
90 return {allocateArray(Array
), Array
};
93 size_t BlobAllocator::allocateString(StringRef Str
) {
94 SmallVector
<UTF16
, 32> WStr
;
95 bool OK
= convertUTF8ToUTF16String(Str
, WStr
);
96 assert(OK
&& "Invalid UTF8 in Str?");
99 // The utf16 string is null-terminated, but the terminator is not counted in
103 allocateNewObject
<support::ulittle32_t
>(2 * (WStr
.size() - 1)).first
;
104 allocateNewArray
<support::ulittle16_t
>(make_range(WStr
.begin(), WStr
.end()));
108 void BlobAllocator::writeTo(raw_ostream
&OS
) const {
109 size_t BeginOffset
= OS
.tell();
110 for (const auto &Callback
: Callbacks
)
112 assert(OS
.tell() == BeginOffset
+ NextOffset
&&
113 "Callbacks wrote an unexpected number of bytes.");
117 static LocationDescriptor
layout(BlobAllocator
&File
, yaml::BinaryRef Data
) {
118 return {support::ulittle32_t(Data
.binary_size()),
119 support::ulittle32_t(File
.allocateBytes(Data
))};
122 static size_t layout(BlobAllocator
&File
, MinidumpYAML::ExceptionStream
&S
) {
123 File
.allocateObject(S
.MDExceptionStream
);
125 size_t DataEnd
= File
.tell();
127 // Lay out the thread context data, (which is not a part of the stream).
128 // TODO: This usually (always?) matches the thread context of the
129 // corresponding thread, and may overlap memory regions as well. We could
130 // add a level of indirection to the MinidumpYAML format (like an array of
131 // Blobs that the LocationDescriptors index into) to be able to distinguish
132 // the cases where location descriptions overlap vs happen to reference
134 S
.MDExceptionStream
.ThreadContext
= layout(File
, S
.ThreadContext
);
139 static void layout(BlobAllocator
&File
, MemoryListStream::entry_type
&Range
) {
140 Range
.Entry
.Memory
= layout(File
, Range
.Content
);
143 static void layout(BlobAllocator
&File
, ModuleListStream::entry_type
&M
) {
144 M
.Entry
.ModuleNameRVA
= File
.allocateString(M
.Name
);
146 M
.Entry
.CvRecord
= layout(File
, M
.CvRecord
);
147 M
.Entry
.MiscRecord
= layout(File
, M
.MiscRecord
);
150 static void layout(BlobAllocator
&File
, ThreadListStream::entry_type
&T
) {
151 T
.Entry
.Stack
.Memory
= layout(File
, T
.Stack
);
152 T
.Entry
.Context
= layout(File
, T
.Context
);
155 template <typename EntryT
>
156 static size_t layout(BlobAllocator
&File
,
157 MinidumpYAML::detail::ListStream
<EntryT
> &S
) {
159 File
.allocateNewObject
<support::ulittle32_t
>(S
.Entries
.size());
160 for (auto &E
: S
.Entries
)
161 File
.allocateObject(E
.Entry
);
163 size_t DataEnd
= File
.tell();
165 // Lay out the auxiliary data, (which is not a part of the stream).
166 DataEnd
= File
.tell();
167 for (auto &E
: S
.Entries
)
173 static Directory
layout(BlobAllocator
&File
, Stream
&S
) {
175 Result
.Type
= S
.Type
;
176 Result
.Location
.RVA
= File
.tell();
177 std::optional
<size_t> DataEnd
;
179 case Stream::StreamKind::Exception
:
180 DataEnd
= layout(File
, cast
<MinidumpYAML::ExceptionStream
>(S
));
182 case Stream::StreamKind::MemoryInfoList
: {
183 MemoryInfoListStream
&InfoList
= cast
<MemoryInfoListStream
>(S
);
184 File
.allocateNewObject
<minidump::MemoryInfoListHeader
>(
185 sizeof(minidump::MemoryInfoListHeader
), sizeof(minidump::MemoryInfo
),
186 InfoList
.Infos
.size());
187 File
.allocateArray(ArrayRef(InfoList
.Infos
));
190 case Stream::StreamKind::MemoryList
:
191 DataEnd
= layout(File
, cast
<MemoryListStream
>(S
));
193 case Stream::StreamKind::ModuleList
:
194 DataEnd
= layout(File
, cast
<ModuleListStream
>(S
));
196 case Stream::StreamKind::RawContent
: {
197 RawContentStream
&Raw
= cast
<RawContentStream
>(S
);
198 File
.allocateCallback(Raw
.Size
, [&Raw
](raw_ostream
&OS
) {
199 Raw
.Content
.writeAsBinary(OS
);
200 assert(Raw
.Content
.binary_size() <= Raw
.Size
);
201 OS
<< std::string(Raw
.Size
- Raw
.Content
.binary_size(), '\0');
205 case Stream::StreamKind::SystemInfo
: {
206 SystemInfoStream
&SystemInfo
= cast
<SystemInfoStream
>(S
);
207 File
.allocateObject(SystemInfo
.Info
);
208 // The CSD string is not a part of the stream.
209 DataEnd
= File
.tell();
210 SystemInfo
.Info
.CSDVersionRVA
= File
.allocateString(SystemInfo
.CSDVersion
);
213 case Stream::StreamKind::TextContent
:
214 File
.allocateArray(arrayRefFromStringRef(cast
<TextContentStream
>(S
).Text
));
216 case Stream::StreamKind::ThreadList
:
217 DataEnd
= layout(File
, cast
<ThreadListStream
>(S
));
220 // If DataEnd is not set, we assume everything we generated is a part of the
222 Result
.Location
.DataSize
=
223 DataEnd
.value_or(File
.tell()) - Result
.Location
.RVA
;
230 bool yaml2minidump(MinidumpYAML::Object
&Obj
, raw_ostream
&Out
,
231 ErrorHandler
/*EH*/) {
233 File
.allocateObject(Obj
.Header
);
235 std::vector
<Directory
> StreamDirectory(Obj
.Streams
.size());
236 Obj
.Header
.StreamDirectoryRVA
= File
.allocateArray(ArrayRef(StreamDirectory
));
237 Obj
.Header
.NumberOfStreams
= StreamDirectory
.size();
239 for (const auto &[Index
, Stream
] : enumerate(Obj
.Streams
))
240 StreamDirectory
[Index
] = layout(File
, *Stream
);