blob: 63aa99823f6cea8ad52bc0179a1c0d288f1c811e [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// A utility class for reading minidumps.
#ifndef SYZYGY_MINIDUMP_MINIDUMP_H_
#define SYZYGY_MINIDUMP_MINIDUMP_H_
#include <windows.h> // NOLINT
#include <dbghelp.h>
#include <stdint.h>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
namespace minidump {
namespace internal {
// Provides the default header parsing for the TypedMinidumpStream class.
class DefaultHeaderParser {
public:
static size_t Parse(const MINIDUMP_MEMORY_LIST& header);
static size_t Parse(const MINIDUMP_MODULE_LIST& header);
static size_t Parse(const MINIDUMP_THREAD_LIST& header);
static size_t Parse(const MINIDUMP_THREAD_EX_LIST& header);
};
} // namespace internal
// fwd.
template <typename HeaderType,
typename ElementType,
size_t (*ParseHeaderFunction)(const HeaderType& hdr) =
internal::DefaultHeaderParser::Parse>
class TypedMinidumpStream;
template <typename ElementType>
class TypedMinidumpStreamIterator;
class Minidump {
public:
using TypedMemoryList =
TypedMinidumpStream<MINIDUMP_MEMORY_LIST, MINIDUMP_MEMORY_DESCRIPTOR>;
using TypedModuleList =
TypedMinidumpStream<MINIDUMP_MODULE_LIST, MINIDUMP_MODULE>;
using TypedThreadList =
TypedMinidumpStream<MINIDUMP_THREAD_LIST, MINIDUMP_THREAD>;
using TypedThreadExList =
TypedMinidumpStream<MINIDUMP_THREAD_EX_LIST, MINIDUMP_THREAD_EX>;
static const size_t kNoStreamId = static_cast<size_t>(-1);
class Stream;
Minidump();
~Minidump();
// @name Typed stream accessors.
// These functions retrieve typed stream accessors to particular well-known
// streams in the mindump directory.
// @{
TypedMemoryList GetMemoryList() const;
TypedModuleList GetModuleList() const;
TypedThreadList GetThreadList() const;
TypedThreadExList GetThreadExList() const;
// @}
// Returns a stream for @p location.
// @param location defines the offset and length of the returned stream.
Stream GetStreamFor(const MINIDUMP_LOCATION_DESCRIPTOR& location) const;
// Returns a stream for the file's @p stream_id.
// @param stream_id the stream id to return, must be a valid stream id.
Stream GetStream(size_t stream_id) const;
// Find the next stream of type @p stream_type.
// @param prev the previous stream of this type or nullptr.
// @param stream_type the stream type to look for.
// @returns a valid stream if one can be found, otherwise an invalid stream.
Stream FindNextStream(const Stream* prev, size_t stream_type) const;
// Accessors.
const std::vector<MINIDUMP_DIRECTORY>& directory() const {
return directory_;
}
protected:
friend class Stream;
// @name Data accessors.
// Reads file contents.
// @param offset the file offset to read from.
// @param data_size the amount of data to read.
// @param data where to write the data, must be of size @p data_data size or
// larger.
// @returns true on success, false on failure, including a short read.
virtual bool ReadBytes(size_t offset, size_t data_size, void* data) const = 0;
bool ReadDirectory();
std::vector<MINIDUMP_DIRECTORY> directory_;
private:
DISALLOW_COPY_AND_ASSIGN(Minidump);
};
// Allows parsing a minidump from a file.
class FileMinidump : public Minidump {
public:
// Opens the minidump file at @p path and verifies its header structure.
// @param path the minidump file to open.
// @return true on success, false on failure.
bool Open(const base::FilePath& path);
protected:
bool ReadBytes(size_t offset, size_t data_size, void* data) const override;
private:
base::ScopedFILE file_;
};
// Allows parsing a minidump from an in-memory buffer.
class BufferMinidump : public Minidump {
public:
BufferMinidump();
// Initializes the minidump to the contents of @p buf[0.. @p buf_len].
// Note that @p buf must outlive this instance, as it does not take ownership
// of the buffer, nor copy it.
// @param buf pointer to the buffer containing the minidump.
// @param buf_len the size of the @p buf buffer.
// @returns true on success, false on failure.
bool Initialize(const uint8_t* buf, size_t buf_len);
protected:
bool ReadBytes(size_t offset, size_t data_size, void* data) const override;
private:
// Not owned.
const uint8_t* buf_;
size_t buf_len_;
};
// A forward-only reading class that bounds reads to streams that make it safe
// and easy to parse minidump streams. Streams are lightweight objects that
// can be freely copied.
// Note that a stream has a current position and a remaining length, and no
// independent start position. It's therefore not possible to "rewind" a
// stream.
class Minidump::Stream {
public:
Stream();
Stream(const Minidump* minidump, size_t offset, size_t length,
size_t stream_id);
bool IsValid() const { return minidump_ != nullptr; }
// @name Functions that read and advance over the read data.
// @{
bool ReadAndAdvanceBytes(size_t data_len, void* data);
bool ReadAndAdvanceBytes(size_t data_len, std::string* data);
template <class DataType>
bool ReadAndAdvanceElement(DataType* element);
bool ReadAndAdvanceString(std::wstring* data);
// @}
// @name Functions that will separately read and advance by a number of bytes.
// @{
bool ReadBytes(size_t data_len, void* data);
bool AdvanceBytes(size_t data_len);
// @}
// Accessors.
size_t current_offset() const { return current_offset_; }
size_t remaining_length() const { return remaining_length_; }
size_t stream_id() const { return stream_id_; }
const Minidump* minidump() const { return minidump_; }
private:
const Minidump* minidump_;
size_t current_offset_;
size_t remaining_length_;
size_t stream_id_;
};
// A forward only-iterator for Minidump Streams that yields elements of a
// given, fixed type.
template <typename ElementType>
class TypedMinidumpStreamIterator {
public:
// Creates a new iterator on @p stream. This iterator will yield
// @p stream.GetBytesRemaining() / sizeof(ElementType) elements.
explicit TypedMinidumpStreamIterator(const minidump::Minidump::Stream& stream)
: stream_(stream) {
// Make sure the stream contains a range that covers whole elements.
DCHECK(!stream_.IsValid() ||
(stream.remaining_length() % sizeof(ElementType) == 0));
if (stream.remaining_length() != 0) {
// It's fatal if we can't pre-read the element that should be there.
CHECK(stream_.ReadBytes(sizeof(element_), &element_));
}
}
TypedMinidumpStreamIterator(const TypedMinidumpStreamIterator& o)
: stream_(o.stream), element_(o.element_) {}
void operator++() {
// It's invalid to advance the end iterator.
DCHECK_NE(0u, stream_.remaining_length());
// It's fatal if we can't advance over the current element.
CHECK(stream_.AdvanceBytes(sizeof(element_)));
if (stream_.remaining_length()) {
// Not yet at end, read the current element. Fatal if this fails.
CHECK(stream_.ReadBytes(sizeof(element_), &element_));
}
}
bool operator!=(const TypedMinidumpStreamIterator& o) const {
// Only iterators on the same minidump can be compared.
DCHECK_EQ(stream_.minidump(), o.stream_.minidump());
return stream_.current_offset() != o.stream_.current_offset();
}
const ElementType& operator*() const {
DCHECK_NE(0u, stream_.remaining_length());
return element_;
}
private:
// Disallow default construction.
TypedMinidumpStreamIterator() {}
minidump::Minidump::Stream stream_;
ElementType element_;
};
// A typed minidump stream allows reading a stream header and iterating over
// the elements of the stream.
template <typename HeaderType,
typename ElementType,
size_t (*ParseHeaderFunction)(const HeaderType& hdr)>
class TypedMinidumpStream {
public:
using Iterator = TypedMinidumpStreamIterator<ElementType>;
// Initializes this instance to a stream of type @p stream_type in
// @p minidump.
TypedMinidumpStream(const Minidump& minidump, size_t stream_type);
TypedMinidumpStream(const TypedMinidumpStream& other) = default;
bool IsValid() const { return element_stream_.IsValid(); }
const HeaderType& header() const {
return *reinterpret_cast<const HeaderType*>(header_storage_);
}
Iterator begin() const { return Iterator(element_stream_); }
Iterator end() const {
return Iterator(Minidump::Stream(
element_stream_.minidump(),
element_stream_.current_offset() + element_stream_.remaining_length(),
0, element_stream_.stream_id()));
}
private:
// Initializes this instance to a stream of type @p stream_type in
// @p minidump.
// @returns true on success, false if the stream doesn't exist, is not
// unique, or the stream header can't be read.
bool Initialize(const Minidump& minidump, size_t stream_type);
// The stream we read elements from, this must be constrained to the
// range elements occupy, e.g. positioned at the start of the first element
// and span a multiple of sizeof(ElementType) bytes.
Minidump::Stream element_stream_;
// Some of the MINIDUMP_* headers declare a zero element array as placeholder
// for the elements. Since such structures can't be directly instantiated,
// we read them into a byte array instead.
uint8_t header_storage_[sizeof(HeaderType)];
};
template <typename HeaderType,
typename ElementType,
size_t (*ParseHeaderFunction)(const HeaderType& hdr)>
TypedMinidumpStream<HeaderType, ElementType, ParseHeaderFunction>::
TypedMinidumpStream(const Minidump& minidump, size_t stream_type) {
memset(header_storage_, 0, sizeof(header_storage_));
Initialize(minidump, stream_type);
}
template <typename HeaderType,
typename ElementType,
size_t (*ParseHeaderFunction)(const HeaderType& hdr)>
bool TypedMinidumpStream<HeaderType, ElementType, ParseHeaderFunction>::
Initialize(const Minidump& minidump, size_t stream_type) {
// Find the first stream of the requested type.
Minidump::Stream stream = minidump.FindNextStream(nullptr, stream_type);
if (!stream.IsValid())
return false;
// Make sure the stream is unique.
if (minidump.FindNextStream(&stream, stream_type).IsValid())
return false;
// Read and advance over the header.
if (!stream.ReadAndAdvanceBytes(sizeof(header_storage_), header_storage_))
return false;
size_t number_of_elements = ParseHeaderFunction(header());
// Make sure the stream has appropriate byte length.
if (stream.remaining_length() != number_of_elements * sizeof(ElementType))
return false;
element_stream_ = stream;
return true;
}
template <typename DataType>
bool Minidump::Stream::ReadAndAdvanceElement(DataType* element) {
return ReadAndAdvanceBytes(sizeof(DataType), element);
}
} // namespace minidump
#endif // SYZYGY_MINIDUMP_MINIDUMP_H_