mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 22:16:13 +00:00
Introduce MinidumpWritable, its dependencies, and their tests.
MinidumpWritable is the base class for all minidump-writing operations. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/432003005
This commit is contained in:
parent
dfe81014f7
commit
995012534e
2
DEPS
2
DEPS
@ -28,7 +28,7 @@ deps = {
|
||||
'39bb8956231c997babf0f25befdfb531f4d0b43c', # svn r1958
|
||||
'crashpad/third_party/mini_chromium/mini_chromium':
|
||||
Var('chromium_git') + '/chromium/mini_chromium@' +
|
||||
'7e95e5859f79ef7fe4163e797f99768e13b86132',
|
||||
'58160e47f55227d1361c65346e0ad4bf26dfaefe',
|
||||
}
|
||||
|
||||
hooks = [
|
||||
|
@ -20,6 +20,8 @@
|
||||
'suppress_wildcard': 1,
|
||||
'dependencies': [
|
||||
'compat/compat.gyp:*',
|
||||
'minidump/minidump.gyp:*',
|
||||
'util/util.gyp:*',
|
||||
],
|
||||
'sources': [
|
||||
'crashpad.doxy.h',
|
||||
|
53
minidump/minidump.gyp
Normal file
53
minidump/minidump.gyp
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'minidump',
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'../compat/compat.gyp:compat',
|
||||
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
|
||||
'../util/util.gyp:util',
|
||||
],
|
||||
'export_dependent_settings': [
|
||||
'../compat/compat.gyp:compat',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
'sources': [
|
||||
'minidump_writable.cc',
|
||||
'minidump_writable.h',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'minidump_test',
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'minidump',
|
||||
'../third_party/gtest/gtest.gyp:gtest',
|
||||
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
'sources': [
|
||||
'../third_party/gtest/gtest/src/gtest_main.cc',
|
||||
'minidump_writable_test.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
264
minidump/minidump_writable.cc
Normal file
264
minidump/minidump_writable.cc
Normal file
@ -0,0 +1,264 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#include "minidump/minidump_writable.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "util/numeric/safe_assignment.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const size_t kMaximumAlignment = 16;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
||||
bool MinidumpWritable::WriteEverything(FileWriterInterface* file_writer) {
|
||||
DCHECK_EQ(state_, kStateMutable);
|
||||
|
||||
if (!Freeze()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DCHECK_EQ(state_, kStateFrozen);
|
||||
|
||||
off_t offset = 0;
|
||||
std::vector<MinidumpWritable*> write_sequence;
|
||||
size_t size = WillWriteAtOffset(kPhaseEarly, &offset, &write_sequence);
|
||||
if (size == kInvalidSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += size;
|
||||
if (WillWriteAtOffset(kPhaseLate, &offset, &write_sequence) == kInvalidSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DCHECK_EQ(state_, kStateWritable);
|
||||
DCHECK_EQ(write_sequence.front(), this);
|
||||
|
||||
for (MinidumpWritable* writable : write_sequence) {
|
||||
if (!writable->WritePaddingAndObject(file_writer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DCHECK_EQ(state_, kStateWritten);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MinidumpWritable::RegisterRVA(RVA* rva) {
|
||||
DCHECK_LE(state_, kStateFrozen);
|
||||
|
||||
registered_rvas_.push_back(rva);
|
||||
}
|
||||
|
||||
void MinidumpWritable::RegisterLocationDescriptor(
|
||||
MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor) {
|
||||
DCHECK_LE(state_, kStateFrozen);
|
||||
|
||||
registered_location_descriptors_.push_back(location_descriptor);
|
||||
}
|
||||
|
||||
const size_t MinidumpWritable::kInvalidSize =
|
||||
std::numeric_limits<size_t>::max();
|
||||
|
||||
MinidumpWritable::MinidumpWritable()
|
||||
: registered_rvas_(),
|
||||
registered_location_descriptors_(),
|
||||
leading_pad_bytes_(0),
|
||||
state_(kStateMutable) {
|
||||
}
|
||||
|
||||
MinidumpWritable::~MinidumpWritable() {
|
||||
}
|
||||
|
||||
bool MinidumpWritable::Freeze() {
|
||||
DCHECK_EQ(state_, kStateMutable);
|
||||
state_ = kStateFrozen;
|
||||
|
||||
std::vector<MinidumpWritable*> children = Children();
|
||||
for (MinidumpWritable* child : children) {
|
||||
if (!child->Freeze()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t MinidumpWritable::Alignment() {
|
||||
DCHECK_GE(state_, kStateFrozen);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
std::vector<MinidumpWritable*> MinidumpWritable::Children() {
|
||||
DCHECK_GE(state_, kStateFrozen);
|
||||
|
||||
return std::vector<MinidumpWritable*>();
|
||||
}
|
||||
|
||||
MinidumpWritable::Phase MinidumpWritable::WritePhase() {
|
||||
return kPhaseEarly;
|
||||
}
|
||||
|
||||
size_t MinidumpWritable::WillWriteAtOffset(
|
||||
Phase phase,
|
||||
off_t* offset,
|
||||
std::vector<MinidumpWritable*>* write_sequence) {
|
||||
off_t local_offset = *offset;
|
||||
CHECK_GE(local_offset, 0);
|
||||
|
||||
size_t leading_pad_bytes_this_phase;
|
||||
size_t size;
|
||||
if (phase == WritePhase()) {
|
||||
DCHECK_EQ(state_, kStateFrozen);
|
||||
|
||||
// Add this object to the sequence of MinidumpWritable objects to be
|
||||
// written.
|
||||
write_sequence->push_back(this);
|
||||
|
||||
size = SizeOfObject();
|
||||
|
||||
if (size > 0) {
|
||||
// Honor this object’s request to be aligned to a specific byte boundary.
|
||||
// Once the alignment is corrected, this object knows exactly what file
|
||||
// offset it will be written at.
|
||||
size_t alignment = Alignment();
|
||||
CHECK_LE(alignment, kMaximumAlignment);
|
||||
|
||||
leading_pad_bytes_this_phase =
|
||||
(alignment - (local_offset % alignment)) % alignment;
|
||||
local_offset += leading_pad_bytes_this_phase;
|
||||
*offset = local_offset;
|
||||
} else {
|
||||
// If the object is size 0, alignment is of no concern.
|
||||
leading_pad_bytes_this_phase = 0;
|
||||
}
|
||||
leading_pad_bytes_ = leading_pad_bytes_this_phase;
|
||||
|
||||
// Now that the file offset that this object will be written at is known,
|
||||
// let the subclass implementation know in case it’s interested.
|
||||
if (!WillWriteAtOffsetImpl(local_offset)) {
|
||||
return kInvalidSize;
|
||||
}
|
||||
|
||||
// Populate the RVA fields in other objects that have registered to point to
|
||||
// this one. Typically, a parent object will have registered to point to its
|
||||
// children, but this can also occur where no parent-child relationship
|
||||
// exists.
|
||||
if (!registered_rvas_.empty() ||
|
||||
!registered_location_descriptors_.empty()) {
|
||||
RVA local_rva;
|
||||
if (!AssignIfInRange(&local_rva, local_offset)) {
|
||||
LOG(ERROR) << "offset " << local_offset << " out of range";
|
||||
return kInvalidSize;
|
||||
}
|
||||
|
||||
for (RVA* rva : registered_rvas_) {
|
||||
*rva = local_rva;
|
||||
}
|
||||
|
||||
if (!registered_location_descriptors_.empty()) {
|
||||
typeof(registered_location_descriptors_[0]->DataSize) local_size;
|
||||
if (!AssignIfInRange(&local_size, size)) {
|
||||
LOG(ERROR) << "size " << size << " out of range";
|
||||
return kInvalidSize;
|
||||
}
|
||||
|
||||
for (MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor :
|
||||
registered_location_descriptors_) {
|
||||
location_descriptor->DataSize = local_size;
|
||||
location_descriptor->Rva = local_rva;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This object is now considered writable. However, if it contains RVA or
|
||||
// MINIDUMP_LOCATION_DESCRIPTOR fields, they may not be fully updated yet,
|
||||
// because it’s the repsonsibility of these fields’ pointees to update them.
|
||||
// Once WillWriteAtOffset has completed running for both phases on an entire
|
||||
// tree, and the entire tree has moved into kStateFrozen, all RVA and
|
||||
// MINIDUMP_LOCATION_DESCRIPTOR fields within that tree will be populated.
|
||||
state_ = kStateWritable;
|
||||
} else {
|
||||
if (phase == kPhaseEarly) {
|
||||
DCHECK_EQ(state_, kStateFrozen);
|
||||
} else {
|
||||
DCHECK_EQ(state_, kStateWritable);
|
||||
}
|
||||
|
||||
size = 0;
|
||||
leading_pad_bytes_this_phase = 0;
|
||||
}
|
||||
|
||||
// Loop over children regardless of whether this object itself will write
|
||||
// during this phase. An object’s children are not required to be written
|
||||
// during the same phase as their parent.
|
||||
std::vector<MinidumpWritable*> children = Children();
|
||||
for (MinidumpWritable* child : children) {
|
||||
// Use “auto” here because it’s impossible to know whether size_t (size) or
|
||||
// off_t (local_offset) is the wider type, and thus what type the result of
|
||||
// adding these two variables will have.
|
||||
auto unaligned_child_offset = local_offset + size;
|
||||
off_t child_offset;
|
||||
if (!AssignIfInRange(&child_offset, unaligned_child_offset)) {
|
||||
LOG(ERROR) << "offset " << unaligned_child_offset << " out of range";
|
||||
return kInvalidSize;
|
||||
}
|
||||
|
||||
size_t child_size =
|
||||
child->WillWriteAtOffset(phase, &child_offset, write_sequence);
|
||||
if (child_size == kInvalidSize) {
|
||||
return kInvalidSize;
|
||||
}
|
||||
|
||||
size += child_size;
|
||||
}
|
||||
|
||||
return leading_pad_bytes_this_phase + size;
|
||||
}
|
||||
|
||||
bool MinidumpWritable::WillWriteAtOffsetImpl(off_t offset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MinidumpWritable::WritePaddingAndObject(FileWriterInterface* file_writer) {
|
||||
DCHECK_EQ(state_, kStateWritable);
|
||||
|
||||
// The number of elements in kZeroes must be at least one less than the
|
||||
// maximum Alignment() ever encountered.
|
||||
const uint8_t kZeroes[kMaximumAlignment - 1] = {};
|
||||
DCHECK_LE(leading_pad_bytes_, arraysize(kZeroes));
|
||||
|
||||
if (leading_pad_bytes_) {
|
||||
if (!file_writer->Write(&kZeroes, leading_pad_bytes_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!WriteObject(file_writer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = kStateWritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
273
minidump/minidump_writable.h
Normal file
273
minidump/minidump_writable.h
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#ifndef CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_
|
||||
#define CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_
|
||||
|
||||
#include <dbghelp.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "util/file/file_writer.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
||||
//! \brief The base class for all content that might be written to a minidump
|
||||
//! file.
|
||||
class MinidumpWritable {
|
||||
public:
|
||||
//! \brief Writes an object and all of its children to a minidump file.
|
||||
//!
|
||||
//! Use this on the root object of a tree of MinidumpWritable objects,
|
||||
//! typically on a MinidumpFileWriter object.
|
||||
//!
|
||||
//! \param[in] file_writer The file writer to receive the minidump file’s
|
||||
//! content.
|
||||
//!
|
||||
//! \return `true` on success. `false` on failure, with an appropriate message
|
||||
//! logged.
|
||||
//!
|
||||
//! \note Valid in #kStateMutable, and transitions the object and the entire
|
||||
//! tree beneath it through all states to #kStateWritten.
|
||||
//!
|
||||
//! \note This method should rarely be overridden.
|
||||
virtual bool WriteEverything(FileWriterInterface* file_writer);
|
||||
|
||||
//! \brief Registers a file offset pointer as one that should point to the
|
||||
//! object on which this method is called.
|
||||
//!
|
||||
//! Once the file offset at which an object will be written is known (when it
|
||||
//! enters #kStateWritable), registered RVA pointers will be updated.
|
||||
//!
|
||||
//! \param[in] rva A pointer to storage for the file offset that should
|
||||
//! contain this object’s writable file offset, once it is known.
|
||||
//!
|
||||
//! \note Valid in #kStateFrozen or any preceding state.
|
||||
//
|
||||
// This is public instead of protected because objects of derived classes need
|
||||
// to be able to register their own pointers with distinct objects.
|
||||
void RegisterRVA(RVA* rva);
|
||||
|
||||
//! \brief Registers a location descriptor as one that should point to the
|
||||
//! object on which this method is called.
|
||||
//!
|
||||
//! Once an object’s size and the file offset at it will be written is known
|
||||
//! (when it enters #kStateFrozen), the relevant data in registered location
|
||||
//! descriptors will be updated.
|
||||
//!
|
||||
//! \param[in] location_descriptor A pointer to a location descriptor that
|
||||
//! should contain this object’s writable size and file offset, once they
|
||||
//! are known.
|
||||
//!
|
||||
//! \note Valid in #kStateFrozen or any preceding state.
|
||||
//
|
||||
// This is public instead of protected because objects of derived classes need
|
||||
// to be able to register their own pointers with distinct objects.
|
||||
void RegisterLocationDescriptor(
|
||||
MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor);
|
||||
|
||||
protected:
|
||||
//! \brief Identifies the state of an object.
|
||||
//!
|
||||
//! Objects will normally transition through each of these states as they are
|
||||
//! created, populated with data, and then written to a minidump file.
|
||||
enum State {
|
||||
//! \brief The object’s properties can be modified.
|
||||
kStateMutable = 0,
|
||||
|
||||
//! \brief The object is “frozen”.
|
||||
//!
|
||||
//! Its properties cannot be modified. Pointers to file offsets of other
|
||||
//! structures may not yet be valid.
|
||||
kStateFrozen,
|
||||
|
||||
//! \brief The object is writable.
|
||||
//!
|
||||
//! The file offset at which it will be written is known. Pointers to file
|
||||
//! offsets of other structures are valid when all objects in a tree are in
|
||||
//! this state.
|
||||
kStateWritable,
|
||||
|
||||
//! \brief The object has been written to a minidump file.
|
||||
kStateWritten,
|
||||
};
|
||||
|
||||
//! \brief Identifies the phase during which an object will be written to a
|
||||
//! minidump file.
|
||||
enum Phase {
|
||||
//! \brief Objects that are written to a minidump file “early”.
|
||||
//!
|
||||
//! The normal sequence is for an object to write itself and then write all
|
||||
//! of its children.
|
||||
kPhaseEarly = 0,
|
||||
|
||||
//! \brief Objects that are written to a minidump file “late”.
|
||||
//!
|
||||
//! Some objects, such as those capturing memory region snapshots, are
|
||||
//! written to minidump files after all other objects. This “late” phase
|
||||
//! identifies such objects. This is useful to improve spatial locality in
|
||||
//! in minidump files in accordance with expected access patterns: unlike
|
||||
//! most other data, memory snapshots are large and the entire snapshots do
|
||||
//! not need to be consulted in order to process a minidump file.
|
||||
kPhaseLate,
|
||||
};
|
||||
|
||||
//! \brief A size value used to signal failure by methods that return
|
||||
//! `size_t`.
|
||||
static const size_t kInvalidSize;
|
||||
|
||||
MinidumpWritable();
|
||||
~MinidumpWritable();
|
||||
|
||||
//! \brief The state of the object.
|
||||
State state() const { return state_; }
|
||||
|
||||
//! \brief Transitions the object from #kStateMutable to #kStateFrozen.
|
||||
//!
|
||||
//! The default implementation marks the object as frozen and recursively
|
||||
//! calls Freeze() on all of its children. Subclasses may override this method
|
||||
//! to perform processing that should only be done once callers have finished
|
||||
//! populating an object with data. Typically, a subclass implementation would
|
||||
//! call RegisterRVA() or RegisterLocationDescriptor() on other objects as
|
||||
//! appropriate, because at the time Freeze() runs, the in-memory locations of
|
||||
//! RVAs and location descriptors are known and will not change for the
|
||||
//! remaining duration of an object’s lifetime.
|
||||
//!
|
||||
//! \return `true` on success. `false` on failure, with an appropriate message
|
||||
//! logged.
|
||||
virtual bool Freeze();
|
||||
|
||||
//! \brief Returns the amount of space that this object will consume when
|
||||
//! written to a minidump file, in bytes, not including any leading or
|
||||
//! trailing padding necessary to maintain proper alignment.
|
||||
//!
|
||||
//! \note Valid in #kStateFrozen or any subsequent state.
|
||||
virtual size_t SizeOfObject() = 0;
|
||||
|
||||
//! \brief Returns the object’s desired byte-boundary alignment.
|
||||
//!
|
||||
//! The default implementation returns `4`. Subclasses may override this as
|
||||
//! needed.
|
||||
//!
|
||||
//! \note Valid in #kStateFrozen or any subsequent state.
|
||||
virtual size_t Alignment();
|
||||
|
||||
//! \brief Returns the object’s children.
|
||||
//!
|
||||
//! \note Valid in #kStateFrozen or any subsequent state.
|
||||
virtual std::vector<MinidumpWritable*> Children();
|
||||
|
||||
//! \brief Returns the object’s desired write phase.
|
||||
//!
|
||||
//! The default implementation returns #kPhaseEarly. Subclasses may override
|
||||
//! this method to alter their write phase.
|
||||
//!
|
||||
//! \note Valid in any state.
|
||||
virtual Phase WritePhase();
|
||||
|
||||
//! \brief Prepares the object to be written at a known file offset,
|
||||
//! transitioning it from #kStateFrozen to #kStateWritable.
|
||||
//!
|
||||
//! This method is responsible for determining the final file offset of the
|
||||
//! object, which may be increased from \a offset to meet alignment
|
||||
//! requirements. It calls WillWriteAtOffsetImpl() for the benefit of
|
||||
//! subclasses. It populates all RVAs and location descriptors registered with
|
||||
//! it via RegisterRVA() and RegisterLocationDescriptor(). It also recurses
|
||||
//! into all known children.
|
||||
//!
|
||||
//! \param[in] phase The phase during which the object will be written. If
|
||||
//! this does not match Phase(), processing is suppressed, although
|
||||
//! recursive processing will still occur on all children. This addresses
|
||||
//! the case where parents and children do not write in the same phase.
|
||||
//! \param[in] offset The file offset at which the object will be written. The
|
||||
//! offset may need to be adjusted for alignment.
|
||||
//! \param[out] write_sequence This object will append itself to this list,
|
||||
//! such that on return from a recursive tree of WillWriteAtOffset()
|
||||
//! calls, elements of the vector will be organized in the sequence that
|
||||
//! the objects will be written to the minidump file.
|
||||
//!
|
||||
//! \return The file size consumed by this object and all children, including
|
||||
//! any padding inserted to meet alignment requirements. On failure,
|
||||
//! #kInvalidSize, with an appropriate message logged.
|
||||
//!
|
||||
//! \note This method cannot be overridden. Subclasses that need to perform
|
||||
//! processing when an object transitions to #kStateWritable should
|
||||
//! implement WillWriteAtOffsetImpl(), which is called by this method.
|
||||
size_t WillWriteAtOffset(Phase phase,
|
||||
off_t* offset,
|
||||
std::vector<MinidumpWritable*>* write_sequence);
|
||||
|
||||
//! \brief Called once an object’s writable file offset is determined, as it
|
||||
//! transitions into #kStateWritable.
|
||||
//!
|
||||
//! Subclasses can override this method if they need to provide additional
|
||||
//! processing once their writable file offset is known. Typically, this will
|
||||
//! be done by subclasses that handle certain RVAs themselves instead of using
|
||||
//! the RegisterRVA() interface.
|
||||
//!
|
||||
//! \param[in] offset The file offset at which the object will be written. The
|
||||
//! value passed to this method will already have been adjusted to meet
|
||||
//! alignment requirements.
|
||||
//!
|
||||
//! \return `true` on success. `false` on error, indicating that the minidump
|
||||
//! file should not be written.
|
||||
//!
|
||||
//! \note Valid in #kStateFrozen. The object will transition to
|
||||
//! #kStateWritable after this method returns.
|
||||
virtual bool WillWriteAtOffsetImpl(off_t offset);
|
||||
|
||||
//! \brief Writes the object, transitioning it from #kStateWritable to
|
||||
//! #kStateWritten.
|
||||
//!
|
||||
//! Writes any padding necessary to meet alignment requirements, and then
|
||||
//! calls WriteObject() to write the object’s content.
|
||||
//!
|
||||
//! \param[in] file_writer The file writer to receive the object’s content.
|
||||
//!
|
||||
//! \return `true` on success. `false` on error with an appropriate message
|
||||
//! logged.
|
||||
//!
|
||||
//! \note This method cannot be overridden. Subclasses must override
|
||||
//! WriteObject().
|
||||
bool WritePaddingAndObject(FileWriterInterface* file_writer);
|
||||
|
||||
//! \brief Writes the object’s content.
|
||||
//!
|
||||
//! \param[in] file_writer The file writer to receive the object’s content.
|
||||
//!
|
||||
//! \return `true` on success. `false` on error, indicating that the content
|
||||
//! could not be written to the minidump file.
|
||||
//!
|
||||
//! \note Valid in #kStateWritable. The object will transition to
|
||||
//! #kStateWritten after this method returns.
|
||||
virtual bool WriteObject(FileWriterInterface* file_writer) = 0;
|
||||
|
||||
private:
|
||||
std::vector<RVA*> registered_rvas_;
|
||||
std::vector<MINIDUMP_LOCATION_DESCRIPTOR*> registered_location_descriptors_;
|
||||
size_t leading_pad_bytes_;
|
||||
State state_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MinidumpWritable);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_
|
835
minidump/minidump_writable_test.cc
Normal file
835
minidump/minidump_writable_test.cc
Normal file
@ -0,0 +1,835 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#include "minidump/minidump_writable.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/file/string_file_writer.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace crashpad;
|
||||
using namespace testing;
|
||||
|
||||
class BaseTestMinidumpWritable : public crashpad::internal::MinidumpWritable {
|
||||
public:
|
||||
BaseTestMinidumpWritable()
|
||||
: MinidumpWritable(),
|
||||
children_(),
|
||||
expected_offset_(-1),
|
||||
alignment_(0),
|
||||
phase_(kPhaseEarly),
|
||||
has_alignment_(false),
|
||||
has_phase_(false),
|
||||
verified_(false) {}
|
||||
|
||||
~BaseTestMinidumpWritable() { EXPECT_TRUE(verified_); }
|
||||
|
||||
void SetAlignment(size_t alignment) {
|
||||
alignment_ = alignment;
|
||||
has_alignment_ = true;
|
||||
}
|
||||
|
||||
void AddChild(BaseTestMinidumpWritable* child) { children_.push_back(child); }
|
||||
|
||||
void SetPhaseLate() {
|
||||
phase_ = kPhaseLate;
|
||||
has_phase_ = true;
|
||||
}
|
||||
|
||||
void Verify() {
|
||||
verified_ = true;
|
||||
EXPECT_EQ(kStateWritten, state());
|
||||
for (BaseTestMinidumpWritable* child : children_) {
|
||||
child->Verify();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Freeze() override {
|
||||
EXPECT_EQ(kStateMutable, state());
|
||||
bool rv = MinidumpWritable::Freeze();
|
||||
EXPECT_TRUE(rv);
|
||||
EXPECT_EQ(kStateFrozen, state());
|
||||
return rv;
|
||||
}
|
||||
|
||||
virtual size_t Alignment() override {
|
||||
EXPECT_GE(state(), kStateFrozen);
|
||||
return has_alignment_ ? alignment_ : MinidumpWritable::Alignment();
|
||||
}
|
||||
|
||||
virtual std::vector<MinidumpWritable*> Children() override {
|
||||
EXPECT_GE(state(), kStateFrozen);
|
||||
if (!children_.empty()) {
|
||||
std::vector<MinidumpWritable*> children;
|
||||
for (BaseTestMinidumpWritable* child : children_) {
|
||||
children.push_back(child);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
return MinidumpWritable::Children();
|
||||
}
|
||||
|
||||
virtual Phase WritePhase() override {
|
||||
return has_phase_ ? phase_ : MinidumpWritable::Phase();
|
||||
}
|
||||
|
||||
virtual bool WillWriteAtOffsetImpl(off_t offset) override {
|
||||
EXPECT_EQ(state(), kStateFrozen);
|
||||
expected_offset_ = offset;
|
||||
bool rv = MinidumpWritable::WillWriteAtOffsetImpl(offset);
|
||||
EXPECT_TRUE(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
virtual bool WriteObject(FileWriterInterface* file_writer) override {
|
||||
EXPECT_EQ(state(), kStateWritable);
|
||||
EXPECT_EQ(expected_offset_, file_writer->Seek(0, SEEK_CUR));
|
||||
|
||||
// Subclasses must override this.
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<BaseTestMinidumpWritable*> children_;
|
||||
off_t expected_offset_;
|
||||
size_t alignment_;
|
||||
Phase phase_;
|
||||
bool has_alignment_;
|
||||
bool has_phase_;
|
||||
bool verified_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BaseTestMinidumpWritable);
|
||||
};
|
||||
|
||||
class TestStringMinidumpWritable final : public BaseTestMinidumpWritable {
|
||||
public:
|
||||
TestStringMinidumpWritable() : BaseTestMinidumpWritable(), data_() {}
|
||||
|
||||
~TestStringMinidumpWritable() {}
|
||||
|
||||
void SetData(const std::string& string) { data_ = string; }
|
||||
|
||||
protected:
|
||||
virtual size_t SizeOfObject() override {
|
||||
EXPECT_GE(state(), kStateFrozen);
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
virtual bool WriteObject(FileWriterInterface* file_writer) override {
|
||||
BaseTestMinidumpWritable::WriteObject(file_writer);
|
||||
EXPECT_TRUE(file_writer->Write(&data_[0], data_.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestStringMinidumpWritable);
|
||||
};
|
||||
|
||||
TEST(MinidumpWritable, MinidumpWritable) {
|
||||
StringFileWriter writer;
|
||||
|
||||
{
|
||||
SCOPED_TRACE("empty");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable string_writable;
|
||||
EXPECT_TRUE(string_writable.WriteEverything(&writer));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
string_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("childless");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable string_writable;
|
||||
string_writable.SetData("a");
|
||||
EXPECT_TRUE(string_writable.WriteEverything(&writer));
|
||||
EXPECT_EQ(1u, writer.string().size());
|
||||
EXPECT_EQ("a", writer.string());
|
||||
string_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("b");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("c");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ(std::string("b\0\0\0c", 5), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("base alignment 2");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("de");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("f");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ(std::string("de\0\0f", 5), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("base alignment 3");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("ghi");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("j");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ(std::string("ghi\0j", 5), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("base alignment 4");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("klmn");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("o");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("klmno", writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("base alignment 5");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("pqrst");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("u");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(9u, writer.string().size());
|
||||
EXPECT_EQ(std::string("pqrst\0\0\0u", 9), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("two children");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("parent");
|
||||
TestStringMinidumpWritable child_0;
|
||||
child_0.SetData("child_0");
|
||||
parent.AddChild(&child_0);
|
||||
TestStringMinidumpWritable child_1;
|
||||
child_1.SetData("child_1");
|
||||
parent.AddChild(&child_1);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(23u, writer.string().size());
|
||||
EXPECT_EQ(std::string("parent\0\0child_0\0child_1", 23), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchild");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("parent");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("child");
|
||||
parent.AddChild(&child);
|
||||
TestStringMinidumpWritable grandchild;
|
||||
grandchild.SetData("grandchild");
|
||||
child.AddChild(&grandchild);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(26u, writer.string().size());
|
||||
EXPECT_EQ(std::string("parent\0\0child\0\0\0grandchild", 26),
|
||||
writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchild with empty parent");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("child");
|
||||
parent.AddChild(&child);
|
||||
TestStringMinidumpWritable grandchild;
|
||||
grandchild.SetData("grandchild");
|
||||
child.AddChild(&grandchild);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(18u, writer.string().size());
|
||||
EXPECT_EQ(std::string("child\0\0\0grandchild", 18), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchild with empty child");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("parent");
|
||||
TestStringMinidumpWritable child;
|
||||
parent.AddChild(&child);
|
||||
TestStringMinidumpWritable grandchild;
|
||||
grandchild.SetData("grandchild");
|
||||
child.AddChild(&grandchild);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(18u, writer.string().size());
|
||||
EXPECT_EQ(std::string("parent\0\0grandchild", 18), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchild with empty grandchild");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("parent");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("child");
|
||||
parent.AddChild(&child);
|
||||
TestStringMinidumpWritable grandchild;
|
||||
child.AddChild(&grandchild);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(13u, writer.string().size());
|
||||
EXPECT_EQ(std::string("parent\0\0child", 13), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchild with late-phase grandchild");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("parent");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("child");
|
||||
parent.AddChild(&child);
|
||||
TestStringMinidumpWritable grandchild;
|
||||
grandchild.SetData("grandchild");
|
||||
grandchild.SetPhaseLate();
|
||||
child.AddChild(&grandchild);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(26u, writer.string().size());
|
||||
EXPECT_EQ(std::string("parent\0\0child\0\0\0grandchild", 26),
|
||||
writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchild with late-phase child");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("parent");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("child");
|
||||
child.SetPhaseLate();
|
||||
parent.AddChild(&child);
|
||||
TestStringMinidumpWritable grandchild;
|
||||
grandchild.SetData("grandchild");
|
||||
child.AddChild(&grandchild);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(25u, writer.string().size());
|
||||
EXPECT_EQ(std::string("parent\0\0grandchild\0\0child", 25),
|
||||
writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("family tree");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("P..");
|
||||
TestStringMinidumpWritable child_0;
|
||||
child_0.SetData("C0.");
|
||||
parent.AddChild(&child_0);
|
||||
TestStringMinidumpWritable child_1;
|
||||
child_1.SetData("C1.");
|
||||
parent.AddChild(&child_1);
|
||||
TestStringMinidumpWritable grandchild_00;
|
||||
grandchild_00.SetData("G00");
|
||||
child_0.AddChild(&grandchild_00);
|
||||
TestStringMinidumpWritable grandchild_01;
|
||||
grandchild_01.SetData("G01");
|
||||
child_0.AddChild(&grandchild_01);
|
||||
TestStringMinidumpWritable grandchild_10;
|
||||
grandchild_10.SetData("G10");
|
||||
child_1.AddChild(&grandchild_10);
|
||||
TestStringMinidumpWritable grandchild_11;
|
||||
grandchild_11.SetData("G11");
|
||||
child_1.AddChild(&grandchild_11);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(27u, writer.string().size());
|
||||
EXPECT_EQ(std::string("P..\0C0.\0G00\0G01\0C1.\0G10\0G11", 27),
|
||||
writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("family tree with C0 late");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("P..");
|
||||
TestStringMinidumpWritable child_0;
|
||||
child_0.SetData("C0.");
|
||||
child_0.SetPhaseLate();
|
||||
parent.AddChild(&child_0);
|
||||
TestStringMinidumpWritable child_1;
|
||||
child_1.SetData("C1.");
|
||||
parent.AddChild(&child_1);
|
||||
TestStringMinidumpWritable grandchild_00;
|
||||
grandchild_00.SetData("G00");
|
||||
child_0.AddChild(&grandchild_00);
|
||||
TestStringMinidumpWritable grandchild_01;
|
||||
grandchild_01.SetData("G01");
|
||||
child_0.AddChild(&grandchild_01);
|
||||
TestStringMinidumpWritable grandchild_10;
|
||||
grandchild_10.SetData("G10");
|
||||
child_1.AddChild(&grandchild_10);
|
||||
TestStringMinidumpWritable grandchild_11;
|
||||
grandchild_11.SetData("G11");
|
||||
child_1.AddChild(&grandchild_11);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(27u, writer.string().size());
|
||||
EXPECT_EQ(std::string("P..\0G00\0G01\0C1.\0G10\0G11\0C0.", 27),
|
||||
writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("family tree with G0 late");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("P..");
|
||||
TestStringMinidumpWritable child_0;
|
||||
child_0.SetData("C0.");
|
||||
parent.AddChild(&child_0);
|
||||
TestStringMinidumpWritable child_1;
|
||||
child_1.SetData("C1.");
|
||||
parent.AddChild(&child_1);
|
||||
TestStringMinidumpWritable grandchild_00;
|
||||
grandchild_00.SetData("G00");
|
||||
grandchild_00.SetPhaseLate();
|
||||
child_0.AddChild(&grandchild_00);
|
||||
TestStringMinidumpWritable grandchild_01;
|
||||
grandchild_01.SetData("G01");
|
||||
grandchild_01.SetPhaseLate();
|
||||
child_0.AddChild(&grandchild_01);
|
||||
TestStringMinidumpWritable grandchild_10;
|
||||
grandchild_10.SetData("G10");
|
||||
child_1.AddChild(&grandchild_10);
|
||||
TestStringMinidumpWritable grandchild_11;
|
||||
grandchild_11.SetData("G11");
|
||||
child_1.AddChild(&grandchild_11);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(27u, writer.string().size());
|
||||
EXPECT_EQ(std::string("P..\0C0.\0C1.\0G10\0G11\0G00\0G01", 27),
|
||||
writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("align 1");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("p");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("c");
|
||||
child.SetAlignment(1);
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(2u, writer.string().size());
|
||||
EXPECT_EQ("pc", writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("align 2");
|
||||
writer.Reset();
|
||||
TestStringMinidumpWritable parent;
|
||||
parent.SetData("p");
|
||||
TestStringMinidumpWritable child;
|
||||
child.SetData("c");
|
||||
child.SetAlignment(2);
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
EXPECT_EQ(3u, writer.string().size());
|
||||
EXPECT_EQ(std::string("p\0c", 3), writer.string());
|
||||
parent.Verify();
|
||||
}
|
||||
}
|
||||
|
||||
class TestRVAMinidumpWritable final : public BaseTestMinidumpWritable {
|
||||
public:
|
||||
TestRVAMinidumpWritable() : BaseTestMinidumpWritable(), rva_() {}
|
||||
|
||||
~TestRVAMinidumpWritable() {}
|
||||
|
||||
void SetRVA(MinidumpWritable* other) { other->RegisterRVA(&rva_); }
|
||||
|
||||
protected:
|
||||
virtual size_t SizeOfObject() override {
|
||||
EXPECT_GE(state(), kStateFrozen);
|
||||
return sizeof(rva_);
|
||||
}
|
||||
|
||||
virtual bool WriteObject(FileWriterInterface* file_writer) override {
|
||||
BaseTestMinidumpWritable::WriteObject(file_writer);
|
||||
EXPECT_TRUE(file_writer->Write(&rva_, sizeof(rva_)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RVA rva_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestRVAMinidumpWritable);
|
||||
};
|
||||
|
||||
RVA RVAAtIndex(const std::string& string, size_t index) {
|
||||
return *reinterpret_cast<const RVA*>(&string[index * sizeof(RVA)]);
|
||||
}
|
||||
|
||||
TEST(MinidumpWritable, RVA) {
|
||||
StringFileWriter writer;
|
||||
|
||||
{
|
||||
SCOPED_TRACE("unset");
|
||||
writer.Reset();
|
||||
TestRVAMinidumpWritable rva_writable;
|
||||
EXPECT_TRUE(rva_writable.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(sizeof(RVA), writer.string().size());
|
||||
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
|
||||
rva_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("self");
|
||||
writer.Reset();
|
||||
TestRVAMinidumpWritable rva_writable;
|
||||
rva_writable.SetRVA(&rva_writable);
|
||||
EXPECT_TRUE(rva_writable.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(sizeof(RVA), writer.string().size());
|
||||
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
|
||||
rva_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child self");
|
||||
writer.Reset();
|
||||
TestRVAMinidumpWritable parent;
|
||||
parent.SetRVA(&parent);
|
||||
TestRVAMinidumpWritable child;
|
||||
child.SetRVA(&child);
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(2 * sizeof(RVA), writer.string().size());
|
||||
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child only");
|
||||
writer.Reset();
|
||||
TestRVAMinidumpWritable parent;
|
||||
TestRVAMinidumpWritable child;
|
||||
parent.SetRVA(&child);
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(2 * sizeof(RVA), writer.string().size());
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
|
||||
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child circular");
|
||||
writer.Reset();
|
||||
TestRVAMinidumpWritable parent;
|
||||
TestRVAMinidumpWritable child;
|
||||
parent.SetRVA(&child);
|
||||
child.SetRVA(&parent);
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(2 * sizeof(RVA), writer.string().size());
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
|
||||
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchildren");
|
||||
writer.Reset();
|
||||
TestRVAMinidumpWritable parent;
|
||||
TestRVAMinidumpWritable child;
|
||||
parent.SetRVA(&child);
|
||||
parent.AddChild(&child);
|
||||
TestRVAMinidumpWritable grandchild_0;
|
||||
grandchild_0.SetRVA(&child);
|
||||
child.AddChild(&grandchild_0);
|
||||
TestRVAMinidumpWritable grandchild_1;
|
||||
grandchild_1.SetRVA(&child);
|
||||
child.AddChild(&grandchild_1);
|
||||
TestRVAMinidumpWritable grandchild_2;
|
||||
grandchild_2.SetRVA(&child);
|
||||
child.AddChild(&grandchild_2);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(5 * sizeof(RVA), writer.string().size());
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
|
||||
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 2));
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 3));
|
||||
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 4));
|
||||
parent.Verify();
|
||||
}
|
||||
}
|
||||
|
||||
class TestLocationDescriptorMinidumpWritable final
|
||||
: public BaseTestMinidumpWritable {
|
||||
public:
|
||||
TestLocationDescriptorMinidumpWritable()
|
||||
: BaseTestMinidumpWritable(), location_descriptor_(), string_() {}
|
||||
|
||||
~TestLocationDescriptorMinidumpWritable() {}
|
||||
|
||||
void SetLocationDescriptor(MinidumpWritable* other) {
|
||||
other->RegisterLocationDescriptor(&location_descriptor_);
|
||||
}
|
||||
|
||||
void SetString(const std::string& string) { string_ = string; }
|
||||
|
||||
protected:
|
||||
virtual size_t SizeOfObject() override {
|
||||
EXPECT_GE(state(), kStateFrozen);
|
||||
// NUL-terminate.
|
||||
return sizeof(location_descriptor_) + string_.size() + 1;
|
||||
}
|
||||
|
||||
virtual bool WriteObject(FileWriterInterface* file_writer) override {
|
||||
BaseTestMinidumpWritable::WriteObject(file_writer);
|
||||
WritableIoVec iov;
|
||||
iov.iov_base = &location_descriptor_;
|
||||
iov.iov_len = sizeof(location_descriptor_);
|
||||
std::vector<WritableIoVec> iovecs(1, iov);
|
||||
// NUL-terminate.
|
||||
iov.iov_base = &string_[0];
|
||||
iov.iov_len = string_.size() + 1;
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_TRUE(file_writer->WriteIoVec(&iovecs));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
MINIDUMP_LOCATION_DESCRIPTOR location_descriptor_;
|
||||
std::string string_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestLocationDescriptorMinidumpWritable);
|
||||
};
|
||||
|
||||
struct LocationDescriptorAndData {
|
||||
MINIDUMP_LOCATION_DESCRIPTOR location_descriptor;
|
||||
char string[1];
|
||||
};
|
||||
|
||||
const LocationDescriptorAndData* LDDAtIndex(const std::string& string,
|
||||
size_t index) {
|
||||
return reinterpret_cast<const LocationDescriptorAndData*>(&string[index]);
|
||||
}
|
||||
|
||||
TEST(MinidumpWritable, LocationDescriptor) {
|
||||
StringFileWriter writer;
|
||||
|
||||
{
|
||||
SCOPED_TRACE("unset");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable location_descriptor_writable;
|
||||
EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(9u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
location_descriptor_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("self");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable location_descriptor_writable;
|
||||
location_descriptor_writable.SetLocationDescriptor(
|
||||
&location_descriptor_writable);
|
||||
EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(9u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(9u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
location_descriptor_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("self with data");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable location_descriptor_writable;
|
||||
location_descriptor_writable.SetLocationDescriptor(
|
||||
&location_descriptor_writable);
|
||||
location_descriptor_writable.SetString("zz");
|
||||
EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(11u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(11u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("zz", ldd->string);
|
||||
location_descriptor_writable.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child self");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable parent;
|
||||
parent.SetLocationDescriptor(&parent);
|
||||
parent.SetString("yy");
|
||||
TestLocationDescriptorMinidumpWritable child;
|
||||
child.SetLocationDescriptor(&child);
|
||||
child.SetString("x");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(22u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(11u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("yy", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 12);
|
||||
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("x", ldd->string);
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child only");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable parent;
|
||||
TestLocationDescriptorMinidumpWritable child;
|
||||
parent.SetLocationDescriptor(&child);
|
||||
parent.SetString("www");
|
||||
child.SetString("vv");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(23u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(11u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("www", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 12);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("vv", ldd->string);
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("parent-child circular");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable parent;
|
||||
TestLocationDescriptorMinidumpWritable child;
|
||||
parent.SetLocationDescriptor(&child);
|
||||
parent.SetString("uuuu");
|
||||
child.SetLocationDescriptor(&parent);
|
||||
child.SetString("tttt");
|
||||
parent.AddChild(&child);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(29u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(13u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(16u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("uuuu", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 16);
|
||||
EXPECT_EQ(13u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("tttt", ldd->string);
|
||||
parent.Verify();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("grandchildren");
|
||||
writer.Reset();
|
||||
TestLocationDescriptorMinidumpWritable parent;
|
||||
TestLocationDescriptorMinidumpWritable child;
|
||||
parent.SetLocationDescriptor(&child);
|
||||
parent.SetString("s");
|
||||
parent.AddChild(&child);
|
||||
child.SetString("r");
|
||||
TestLocationDescriptorMinidumpWritable grandchild_0;
|
||||
grandchild_0.SetLocationDescriptor(&child);
|
||||
grandchild_0.SetString("q");
|
||||
child.AddChild(&grandchild_0);
|
||||
TestLocationDescriptorMinidumpWritable grandchild_1;
|
||||
grandchild_1.SetLocationDescriptor(&child);
|
||||
grandchild_1.SetString("p");
|
||||
child.AddChild(&grandchild_1);
|
||||
TestLocationDescriptorMinidumpWritable grandchild_2;
|
||||
grandchild_2.SetLocationDescriptor(&child);
|
||||
grandchild_2.SetString("o");
|
||||
child.AddChild(&grandchild_2);
|
||||
EXPECT_TRUE(parent.WriteEverything(&writer));
|
||||
|
||||
ASSERT_EQ(58u, writer.string().size());
|
||||
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
|
||||
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("s", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 12);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("r", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 24);
|
||||
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("q", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 36);
|
||||
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("p", ldd->string);
|
||||
ldd = LDDAtIndex(writer.string(), 48);
|
||||
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
|
||||
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
|
||||
EXPECT_STREQ("o", ldd->string);
|
||||
parent.Verify();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
75
util/file/fd_io.cc
Normal file
75
util/file/fd_io.cc
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#include "util/file/fd_io.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct ReadTraits {
|
||||
typedef void* VoidBufferType;
|
||||
typedef char* CharBufferType;
|
||||
static ssize_t Operate(int fd, CharBufferType buffer, size_t size) {
|
||||
return read(fd, buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
struct WriteTraits {
|
||||
typedef const void* VoidBufferType;
|
||||
typedef const char* CharBufferType;
|
||||
static ssize_t Operate(int fd, CharBufferType buffer, size_t size) {
|
||||
return write(fd, buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Traits>
|
||||
ssize_t ReadOrWrite(int fd,
|
||||
typename Traits::VoidBufferType buffer,
|
||||
size_t size) {
|
||||
typename Traits::CharBufferType buffer_c =
|
||||
reinterpret_cast<typename Traits::CharBufferType>(buffer);
|
||||
|
||||
ssize_t total_bytes = 0;
|
||||
while (size > 0) {
|
||||
ssize_t bytes = HANDLE_EINTR(Traits::Operate(fd, buffer_c, size));
|
||||
if (bytes < 0) {
|
||||
return bytes;
|
||||
} else if (bytes == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_c += bytes;
|
||||
size -= bytes;
|
||||
total_bytes += bytes;
|
||||
}
|
||||
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
ssize_t ReadFD(int fd, void* buffer, size_t size) {
|
||||
return ReadOrWrite<ReadTraits>(fd, buffer, size);
|
||||
}
|
||||
|
||||
ssize_t WriteFD(int fd, const void* buffer, size_t size) {
|
||||
return ReadOrWrite<WriteTraits>(fd, buffer, size);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
50
util/file/fd_io.h
Normal file
50
util/file/fd_io.h
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_FILE_FD_IO_H_
|
||||
#define CRASHPAD_UTIL_FILE_FD_IO_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Wraps `read()`, retrying when interrupted or following a short read.
|
||||
//!
|
||||
//! This function reads into \a buffer, stopping only when \a size bytes have
|
||||
//! been read or when `read()` returns 0, indicating that end-of-file has been
|
||||
//! reached.
|
||||
//!
|
||||
//! \return The number of bytes read and placed into \a buffer, or `-1` on
|
||||
//! error, with `errno` set appropriately. On error, a portion of \a fd may
|
||||
//! have been read into \a buffer.
|
||||
//!
|
||||
//! \sa WriteFD
|
||||
ssize_t ReadFD(int fd, void* buffer, size_t size);
|
||||
|
||||
//! \brief Wraps `write()`, retrying when interrupted or following a short
|
||||
//! write.
|
||||
//!
|
||||
//! This function writes to \a fd, stopping only when \a size bytes have been
|
||||
//! written.
|
||||
//!
|
||||
//! \return The number of bytes written from \a buffer, or `-1` on error, with
|
||||
//! `errno` set appropriately. On error, a portion of \a buffer may have
|
||||
//! been written to \a fd.
|
||||
//!
|
||||
//! \sa ReadFD
|
||||
ssize_t WriteFD(int fd, const void* buffer, size_t size);
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_FILE_FD_IO_H_
|
149
util/file/file_writer.cc
Normal file
149
util/file/file_writer.cc
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#include "util/file/file_writer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "util/file/fd_io.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
// Ensure type compatibility between WritableIoVec and iovec.
|
||||
COMPILE_ASSERT(sizeof(WritableIoVec) == sizeof(iovec), WritableIoVec_size);
|
||||
COMPILE_ASSERT(offsetof(WritableIoVec, iov_base) == offsetof(iovec, iov_base),
|
||||
WritableIoVec_base_offset);
|
||||
COMPILE_ASSERT(offsetof(WritableIoVec, iov_len) == offsetof(iovec, iov_len),
|
||||
WritableIoVec_len_offset);
|
||||
|
||||
FileWriter::FileWriter() : fd_() {
|
||||
}
|
||||
|
||||
FileWriter::~FileWriter() {
|
||||
}
|
||||
|
||||
bool FileWriter::Open(const base::FilePath& path, int oflag, mode_t mode) {
|
||||
CHECK(!fd_.is_valid());
|
||||
|
||||
DCHECK((oflag & O_WRONLY) || (oflag & O_RDWR));
|
||||
|
||||
fd_.reset(HANDLE_EINTR(open(path.value().c_str(), oflag, mode)));
|
||||
if (!fd_.is_valid()) {
|
||||
PLOG(ERROR) << "open " << path.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileWriter::Close() {
|
||||
CHECK(fd_.is_valid());
|
||||
|
||||
fd_.reset();
|
||||
}
|
||||
|
||||
bool FileWriter::Write(const void* data, size_t size) {
|
||||
DCHECK(fd_.is_valid());
|
||||
|
||||
// TODO(mark): Write no more than SSIZE_MAX bytes in a single call.
|
||||
ssize_t written = WriteFD(fd_.get(), data, size);
|
||||
if (written < 0) {
|
||||
PLOG(ERROR) << "write";
|
||||
return false;
|
||||
} else if (written == 0) {
|
||||
LOG(ERROR) << "write: returned 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
|
||||
DCHECK(fd_.is_valid());
|
||||
|
||||
ssize_t size = 0;
|
||||
for (const WritableIoVec& iov : *iovecs) {
|
||||
// TODO(mark): Check to avoid overflow of ssize_t, and fail with EINVAL.
|
||||
size += iov.iov_len;
|
||||
}
|
||||
|
||||
// Get an iovec*, because that’s what writev wants. The only difference
|
||||
// between WritableIoVec and iovec is that WritableIoVec’s iov_base is a
|
||||
// pointer to a const buffer, where iovec’s iov_base isn’t. writev doesn’t
|
||||
// actually write to the data, so this cast is safe here. iovec’s iov_base is
|
||||
// non-const because the same structure is used for readv and writev, and
|
||||
// readv needs to write to the buffer that iov_base points to.
|
||||
iovec* iov = reinterpret_cast<iovec*>(&(*iovecs)[0]);
|
||||
size_t remaining_iovecs = iovecs->size();
|
||||
|
||||
while (size > 0) {
|
||||
size_t writev_iovec_count =
|
||||
std::min(remaining_iovecs, static_cast<size_t>(IOV_MAX));
|
||||
ssize_t written = HANDLE_EINTR(writev(fd_.get(), iov, writev_iovec_count));
|
||||
if (written < 0) {
|
||||
PLOG(ERROR) << "writev";
|
||||
return false;
|
||||
} else if (written == 0) {
|
||||
LOG(ERROR) << "writev: returned 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
size -= written;
|
||||
DCHECK_GE(size, 0);
|
||||
|
||||
if (size == 0) {
|
||||
remaining_iovecs = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
while (written > 0) {
|
||||
size_t wrote_this_iovec =
|
||||
std::min(static_cast<size_t>(written), iov->iov_len);
|
||||
written -= wrote_this_iovec;
|
||||
if (wrote_this_iovec < iov->iov_len) {
|
||||
iov->iov_base =
|
||||
reinterpret_cast<char*>(iov->iov_base) + wrote_this_iovec;
|
||||
iov->iov_len -= wrote_this_iovec;
|
||||
} else {
|
||||
++iov;
|
||||
--remaining_iovecs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DCHECK_EQ(remaining_iovecs, 0u);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// The interface says that |iovecs| is not sacred, so scramble it to make sure
|
||||
// that nobody depends on it.
|
||||
memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size());
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
off_t FileWriter::Seek(off_t offset, int whence) {
|
||||
DCHECK(fd_.is_valid());
|
||||
|
||||
off_t rv = lseek(fd_.get(), offset, whence);
|
||||
if (rv < 0) {
|
||||
PLOG(ERROR) << "lseek";
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
130
util/file/file_writer.h
Normal file
130
util/file/file_writer.h
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_FILE_FILE_WRITER_H_
|
||||
#define CRASHPAD_UTIL_FILE_FILE_WRITER_H_
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief A version of `iovec` with a `const` #iov_base field.
|
||||
//!
|
||||
//! This structure is intended to be used for write operations.
|
||||
//
|
||||
// Type compatibility with iovec is tested with static assertions in the
|
||||
// implementation file.
|
||||
struct WritableIoVec {
|
||||
//! \brief The base address of a memory region for output.
|
||||
const void* iov_base;
|
||||
|
||||
//! \brief The size of the memory pointed to by #iov_base.
|
||||
size_t iov_len;
|
||||
};
|
||||
|
||||
//! \brief An interface to write to files and other file-like objects with POSIX
|
||||
//! semantics.
|
||||
class FileWriterInterface {
|
||||
public:
|
||||
//! \brief Wraps `write()` or provides an alternate implementation with
|
||||
//! identical semantics. This method will write the entire buffer,
|
||||
//! continuing after a short write or after being interrupted.
|
||||
//!
|
||||
//! \return `true` if the operation succeeded, `false` if it failed, with an
|
||||
//! error message logged.
|
||||
virtual bool Write(const void* data, size_t size) = 0;
|
||||
|
||||
//! \brief Wraps `writev()` or provides an alternate implementation with
|
||||
//! identical semantics. This method will write the entire buffer,
|
||||
//! continuing after a short write or after being interrupted.
|
||||
//!
|
||||
//! \return `true` if the operation succeeded, `false` if it failed, with an
|
||||
//! error message logged.
|
||||
//!
|
||||
//! \note The contents of \a iovecs are undefined when this method returns.
|
||||
virtual bool WriteIoVec(std::vector<WritableIoVec>* iovecs) = 0;
|
||||
|
||||
//! \brief Wraps `lseek()` or provides an alternate implementation with
|
||||
//! identical semantics.
|
||||
//!
|
||||
//! \return The return value of `lseek()`. `-1` on failure, with an error
|
||||
//! message logged.
|
||||
virtual off_t Seek(off_t offset, int whence) = 0;
|
||||
|
||||
protected:
|
||||
~FileWriterInterface() {}
|
||||
};
|
||||
|
||||
//! \brief A file writer implementation that wraps traditional POSIX file
|
||||
//! operations on files accessed through the filesystem.
|
||||
class FileWriter : public FileWriterInterface {
|
||||
public:
|
||||
FileWriter();
|
||||
~FileWriter();
|
||||
|
||||
//! \brief Wraps `open()`.
|
||||
//!
|
||||
//! \return `true` if the operation succeeded, `false` if it failed, with an
|
||||
//! error message logged.
|
||||
//!
|
||||
//! \note After a successful call, this method cannot be called again until
|
||||
//! after Close().
|
||||
bool Open(const base::FilePath& path, int oflag, mode_t mode);
|
||||
|
||||
//! \brief Wraps `close().`
|
||||
//!
|
||||
//! \note It is only valid to call this method on an object that has had a
|
||||
//! successful Open() that has not yet been matched by a subsequent call
|
||||
//! to this method.
|
||||
void Close();
|
||||
|
||||
// FileWriterInterface:
|
||||
|
||||
//! \copydoc FileWriterInterface::Write()
|
||||
//!
|
||||
//! \note It is only valid to call this method between a successful Open() and
|
||||
//! a Close().
|
||||
virtual bool Write(const void* data, size_t size) override;
|
||||
|
||||
//! \copydoc FileWriterInterface::WriteIoVec()
|
||||
//!
|
||||
//! \note It is only valid to call this method between a successful Open() and
|
||||
//! a Close().
|
||||
virtual bool WriteIoVec(std::vector<WritableIoVec>* iovecs) override;
|
||||
|
||||
//! \copydoc FileWriterInterface::Seek()
|
||||
//!
|
||||
//! \note It is only valid to call this method between a successful Open() and
|
||||
//! a Close().
|
||||
virtual off_t Seek(off_t offset, int whence) override;
|
||||
|
||||
private:
|
||||
base::ScopedFD fd_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FileWriter);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_FILE_FILE_WRITER_H_
|
137
util/file/string_file_writer.cc
Normal file
137
util/file/string_file_writer.cc
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#include "util/file/string_file_writer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/numerics/safe_math.h"
|
||||
#include "util/numeric/safe_assignment.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
StringFileWriter::StringFileWriter() : string_(), offset_(0) {
|
||||
}
|
||||
|
||||
StringFileWriter::~StringFileWriter() {
|
||||
}
|
||||
|
||||
void StringFileWriter::Reset() {
|
||||
string_.clear();
|
||||
offset_ = 0;
|
||||
}
|
||||
|
||||
bool StringFileWriter::Write(const void* data, size_t size) {
|
||||
DCHECK(offset_.IsValid());
|
||||
|
||||
const size_t offset = offset_.ValueOrDie();
|
||||
if (offset > string_.size()) {
|
||||
string_.resize(offset);
|
||||
}
|
||||
|
||||
base::CheckedNumeric<ssize_t> new_offset = offset_;
|
||||
new_offset += size;
|
||||
if (!new_offset.IsValid()) {
|
||||
LOG(ERROR) << "Write(): file too large";
|
||||
return false;
|
||||
}
|
||||
|
||||
string_.replace(offset, size, reinterpret_cast<const char*>(data), size);
|
||||
offset_ = new_offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StringFileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
|
||||
DCHECK(offset_.IsValid());
|
||||
|
||||
if (iovecs->empty()) {
|
||||
LOG(ERROR) << "WriteIoVec(): no iovecs";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid writing anything at all if it would cause an overflow.
|
||||
base::CheckedNumeric<ssize_t> new_offset = offset_;
|
||||
for (const WritableIoVec& iov : *iovecs) {
|
||||
new_offset += iov.iov_len;
|
||||
if (!new_offset.IsValid()) {
|
||||
LOG(ERROR) << "WriteIoVec(): file too large";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const WritableIoVec& iov : *iovecs) {
|
||||
if (!Write(iov.iov_base, iov.iov_len)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// The interface says that |iovecs| is not sacred, so scramble it to make sure
|
||||
// that nobody depends on it.
|
||||
memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size());
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
off_t StringFileWriter::Seek(off_t offset, int whence) {
|
||||
DCHECK(offset_.IsValid());
|
||||
|
||||
size_t base_offset;
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
base_offset = 0;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
base_offset = offset_.ValueOrDie();
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
base_offset = string_.size();
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(ERROR) << "Seek(): invalid whence " << whence;
|
||||
return -1;
|
||||
}
|
||||
|
||||
off_t offset_offt;
|
||||
if (!AssignIfInRange(&offset_offt, base_offset)) {
|
||||
LOG(ERROR) << "Seek(): base_offset " << base_offset << " invalid for off_t";
|
||||
return -1;
|
||||
}
|
||||
|
||||
base::CheckedNumeric<off_t> new_offset(offset_offt);
|
||||
new_offset += offset;
|
||||
if (!new_offset.IsValid()) {
|
||||
LOG(ERROR) << "Seek(): new_offset invalid";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!AssignIfInRange(&offset_offt, new_offset.ValueOrDie())) {
|
||||
LOG(ERROR) << "Seek(): new_offset " << new_offset.ValueOrDie()
|
||||
<< " invalid for size_t";
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset_ = offset_offt;
|
||||
|
||||
return offset_.ValueOrDie();
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
70
util/file/string_file_writer.h
Normal file
70
util/file/string_file_writer.h
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_
|
||||
#define CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/numerics/safe_math.h"
|
||||
#include "util/file/file_writer.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief A file writer backed by a virtual file, as opposed to a file on disk
|
||||
//! or other operating system file descriptor-based file.
|
||||
//!
|
||||
//! The virtual file is a buffer in memory. This class is convenient for use
|
||||
//! with other code that normally expects to write files, when it is impractical
|
||||
//! or inconvenient to write a file. It is expected that tests, in particular,
|
||||
//! will benefit from using this class.
|
||||
class StringFileWriter : public FileWriterInterface {
|
||||
public:
|
||||
StringFileWriter();
|
||||
~StringFileWriter();
|
||||
|
||||
//! \brief Returns a string containing the virtual file’s contents.
|
||||
const std::string& string() const { return string_; }
|
||||
|
||||
//! \brief Resets the virtual file’s contents to be empty, and resets its file
|
||||
//! position to `0`.
|
||||
void Reset();
|
||||
|
||||
// FileWriterInterface:
|
||||
virtual bool Write(const void* data, size_t size) override;
|
||||
virtual bool WriteIoVec(std::vector<WritableIoVec>* iovecs) override;
|
||||
virtual off_t Seek(off_t offset, int whence) override;
|
||||
|
||||
private:
|
||||
//! \brief The virtual file’s contents.
|
||||
std::string string_;
|
||||
|
||||
//! \brief The file offset of the virtual file.
|
||||
//!
|
||||
//! \note This is stored in a `size_t` to match the characteristics of
|
||||
//! #string_, the `std::string` used to store the virtual file’s contents.
|
||||
//! This type will have different characteristics than the `off_t` used to
|
||||
//! report file offsets. The implementation must take care when converting
|
||||
//! between these distinct types.
|
||||
base::CheckedNumeric<size_t> offset_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StringFileWriter);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_
|
378
util/file/string_file_writer_test.cc
Normal file
378
util/file/string_file_writer_test.cc
Normal file
@ -0,0 +1,378 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#include "util/file/string_file_writer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace crashpad;
|
||||
|
||||
TEST(StringFileWriter, EmptyFile) {
|
||||
StringFileWriter writer;
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("", 0));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, OneByteFile) {
|
||||
StringFileWriter writer;
|
||||
|
||||
EXPECT_TRUE(writer.Write("a", 1));
|
||||
EXPECT_EQ(1u, writer.string().size());
|
||||
EXPECT_EQ("a", writer.string());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("b", 1));
|
||||
EXPECT_EQ(1u, writer.string().size());
|
||||
EXPECT_EQ("b", writer.string());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("\0", 1));
|
||||
EXPECT_EQ(1u, writer.string().size());
|
||||
EXPECT_EQ('\0', writer.string()[0]);
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, Reset) {
|
||||
StringFileWriter writer;
|
||||
|
||||
EXPECT_TRUE(writer.Write("abc", 3));
|
||||
EXPECT_EQ(3u, writer.string().size());
|
||||
EXPECT_EQ("abc", writer.string());
|
||||
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
writer.Reset();
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("de", 2));
|
||||
EXPECT_EQ(2u, writer.string().size());
|
||||
EXPECT_EQ("de", writer.string());
|
||||
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
writer.Reset();
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("fghi", 4));
|
||||
EXPECT_EQ(4u, writer.string().size());
|
||||
EXPECT_EQ("fghi", writer.string());
|
||||
EXPECT_EQ(4, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
writer.Reset();
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
// Test resetting after a sparse write.
|
||||
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("j", 1));
|
||||
EXPECT_EQ(2u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0j", 2), writer.string());
|
||||
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
writer.Reset();
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, WriteInvalid) {
|
||||
StringFileWriter writer;
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_FALSE(writer.Write(
|
||||
"", static_cast<size_t>(std::numeric_limits<ssize_t>::max()) + 1));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("a", 1));
|
||||
EXPECT_FALSE(writer.Write(
|
||||
"", static_cast<size_t>(std::numeric_limits<ssize_t>::max())));
|
||||
EXPECT_EQ(1u, writer.string().size());
|
||||
EXPECT_EQ("a", writer.string());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, WriteIoVec) {
|
||||
StringFileWriter writer;
|
||||
|
||||
std::vector<WritableIoVec> iovecs;
|
||||
WritableIoVec iov;
|
||||
iov.iov_base = "";
|
||||
iov.iov_len = 0;
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
iovecs.clear();
|
||||
iov.iov_base = "a";
|
||||
iov.iov_len = 1;
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_EQ(1u, writer.string().size());
|
||||
EXPECT_EQ("a", writer.string());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
iovecs.clear();
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_EQ(2u, writer.string().size());
|
||||
EXPECT_EQ("aa", writer.string());
|
||||
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
iovecs.clear();
|
||||
iovecs.push_back(iov);
|
||||
iov.iov_base = "bc";
|
||||
iov.iov_len = 2;
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("aaabc", writer.string());
|
||||
EXPECT_EQ(5, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("def", 3));
|
||||
EXPECT_EQ(8u, writer.string().size());
|
||||
EXPECT_EQ("aaabcdef", writer.string());
|
||||
EXPECT_EQ(8, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
iovecs.clear();
|
||||
iov.iov_base = "ghij";
|
||||
iov.iov_len = 4;
|
||||
iovecs.push_back(iov);
|
||||
iov.iov_base = "klmno";
|
||||
iov.iov_len = 5;
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_EQ(17u, writer.string().size());
|
||||
EXPECT_EQ("aaabcdefghijklmno", writer.string());
|
||||
EXPECT_EQ(17, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
writer.Reset();
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
iovecs.clear();
|
||||
iov.iov_base = "abcd";
|
||||
iov.iov_len = 4;
|
||||
iovecs.resize(16, iov);
|
||||
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_EQ(64u, writer.string().size());
|
||||
EXPECT_EQ("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||
writer.string());
|
||||
EXPECT_EQ(64, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, WriteIoVecInvalid) {
|
||||
StringFileWriter writer;
|
||||
|
||||
std::vector<WritableIoVec> iovecs;
|
||||
EXPECT_FALSE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
WritableIoVec iov;
|
||||
EXPECT_EQ(1, writer.Seek(1, SEEK_CUR));
|
||||
iov.iov_base = "a";
|
||||
iov.iov_len = std::numeric_limits<ssize_t>::max();
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_FALSE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
iovecs.clear();
|
||||
iov.iov_base = "a";
|
||||
iov.iov_len = 1;
|
||||
iovecs.push_back(iov);
|
||||
iov.iov_len = std::numeric_limits<ssize_t>::max() - 1;
|
||||
iovecs.push_back(iov);
|
||||
EXPECT_FALSE(writer.WriteIoVec(&iovecs));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, Seek) {
|
||||
StringFileWriter writer;
|
||||
|
||||
EXPECT_TRUE(writer.Write("abcd", 4));
|
||||
EXPECT_EQ(4u, writer.string().size());
|
||||
EXPECT_EQ("abcd", writer.string());
|
||||
EXPECT_EQ(4, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("efgh", 4));
|
||||
EXPECT_EQ(4u, writer.string().size());
|
||||
EXPECT_EQ("efgh", writer.string());
|
||||
EXPECT_EQ(4, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("ijk", 3));
|
||||
EXPECT_EQ(4u, writer.string().size());
|
||||
EXPECT_EQ("ijkh", writer.string());
|
||||
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("lmnop", 5));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("lmnop", writer.string());
|
||||
EXPECT_EQ(5, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
|
||||
EXPECT_TRUE(writer.Write("q", 1));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("lqnop", writer.string());
|
||||
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(1, writer.Seek(-1, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("r", 1));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("lrnop", writer.string());
|
||||
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("s", 1));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("lrsop", writer.string());
|
||||
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(2, writer.Seek(-1, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("t", 1));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("lrtop", writer.string());
|
||||
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(4, writer.Seek(-1, SEEK_END));
|
||||
EXPECT_TRUE(writer.Write("u", 1));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("lrtou", writer.string());
|
||||
EXPECT_EQ(5, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(-5, SEEK_END));
|
||||
EXPECT_TRUE(writer.Write("v", 1));
|
||||
EXPECT_EQ(5u, writer.string().size());
|
||||
EXPECT_EQ("vrtou", writer.string());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(5, writer.Seek(0, SEEK_END));
|
||||
EXPECT_TRUE(writer.Write("w", 1));
|
||||
EXPECT_EQ(6u, writer.string().size());
|
||||
EXPECT_EQ("vrtouw", writer.string());
|
||||
EXPECT_EQ(6, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(8, writer.Seek(2, SEEK_END));
|
||||
EXPECT_EQ(6u, writer.string().size());
|
||||
EXPECT_EQ("vrtouw", writer.string());
|
||||
|
||||
EXPECT_EQ(6, writer.Seek(0, SEEK_END));
|
||||
EXPECT_TRUE(writer.Write("x", 1));
|
||||
EXPECT_EQ(7u, writer.string().size());
|
||||
EXPECT_EQ("vrtouwx", writer.string());
|
||||
EXPECT_EQ(7, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, SeekSparse) {
|
||||
StringFileWriter writer;
|
||||
|
||||
EXPECT_EQ(3, writer.Seek(3, SEEK_SET));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("abc", 3));
|
||||
EXPECT_EQ(6u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0\0\0abc", 6), writer.string());
|
||||
EXPECT_EQ(6, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(9, writer.Seek(3, SEEK_END));
|
||||
EXPECT_EQ(6u, writer.string().size());
|
||||
EXPECT_EQ(9, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("def", 3));
|
||||
EXPECT_EQ(12u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0\0\0abc\0\0\0def", 12), writer.string());
|
||||
EXPECT_EQ(12, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(7, writer.Seek(-5, SEEK_END));
|
||||
EXPECT_EQ(12u, writer.string().size());
|
||||
EXPECT_EQ(7, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("g", 1));
|
||||
EXPECT_EQ(12u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0\0\0abc\0g\0def", 12), writer.string());
|
||||
EXPECT_EQ(8, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(15, writer.Seek(7, SEEK_CUR));
|
||||
EXPECT_EQ(12u, writer.string().size());
|
||||
EXPECT_EQ(15, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("hij", 3));
|
||||
EXPECT_EQ(18u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0\0\0abc\0g\0def\0\0\0hij", 18), writer.string());
|
||||
EXPECT_EQ(18, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_EQ(1, writer.Seek(-17, SEEK_CUR));
|
||||
EXPECT_EQ(18u, writer.string().size());
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.Write("k", 1));
|
||||
EXPECT_EQ(18u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0k\0abc\0g\0def\0\0\0hij", 18), writer.string());
|
||||
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
|
||||
|
||||
EXPECT_TRUE(writer.Write("l", 1));
|
||||
EXPECT_TRUE(writer.Write("mnop", 4));
|
||||
EXPECT_EQ(18u, writer.string().size());
|
||||
EXPECT_EQ(std::string("\0klmnopg\0def\0\0\0hij", 18), writer.string());
|
||||
EXPECT_EQ(7, writer.Seek(0, SEEK_CUR));
|
||||
}
|
||||
|
||||
TEST(StringFileWriter, SeekInvalid) {
|
||||
StringFileWriter writer;
|
||||
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_LT(writer.Seek(-1, SEEK_SET), 0);
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_LT(writer.Seek(std::numeric_limits<ssize_t>::min(), SEEK_SET), 0);
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_LT(writer.Seek(std::numeric_limits<off_t>::min(), SEEK_SET), 0);
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
|
||||
COMPILE_ASSERT(SEEK_SET != 3 && SEEK_CUR != 3 && SEEK_END != 3,
|
||||
three_must_be_invalid_for_whence);
|
||||
EXPECT_LT(writer.Seek(0, 3), 0);
|
||||
|
||||
writer.Reset();
|
||||
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_TRUE(writer.string().empty());
|
||||
|
||||
const off_t kMaxOffset =
|
||||
std::min(static_cast<uint64_t>(std::numeric_limits<off_t>::max()),
|
||||
static_cast<uint64_t>(std::numeric_limits<size_t>::max()));
|
||||
|
||||
EXPECT_EQ(kMaxOffset, writer.Seek(kMaxOffset, SEEK_SET));
|
||||
EXPECT_EQ(kMaxOffset, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_LT(writer.Seek(1, SEEK_CUR), 0);
|
||||
|
||||
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
|
||||
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
|
||||
EXPECT_LT(writer.Seek(kMaxOffset, SEEK_CUR), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
44
util/numeric/safe_assignment.h
Normal file
44
util/numeric/safe_assignment.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_
|
||||
#define CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_
|
||||
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Performs an assignment if it can be done safely, and signals if it
|
||||
//! cannot be done safely.
|
||||
//!
|
||||
//! \param[out] destination A pointer to the variable to be assigned to.
|
||||
//! \param[in] source The value to assign.
|
||||
//!
|
||||
//! \return `true` if \a source is in the range supported by the type of \a
|
||||
//! *destination, with the assignment to \a *destination having been
|
||||
//! performed. `false` if the assignment cannot be completed safely because
|
||||
//! \a source is outside of this range.
|
||||
template <typename Destination, typename Source>
|
||||
bool AssignIfInRange(Destination* destination, Source source) {
|
||||
if (!base::IsValueInRangeForNumericType<Destination>(source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*destination = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_
|
55
util/util.gyp
Normal file
55
util/util.gyp
Normal file
@ -0,0 +1,55 @@
|
||||
# Copyright 2014 The Crashpad Authors. 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.
|
||||
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'util',
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'../compat/compat.gyp:compat',
|
||||
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
'<(INTERMEDIATE_DIR)',
|
||||
],
|
||||
'sources': [
|
||||
'file/fd_io.cc',
|
||||
'file/fd_io.h',
|
||||
'file/file_writer.cc',
|
||||
'file/file_writer.h',
|
||||
'file/string_file_writer.cc',
|
||||
'file/string_file_writer.h',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'util_test',
|
||||
'type': 'executable',
|
||||
'dependencies': [
|
||||
'util',
|
||||
'../compat/compat.gyp:compat',
|
||||
'../third_party/gtest/gtest.gyp:gtest',
|
||||
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
'sources': [
|
||||
'../third_party/gtest/gtest/src/gtest_main.cc',
|
||||
'file/string_file_writer_test.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user