Skip to content

[Obj2Yaml] Add support for minidump generation with 64b memory ranges. #101272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d28a238
Squash 64b-memory-regions-minidump and take only llvm-changes
Jlalond Jul 30, 2024
101c006
Make some further changes to enable content, remove const modifier so…
Jlalond Jul 31, 2024
fe45ccd
Quick cleanup as Greg pointed out, actually include test code to test…
Jlalond Jul 31, 2024
b7352c9
Fix test with arrayRefFrom suggestion
Jlalond Jul 31, 2024
06f263f
Convert to an iterator instead of cacheing
Jlalond Jul 31, 2024
fe84172
Readd const to the objectfile reference
Jlalond Jul 31, 2024
e4ad875
Run git-clang-format
Jlalond Jul 31, 2024
6052339
Manual fix formatting for {} and fix the basic.yaml test
Jlalond Aug 1, 2024
4fe66e1
Get fallible iterator happy path complete, need to remove debug code …
Jlalond Aug 6, 2024
1c6e513
Add test case for DataSize < Content
Jlalond Aug 6, 2024
2bf7371
Rebase and run clang format
Jlalond Aug 6, 2024
807dfd3
Remove last extraneous ; on constructor curly braces
Jlalond Aug 6, 2024
123f6ed
Fix Obj2yaml bug and add comment where the iterator starts uninitiali…
Jlalond Aug 6, 2024
e5794ae
Create out of bounds check for first element of iterator, allowing no…
Jlalond Aug 7, 2024
bb7bf1f
Drop RVA property and index and instead pop off the front of the arra…
Jlalond Aug 7, 2024
06732f0
Run GCF
Jlalond Aug 7, 2024
63ca9e1
Modify comment
Jlalond Aug 7, 2024
72d6a16
Impliment good feedback on making code more concise, and fix callback…
Jlalond Aug 8, 2024
388e0aa
Add .empty() calls that got missed when I reorganized the code
Jlalond Aug 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions llvm/include/llvm/BinaryFormat/Minidump.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ struct MemoryDescriptor_64 {
support::ulittle64_t StartOfMemoryRange;
support::ulittle64_t DataSize;
};
static_assert(sizeof(MemoryDescriptor_64) == 16);

struct MemoryListHeader {
support::ulittle32_t NumberOfMemoryRanges;
};
static_assert(sizeof(MemoryListHeader) == 4);

struct Memory64ListHeader {
support::ulittle64_t NumberOfMemoryRanges;
support::ulittle64_t BaseRVA;
};
static_assert(sizeof(Memory64ListHeader) == 16);

struct MemoryInfoListHeader {
support::ulittle32_t SizeOfHeader;
Expand Down
105 changes: 99 additions & 6 deletions llvm/include/llvm/Object/Minidump.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/fallible_iterator.h"
#include "llvm/ADT/iterator.h"
#include "llvm/BinaryFormat/Minidump.h"
#include "llvm/Object/Binary.h"
Expand Down Expand Up @@ -103,6 +104,13 @@ class MinidumpFile : public Binary {
minidump::StreamType::MemoryList);
}

/// Returns the header to the memory 64 list stream. An error is returned if
/// the file does not contain this stream.
Expected<minidump::Memory64ListHeader> getMemoryList64Header() const {
return getStream<minidump::Memory64ListHeader>(
minidump::StreamType::Memory64List);
}

class MemoryInfoIterator
: public iterator_facade_base<MemoryInfoIterator,
std::forward_iterator_tag,
Expand Down Expand Up @@ -132,6 +140,90 @@ class MinidumpFile : public Binary {
size_t Stride;
};

/// Class the provides an iterator over the memory64 memory ranges. Only the
/// the first descriptor is validated as readable beforehand.
class Memory64Iterator {
public:
static Memory64Iterator
begin(ArrayRef<uint8_t> Storage,
ArrayRef<minidump::MemoryDescriptor_64> Descriptors) {
return Memory64Iterator(Storage, Descriptors);
}

static Memory64Iterator end() { return Memory64Iterator(); }

bool operator==(const Memory64Iterator &R) const {
return IsEnd == R.IsEnd;
}

bool operator!=(const Memory64Iterator &R) const { return !(*this == R); }

const std::pair<minidump::MemoryDescriptor_64, ArrayRef<uint8_t>> &
operator*() {
return Current;
}

const std::pair<minidump::MemoryDescriptor_64, ArrayRef<uint8_t>> *
operator->() {
return &Current;
}

Error inc() {
if (Descriptors.empty()) {
IsEnd = true;
return Error::success();
}

// Drop front gives us an array ref, so we need to call .front() as well.
const minidump::MemoryDescriptor_64 &Descriptor = Descriptors.front();
if (Descriptor.DataSize > Storage.size()) {
IsEnd = true;
return make_error<GenericBinaryError>(
"Memory64 Descriptor exceeds end of file.",
object_error::unexpected_eof);
}

ArrayRef<uint8_t> Content = Storage.take_front(Descriptor.DataSize);
Current = std::make_pair(Descriptor, Content);

Storage = Storage.drop_front(Descriptor.DataSize);
Descriptors = Descriptors.drop_front();

return Error::success();
}

private:
// This constructor expects that the first descriptor is readable.
Memory64Iterator(ArrayRef<uint8_t> Storage,
ArrayRef<minidump::MemoryDescriptor_64> Descriptors)
: Storage(Storage), Descriptors(Descriptors), IsEnd(false) {
assert(!Descriptors.empty() &&
Storage.size() >= Descriptors.front().DataSize);
minidump::MemoryDescriptor_64 Descriptor = Descriptors.front();
ArrayRef<uint8_t> Content = Storage.take_front(Descriptor.DataSize);
Current = std::make_pair(Descriptor, Content);
this->Descriptors = Descriptors.drop_front();
this->Storage = Storage.drop_front(Descriptor.DataSize);
}

Memory64Iterator()
: Storage(ArrayRef<uint8_t>()),
Descriptors(ArrayRef<minidump::MemoryDescriptor_64>()), IsEnd(true) {}

std::pair<minidump::MemoryDescriptor_64, ArrayRef<uint8_t>> Current;
ArrayRef<uint8_t> Storage;
ArrayRef<minidump::MemoryDescriptor_64> Descriptors;
bool IsEnd;
};

using FallibleMemory64Iterator = llvm::fallible_iterator<Memory64Iterator>;

/// Returns an iterator that pairs each descriptor with it's respective
/// content from the Memory64List stream. An error is returned if the file
/// does not contain a Memory64List stream, or if the descriptor data is
/// unreadable.
iterator_range<FallibleMemory64Iterator> getMemory64List(Error &Err) const;

/// Returns the list of descriptors embedded in the MemoryInfoList stream. The
/// descriptors provide properties (e.g. permissions) of interesting regions
/// of memory at the time the minidump was taken. An error is returned if the
Expand All @@ -152,15 +244,15 @@ class MinidumpFile : public Binary {
}

/// Return a slice of the given data array, with bounds checking.
static Expected<ArrayRef<uint8_t>> getDataSlice(ArrayRef<uint8_t> Data,
size_t Offset, size_t Size);
static Expected<ArrayRef<uint8_t>>
getDataSlice(ArrayRef<uint8_t> Data, uint64_t Offset, uint64_t Size);

/// Return the slice of the given data array as an array of objects of the
/// given type. The function checks that the input array is large enough to
/// contain the correct number of objects of the given type.
template <typename T>
static Expected<ArrayRef<T>> getDataSliceAs(ArrayRef<uint8_t> Data,
size_t Offset, size_t Count);
uint64_t Offset, uint64_t Count);

MinidumpFile(MemoryBufferRef Source, const minidump::Header &Header,
ArrayRef<minidump::Directory> Streams,
Expand Down Expand Up @@ -199,15 +291,16 @@ Expected<const T &> MinidumpFile::getStream(minidump::StreamType Type) const {

template <typename T>
Expected<ArrayRef<T>> MinidumpFile::getDataSliceAs(ArrayRef<uint8_t> Data,
size_t Offset,
size_t Count) {
uint64_t Offset,
uint64_t Count) {
// Check for overflow.
if (Count > std::numeric_limits<size_t>::max() / sizeof(T))
if (Count > std::numeric_limits<uint64_t>::max() / sizeof(T))
return createEOFError();
Expected<ArrayRef<uint8_t>> Slice =
getDataSlice(Data, Offset, sizeof(T) * Count);
if (!Slice)
return Slice.takeError();

return ArrayRef<T>(reinterpret_cast<const T *>(Slice->data()), Count);
}

Expand Down
28 changes: 28 additions & 0 deletions llvm/include/llvm/ObjectYAML/MinidumpYAML.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct Stream {
Exception,
MemoryInfoList,
MemoryList,
Memory64List,
ModuleList,
RawContent,
SystemInfo,
Expand Down Expand Up @@ -98,12 +99,30 @@ struct ParsedMemoryDescriptor {
minidump::MemoryDescriptor Entry;
yaml::BinaryRef Content;
};

struct ParsedMemory64Descriptor {
static constexpr Stream::StreamKind Kind = Stream::StreamKind::Memory64List;
static constexpr minidump::StreamType Type =
minidump::StreamType::Memory64List;

minidump::MemoryDescriptor_64 Entry;
yaml::BinaryRef Content;
};
} // namespace detail

using ModuleListStream = detail::ListStream<detail::ParsedModule>;
using ThreadListStream = detail::ListStream<detail::ParsedThread>;
using MemoryListStream = detail::ListStream<detail::ParsedMemoryDescriptor>;

struct Memory64ListStream
: public detail::ListStream<detail::ParsedMemory64Descriptor> {
minidump::Memory64ListHeader Header;

explicit Memory64ListStream(
std::vector<detail::ParsedMemory64Descriptor> Entries = {})
: ListStream(Entries) {}
};

/// ExceptionStream minidump stream.
struct ExceptionStream : public Stream {
minidump::ExceptionStream MDExceptionStream;
Expand Down Expand Up @@ -244,6 +263,12 @@ template <> struct MappingContextTraits<minidump::MemoryDescriptor, BinaryRef> {
BinaryRef &Content);
};

template <>
struct MappingContextTraits<minidump::MemoryDescriptor_64, BinaryRef> {
static void mapping(IO &IO, minidump::MemoryDescriptor_64 &Memory,
BinaryRef &Content);
};

} // namespace yaml

} // namespace llvm
Expand All @@ -269,11 +294,14 @@ LLVM_YAML_DECLARE_MAPPING_TRAITS(
llvm::MinidumpYAML::ModuleListStream::entry_type)
LLVM_YAML_DECLARE_MAPPING_TRAITS(
llvm::MinidumpYAML::ThreadListStream::entry_type)
LLVM_YAML_DECLARE_MAPPING_TRAITS(
llvm::MinidumpYAML::Memory64ListStream::entry_type)

LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr<llvm::MinidumpYAML::Stream>)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::MinidumpYAML::MemoryListStream::entry_type)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::MinidumpYAML::ModuleListStream::entry_type)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::MinidumpYAML::ThreadListStream::entry_type)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::MinidumpYAML::Memory64ListStream::entry_type)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::minidump::MemoryInfo)

LLVM_YAML_DECLARE_MAPPING_TRAITS(llvm::MinidumpYAML::Object)
Expand Down
45 changes: 43 additions & 2 deletions llvm/lib/Object/Minidump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ template Expected<ArrayRef<Thread>>
template Expected<ArrayRef<MemoryDescriptor>>
MinidumpFile::getListStream(StreamType) const;

Expected<ArrayRef<uint8_t>>
MinidumpFile::getDataSlice(ArrayRef<uint8_t> Data, size_t Offset, size_t Size) {
Expected<ArrayRef<uint8_t>> MinidumpFile::getDataSlice(ArrayRef<uint8_t> Data,
uint64_t Offset,
uint64_t Size) {
// Check for overflow.
if (Offset + Size < Offset || Offset + Size < Size ||
Offset + Size > Data.size())
Expand Down Expand Up @@ -154,3 +155,43 @@ MinidumpFile::create(MemoryBufferRef Source) {
return std::unique_ptr<MinidumpFile>(
new MinidumpFile(Source, Hdr, *ExpectedStreams, std::move(StreamMap)));
}

iterator_range<MinidumpFile::FallibleMemory64Iterator>
MinidumpFile::getMemory64List(Error &Err) const {
ErrorAsOutParameter ErrAsOutParam(&Err);
auto end = FallibleMemory64Iterator::end(Memory64Iterator::end());
Expected<minidump::Memory64ListHeader> ListHeader = getMemoryList64Header();
if (!ListHeader) {
Err = ListHeader.takeError();
return make_range(end, end);
}

std::optional<ArrayRef<uint8_t>> Stream =
getRawStream(StreamType::Memory64List);
if (!Stream) {
Err = createError("No such stream");
return make_range(end, end);
}

Expected<ArrayRef<minidump::MemoryDescriptor_64>> Descriptors =
getDataSliceAs<minidump::MemoryDescriptor_64>(
*Stream, sizeof(Memory64ListHeader),
ListHeader->NumberOfMemoryRanges);

if (!Descriptors) {
Err = Descriptors.takeError();
return make_range(end, end);
}

if (!Descriptors->empty() &&
ListHeader->BaseRVA + Descriptors->front().DataSize > getData().size()) {
Err = createError("Memory64List header RVA out of range");
return make_range(end, end);
}

return make_range(FallibleMemory64Iterator::itr(
Memory64Iterator::begin(
getData().slice(ListHeader->BaseRVA), *Descriptors),
Err),
FallibleMemory64Iterator::end(Memory64Iterator::end()));
}
27 changes: 27 additions & 0 deletions llvm/lib/ObjectYAML/MinidumpEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,30 @@ static size_t layout(BlobAllocator &File, MinidumpYAML::ExceptionStream &S) {
return DataEnd;
}

static size_t layout(BlobAllocator &File, MinidumpYAML::Memory64ListStream &S) {
size_t BaseRVA = File.tell() + sizeof(minidump::Memory64ListHeader);
BaseRVA += S.Entries.size() * sizeof(minidump::MemoryDescriptor_64);
S.Header.BaseRVA = BaseRVA;
S.Header.NumberOfMemoryRanges = S.Entries.size();
File.allocateObject(S.Header);
for (auto &E : S.Entries)
File.allocateObject(E.Entry);

// Save the new offset for the stream size.
size_t DataEnd = File.tell();
for (auto &E : S.Entries) {
File.allocateBytes(E.Content);
if (E.Entry.DataSize > E.Content.binary_size()) {
size_t Padding = E.Entry.DataSize - E.Content.binary_size();
File.allocateCallback(Padding, [Padding](raw_ostream &OS) {
OS << std::string(Padding, '\0');
});
}
}

return DataEnd;
}

static void layout(BlobAllocator &File, MemoryListStream::entry_type &Range) {
Range.Entry.Memory = layout(File, Range.Content);
}
Expand Down Expand Up @@ -190,6 +214,9 @@ static Directory layout(BlobAllocator &File, Stream &S) {
case Stream::StreamKind::MemoryList:
DataEnd = layout(File, cast<MemoryListStream>(S));
break;
case Stream::StreamKind::Memory64List:
DataEnd = layout(File, cast<Memory64ListStream>(S));
break;
case Stream::StreamKind::ModuleList:
DataEnd = layout(File, cast<ModuleListStream>(S));
break;
Expand Down
Loading