Add MachOImageReader and its test, which also tests

MachOImageSegmentReader.

TEST=util_test MachOImageReader.*
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/535343004
This commit is contained in:
Mark Mentovai 2014-09-04 11:45:40 -04:00
parent 52064fdd1b
commit 4f74716f6d
11 changed files with 1408 additions and 5 deletions

View File

@ -0,0 +1,580 @@
// 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/mac/mach_o_image_reader.h"
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <string.h>
#include <limits>
#include <vector>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "util/mac/checked_mach_address_range.h"
#include "util/mac/mach_o_image_segment_reader.h"
#include "util/mac/process_reader.h"
namespace {
const uint32_t kInvalidSegmentIndex = std::numeric_limits<uint32_t>::max();
} // namespace
namespace crashpad {
MachOImageReader::MachOImageReader()
: segments_(),
segment_map_(),
module_info_(),
dylinker_name_(),
uuid_(),
address_(0),
size_(0),
slide_(0),
source_version_(0),
symtab_command_(),
dysymtab_command_(),
id_dylib_command_(),
process_reader_(NULL),
file_type_(0),
initialized_() {
}
MachOImageReader::~MachOImageReader() {
}
bool MachOImageReader::Initialize(ProcessReader* process_reader,
mach_vm_address_t address,
const std::string& name) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
process_reader_ = process_reader;
address_ = address;
module_info_ =
base::StringPrintf(", module %s, address 0x%llx", name.c_str(), address);
process_types::mach_header mach_header;
if (!mach_header.Read(process_reader, address)) {
LOG(WARNING) << "could not read mach_header" << module_info_;
return false;
}
const bool is_64_bit = process_reader->Is64Bit();
const uint32_t kExpectedMagic = is_64_bit ? MH_MAGIC_64 : MH_MAGIC;
if (mach_header.magic != kExpectedMagic) {
LOG(WARNING) << base::StringPrintf("unexpected mach_header::magic 0x%08x",
mach_header.magic) << module_info_;
return false;
}
file_type_ = mach_header.filetype;
const uint32_t kExpectedSegmentCommand =
is_64_bit ? LC_SEGMENT_64 : LC_SEGMENT;
const uint32_t kUnexpectedSegmentCommand =
is_64_bit ? LC_SEGMENT : LC_SEGMENT_64;
const struct {
// Which method to call when encountering a load command matching |command|.
bool (MachOImageReader::*function)(mach_vm_address_t, const std::string&);
// The minimum size that may be allotted to store the load command.
size_t size;
// The load command to match.
uint32_t command;
// True if the load command must not appear more than one time.
bool singleton;
} kLoadCommandReaders[] = {
{
&MachOImageReader::ReadSegmentCommand,
process_types::segment_command::ExpectedSize(process_reader),
kExpectedSegmentCommand,
false,
},
{
&MachOImageReader::ReadSymTabCommand,
process_types::symtab_command::ExpectedSize(process_reader),
LC_SYMTAB,
true,
},
{
&MachOImageReader::ReadDySymTabCommand,
process_types::symtab_command::ExpectedSize(process_reader),
LC_DYSYMTAB,
true,
},
{
&MachOImageReader::ReadIdDylibCommand,
process_types::dylib_command::ExpectedSize(process_reader),
LC_ID_DYLIB,
true,
},
{
&MachOImageReader::ReadDylinkerCommand,
process_types::dylinker_command::ExpectedSize(process_reader),
LC_LOAD_DYLINKER,
true,
},
{
&MachOImageReader::ReadDylinkerCommand,
process_types::dylinker_command::ExpectedSize(process_reader),
LC_ID_DYLINKER,
true,
},
{
&MachOImageReader::ReadUUIDCommand,
process_types::uuid_command::ExpectedSize(process_reader),
LC_UUID,
true,
},
{
&MachOImageReader::ReadSourceVersionCommand,
process_types::source_version_command::ExpectedSize(process_reader),
LC_SOURCE_VERSION,
true,
},
// When reading a 64-bit process, no 32-bit segment commands should be
// present, and vice-versa.
{
&MachOImageReader::ReadUnexpectedCommand,
process_types::load_command::ExpectedSize(process_reader),
kUnexpectedSegmentCommand,
false,
},
};
// This vector is parallel to the kLoadCommandReaders array, and tracks
// whether a singleton load command matching the |command| field has been
// found yet.
std::vector<uint32_t> singleton_indices(arraysize(kLoadCommandReaders),
kInvalidSegmentIndex);
size_t offset = mach_header.Size();
const mach_vm_address_t kLoadCommandAddressLimit =
address + offset + mach_header.sizeofcmds;
for (uint32_t load_command_index = 0;
load_command_index < mach_header.ncmds;
++load_command_index) {
mach_vm_address_t load_command_address = address + offset;
std::string load_command_info = base::StringPrintf(", load command %u/%u%s",
load_command_index,
mach_header.ncmds,
module_info_.c_str());
process_types::load_command load_command;
// Make sure that the basic load command structure doesnt overflow the
// space allotted for load commands.
if (load_command_address + load_command.ExpectedSize(process_reader) >
kLoadCommandAddressLimit) {
LOG(WARNING) << base::StringPrintf(
"load_command at 0x%llx exceeds sizeofcmds 0x%x",
load_command_address,
mach_header.sizeofcmds) << load_command_info;
return false;
}
if (!load_command.Read(process_reader, load_command_address)) {
LOG(WARNING) << "could not read load_command" << load_command_info;
return false;
}
load_command_info = base::StringPrintf(", load command 0x%x %u/%u%s",
load_command.cmd,
load_command_index,
mach_header.ncmds,
module_info_.c_str());
// Now that the load commands stated size is known, make sure that it
// doesnt overflow the space allotted for load commands.
if (load_command_address + load_command.cmdsize >
kLoadCommandAddressLimit) {
LOG(WARNING)
<< base::StringPrintf(
"load_command at 0x%llx cmdsize 0x%x exceeds sizeofcmds 0x%x",
load_command_address,
load_command.cmdsize,
mach_header.sizeofcmds) << load_command_info;
return false;
}
for (size_t reader_index = 0;
reader_index < arraysize(kLoadCommandReaders);
++reader_index) {
if (load_command.cmd != kLoadCommandReaders[reader_index].command) {
continue;
}
if (load_command.cmdsize < kLoadCommandReaders[reader_index].size) {
LOG(WARNING)
<< base::StringPrintf(
"load command cmdsize 0x%x insufficient for 0x%zx",
load_command.cmdsize,
kLoadCommandReaders[reader_index].size)
<< load_command_info;
return false;
}
if (kLoadCommandReaders[reader_index].singleton) {
if (singleton_indices[reader_index] != kInvalidSegmentIndex) {
LOG(WARNING) << "duplicate load command at "
<< singleton_indices[reader_index]
<< load_command_info;
return false;
}
singleton_indices[reader_index] = load_command_index;
}
if (!((this)->*(kLoadCommandReaders[reader_index].function))(
load_command_address, load_command_info)) {
return false;
}
break;
}
offset += load_command.cmdsize;
}
// This was already checked for the unslid values while the segments were
// read, but now that the slide is known, check the slid values too. The
// individual sections dont need to be checked because they were verified to
// be contained within their respective segments when the segments were read.
for (const MachOImageSegmentReader* segment : segments_) {
mach_vm_address_t slid_segment_address = segment->vmaddr();
mach_vm_size_t slid_segment_size = segment->vmsize();
if (segment->SegmentSlides()) {
slid_segment_address += slide_;
} else {
// The non-sliding __PAGEZERO segment extends instead of slides. See
// MachOImageSegmentReader::SegmentSlides().
slid_segment_size += slide_;
}
CheckedMachAddressRange slid_segment_range(
process_reader_, slid_segment_address, slid_segment_size);
if (!slid_segment_range.IsValid()) {
LOG(WARNING) << base::StringPrintf(
"invalid slid segment range 0x%llx + 0x%llx, "
"segment ",
slid_segment_address,
slid_segment_size) << segment->Name() << module_info_;
return false;
}
}
if (!segment_map_.count(SEG_TEXT)) {
// The __TEXT segment is required. Even a module with no executable code
// will have a __TEXT segment encompassing the Mach-O header and load
// commands. Without a __TEXT segment, |size_| will not have been computed.
LOG(WARNING) << "no " SEG_TEXT " segment" << module_info_;
return false;
}
if (mach_header.filetype == MH_DYLIB && !id_dylib_command_) {
// This doesnt render a module unusable, its just weird and worth noting.
LOG(INFO) << "no LC_ID_DYLIB" << module_info_;
}
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
const MachOImageSegmentReader* MachOImageReader::GetSegmentByName(
const std::string& segment_name,
mach_vm_address_t* address,
mach_vm_size_t* size) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
const auto& iterator = segment_map_.find(segment_name);
if (iterator == segment_map_.end()) {
return NULL;
}
const MachOImageSegmentReader* segment = segments_[iterator->second];
if (address) {
*address = segment->vmaddr() + (segment->SegmentSlides() ? slide_ : 0);
}
if (size) {
// The non-sliding __PAGEZERO segment extends instead of slides. See
// MachOImageSegmentReader::SegmentSlides().
*size = segment->vmsize() + (segment->SegmentSlides() ? 0 : slide_);
}
return segment;
}
const process_types::section* MachOImageReader::GetSectionByName(
const std::string& segment_name,
const std::string& section_name,
mach_vm_address_t* address) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
const MachOImageSegmentReader* segment =
GetSegmentByName(segment_name, NULL, NULL);
if (!segment) {
return NULL;
}
const process_types::section* section =
segment->GetSectionByName(section_name);
if (!section) {
return NULL;
}
if (address) {
*address = section->addr + (segment->SegmentSlides() ? slide_ : 0);
}
return section;
}
const process_types::section* MachOImageReader::GetSectionAtIndex(
size_t index,
mach_vm_address_t* address) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
COMPILE_ASSERT(NO_SECT == 0, no_sect_must_be_zero);
if (index == NO_SECT) {
LOG(WARNING) << "section index " << index << " out of range";
return NULL;
}
// Switch to a more comfortable 0-based index.
size_t local_index = index - 1;
for (const MachOImageSegmentReader* segment : segments_) {
size_t nsects = segment->nsects();
if (local_index < nsects) {
const process_types::section* section =
segment->GetSectionAtIndex(local_index);
if (address) {
*address = section->addr + (segment->SegmentSlides() ? slide_ : 0);
}
return section;
}
local_index -= nsects;
}
LOG(WARNING) << "section index " << index << " out of range";
return NULL;
}
uint32_t MachOImageReader::DylibVersion() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
DCHECK_EQ(FileType(), static_cast<uint32_t>(MH_DYLIB));
if (id_dylib_command_) {
return id_dylib_command_->dylib_current_version;
}
// In case this was a weird dylib without an LC_ID_DYLIB command.
return 0;
}
void MachOImageReader::UUID(crashpad::UUID* uuid) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
memcpy(uuid, &uuid_, sizeof(uuid_));
}
template <typename T>
bool MachOImageReader::ReadLoadCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info,
uint32_t expected_load_command_id,
T* load_command) {
if (!load_command->Read(process_reader_, load_command_address)) {
LOG(WARNING) << "could not read load command" << load_command_info;
return false;
}
DCHECK_GE(load_command->cmdsize, load_command->Size());
DCHECK_EQ(load_command->cmd, expected_load_command_id);
return true;
}
bool MachOImageReader::ReadSegmentCommand(
mach_vm_address_t load_command_address,
const std::string& load_command_info) {
MachOImageSegmentReader* segment = new MachOImageSegmentReader();
size_t segment_index = segments_.size();
segments_.push_back(segment); // Takes ownership.
if (!segment->Initialize(
process_reader_, load_command_address, load_command_info)) {
segments_.pop_back();
return false;
}
// At this point, the segment itself is considered valid, but if one of the
// next checks fails, it will render the module invalid. If any of the next
// checks fail, this method should return false, but it doesnt need to bother
// removing the segment from segments_. The segment will be properly released
// when the image is destroyed, and the image wont be usable because
// initialization wont have completed. Most importantly, leaving the segment
// in segments_ means that no other structures (such as perhaps segment_map_)
// become inconsistent or require cleanup.
const std::string segment_name = segment->Name();
const auto& iterator = segment_map_.find(segment_name);
if (iterator != segment_map_.end()) {
LOG(WARNING) << base::StringPrintf("duplicate %s segment at %zu and %zu",
segment_name.c_str(),
iterator->second,
segment_index) << load_command_info;
return false;
}
segment_map_[segment_name] = segment_index;
mach_vm_size_t vmsize = segment->vmsize();
if (segment_name == SEG_TEXT) {
if (vmsize == 0) {
LOG(WARNING) << "zero-sized " SEG_TEXT " segment" << load_command_info;
return false;
}
mach_vm_size_t fileoff = segment->fileoff();
if (fileoff != 0) {
LOG(WARNING) << base::StringPrintf(
SEG_TEXT " segment has unexpected fileoff 0x%llx",
fileoff) << load_command_info;
return false;
}
size_ = vmsize;
// The slide is computed as the difference between the __TEXT segments
// preferred and actual load addresses. This is the same way that dyld
// computes slide. See 10.9.2 dyld-239.4/src/dyldInitialization.cpp
// slideOfMainExecutable().
slide_ = address_ - segment->vmaddr();
}
return true;
}
bool MachOImageReader::ReadSymTabCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info) {
symtab_command_.reset(new process_types::symtab_command());
return ReadLoadCommand(load_command_address,
load_command_info,
LC_SYMTAB,
symtab_command_.get());
}
bool MachOImageReader::ReadDySymTabCommand(
mach_vm_address_t load_command_address,
const std::string& load_command_info) {
dysymtab_command_.reset(new process_types::dysymtab_command());
return ReadLoadCommand(load_command_address,
load_command_info,
LC_DYSYMTAB,
dysymtab_command_.get());
}
bool MachOImageReader::ReadIdDylibCommand(
mach_vm_address_t load_command_address,
const std::string& load_command_info) {
if (file_type_ != MH_DYLIB) {
LOG(WARNING) << base::StringPrintf(
"LC_ID_DYLIB inappropriate in non-dylib file type 0x%x",
file_type_) << load_command_info;
return false;
}
DCHECK(!id_dylib_command_);
id_dylib_command_.reset(new process_types::dylib_command());
return ReadLoadCommand(load_command_address,
load_command_info,
LC_ID_DYLIB,
id_dylib_command_.get());
}
bool MachOImageReader::ReadDylinkerCommand(
mach_vm_address_t load_command_address,
const std::string& load_command_info) {
if (file_type_ != MH_EXECUTE && file_type_ != MH_DYLINKER) {
LOG(WARNING) << base::StringPrintf(
"LC_LOAD_DYLINKER/LC_ID_DYLINKER inappropriate in file "
"type 0x%x",
file_type_) << load_command_info;
return false;
}
const uint32_t kExpectedCommand =
file_type_ == MH_DYLINKER ? LC_ID_DYLINKER : LC_LOAD_DYLINKER;
process_types::dylinker_command dylinker_command;
if (!ReadLoadCommand(load_command_address,
load_command_info,
kExpectedCommand,
&dylinker_command)) {
return false;
}
if (!process_reader_->Memory()->ReadCStringSizeLimited(
load_command_address + dylinker_command.name,
dylinker_command.cmdsize - dylinker_command.name,
&dylinker_name_)) {
LOG(WARNING) << "could not read dylinker_command name" << load_command_info;
return false;
}
return true;
}
bool MachOImageReader::ReadUUIDCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info) {
process_types::uuid_command uuid_command;
if (!ReadLoadCommand(
load_command_address, load_command_info, LC_UUID, &uuid_command)) {
return false;
}
uuid_.InitializeFromBytes(uuid_command.uuid);
return true;
}
bool MachOImageReader::ReadSourceVersionCommand(
mach_vm_address_t load_command_address,
const std::string& load_command_info) {
process_types::source_version_command source_version_command;
if (!ReadLoadCommand(load_command_address,
load_command_info,
LC_SOURCE_VERSION,
&source_version_command)) {
return false;
}
source_version_ = source_version_command.version;
return true;
}
bool MachOImageReader::ReadUnexpectedCommand(
mach_vm_address_t load_command_address,
const std::string& load_command_info) {
LOG(WARNING) << "unexpected load command" << load_command_info;
return false;
}
} // namespace crashpad

View File

@ -0,0 +1,279 @@
// 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_MAC_MACH_O_IMAGE_READER_H_
#define CRASHPAD_UTIL_MAC_MACH_O_IMAGE_READER_H_
#include <mach/mach.h>
#include <stdint.h>
#include <map>
#include <string>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/misc/uuid.h"
#include "util/stdlib/pointer_container.h"
#include "util/mac/process_types.h"
namespace crashpad {
class MachOImageSegmentReader;
class ProcessReader;
//! \brief A reader for Mach-O images mapped into another process.
//!
//! This class is capable of reading both 32-bit (`mach_header`/`MH_MAGIC`) and
//! 64-bit (`mach_header_64`/`MH_MAGIC_64`) images based on the bitness of the
//! remote process.
class MachOImageReader {
public:
MachOImageReader();
~MachOImageReader();
//! \brief Reads the Mach-O image files load commands from another process.
//!
//! This method must only be called once on an object. This method must be
//! called successfully before any other method in this class may be called.
//!
//! \param[in] process_reader The reader for the remote process.
//! \param[in] address The address, in the remote process address space,
//! where the `mach_header` or `mach_header_64` at the beginning of the
//! image to be read is located. This address can be determined by reading
//! the remote process dyld information (see
//! util/mac/process_types/dyld_images.proctype).
//! \param[in] name The modules name, a string to be used in logged messages.
//! This string is for diagnostic purposes only, and may be empty.
//!
//! \return `true` if the image was read successfully, including all load
//! commands. `false` otherwise, with an appropriate message logged.
bool Initialize(ProcessReader* process_reader,
mach_vm_address_t address,
const std::string& name);
//! \brief Returns the Mach-O file type.
//!
//! This value comes from the `filetype` field of the `mach_header` or
//! `mach_header_64`. Common values include `MH_EXECUTE`, `MH_DYLIB`,
//! `MH_DYLINKER`, and `MH_BUNDLE`.
uint32_t FileType() const { return file_type_; }
//! \brief Returns the Mach-O images load address.
//!
//! This is the value passed as \a address to Initialize().
mach_vm_address_t Address() const { return address_; }
//! \brief Returns the mapped size of the Mach-O images __TEXT segment.
//!
//! Note that this is returns only the size of the __TEXT segment, not of any
//! other segment. This is because the interface only allows one load address
//! and size to be reported, but Mach-O image files may consist of multiple
//! discontiguous segments. By convention, the __TEXT segment is always mapped
//! at the beginning of a Mach-O image file, and it is the most useful for the
//! expected intended purpose of collecting data to obtain stack backtraces.
//! The implementation insists during initialization that the __TEXT segment
//! be mapped at the beginning of the file.
//!
//! In practice, discontiguous segments are only found for images that have
//! loaded out of the dyld shared cache, but the __TEXT segments size is
//! returned for modules that loaded with contiguous segments as well for
//! consistency.
mach_vm_size_t Size() const { return size_; }
//! \brief Returns the Mach-O images “slide,” the difference between its
//! actual load address and its preferred load address.
//!
//! “Slide” is computed by subtracting the __TEXT segments preferred load
//! address from its actual load address. It will be reported as a positive
//! offset when the actual load address is greater than the preferred load
//! address. The preferred load address is taken to be the segments reported
//! `vmaddr` value.
mach_vm_size_t Slide() const { return slide_; }
//! \brief Obtain segment information by segment name.
//!
//! \param[in] segment_name The name of the segment to search for, for
//! example, `"__TEXT"`.
//! \param[out] address The actual address that the segment was loaded at in
//! memory, taking any “slide” into account if the segment did not load at
//! its preferred address as stored in the Mach-O image file. This
//! parameter can be `NULL`.
//! \param[out] size The actual size of the segment as loaded at in memory.
//! This value takes any expansion of the segment into account, which
//! occurs when a nonsliding segment in a sliding image loads at its
//! preferred address but grows by the value of the slide. This parameter
//! can be `NULL`.
//!
//! \return A pointer to the segment information if it was found, or `NULL` if
//! it was not found.
//!
//! \note The \a address parameter takes “slide” into account, and the \a size
//! parameter takes growth into account for non-sliding segments, so that
//! these parameters reflect the actual address and size of the segment as
//! loaded into a process address space. This is distinct from the
//! segments preferred load address and size, which may be obtained by
//! calling MachOImageSegmentReader::vmaddr() and
//! MachOImageSegmentReader::vmsize(), respectively.
const MachOImageSegmentReader* GetSegmentByName(
const std::string& segment_name,
mach_vm_address_t* address,
mach_vm_size_t* size) const;
//! \brief Obtain section information by segment and section name.
//!
//! \param[in] segment_name The name of the segment to search for, for
//! example, `"__TEXT"`.
//! \param[in] section_name The name of the section within the segment to
//! search for, for example, `"__text"`.
//! \param[out] address The actual address that the section was loaded at in
//! memory, taking any “slide” into account if the section did not load at
//! its preferred address as stored in the Mach-O image file. This
//! parameter can be `NULL`.
//!
//! \return A pointer to the section information if it was found, or `NULL` if
//! it was not found.
//!
//! No parameter is provided for the sections size, because it can be
//! obtained from the returned process_types::section::size field.
//!
//! \note The process_types::section::addr field gives the sections preferred
//! load address as stored in the Mach-O image file, and is not adjusted
//! for any “slide” that may have occurred when the image was loaded. Use
//! \a address to obtain the sections actual load address.
const process_types::section* GetSectionByName(
const std::string& segment_name,
const std::string& section_name,
mach_vm_address_t* address) const;
//! \brief Obtain section information by section index.
//!
//! \param[in] index The index of the section to return, in the order that it
//! appears in the segment load commands. This is a 1-based index,
//! matching the section number values used for `nlist::n_sect`.
//! \param[out] address The actual address that the section was loaded at in
//! memory, taking any “slide” into account if the section did not load at
//! its preferred address as stored in the Mach-O image file. This
//! parameter can be `NULL`.
//!
//! \return A pointer to the section information. If \a index is out of range,
//! logs a warning and returns `NULL`.
//!
//! No parameter is provided for the sections size, because it can be
//! obtained from the returned process_types::section::size field.
//!
//! \note The process_types::section::addr field gives the sections preferred
//! load address as stored in the Mach-O image file, and is not adjusted
//! for any “slide” that may have occurred when the image was loaded. Use
//! \a address to obtain the sections actual load address.
//! \note Unlike MachOImageSegmentReader::GetSectionAtIndex(), this method
//! accepts out-of-range values for \a index, and returns `NULL` instead
//! of aborting execution upon encountering an out-of-range value. This is
//! because a Mach-O image files symbol table refers to this per-module
//! section index, and an out-of-range index in that case should be
//! treated as a data error (where the data is beyond this codes control)
//! and handled non-fatally by reporting the error to the caller.
const process_types::section* GetSectionAtIndex(
size_t index,
mach_vm_address_t* address) const;
//! \brief Returns a Mach-O dylib images current version.
//!
//! This information comes from the `dylib_current_version` field of a dylibs
//! `LC_ID_DYLIB` load command. For dylibs without this load command, `0` will
//! be returned.
//!
//! This method may only be called on Mach-O images for which FileType()
//! returns `MH_DYLIB`.
uint32_t DylibVersion() const;
//! \brief Returns a Mach-O images source version.
//!
//! This information comes from a Mach-O images `LC_SOURCE_VERSION` load
//! command. For Mach-O images without this load command, `0` will be
//! returned.
uint64_t SourceVersion() const { return source_version_; }
//! \brief Returns a Mach-O images UUID.
//!
//! This information comes from a Mach-O images `LC_UUID` load command. For
//! Mach-O images without this load command, a zeroed-out UUID value will be
//! returned.
//
// UUID is a name in this scope (referring to this method), so the parameters
// type needs to be qualified with |crashpad::|.
void UUID(crashpad::UUID* uuid) const;
//! \brief Returns the dynamic linkers pathname.
//!
//! The dynamic linker is normally /usr/lib/dyld.
//!
//! For executable images (those with file type `MH_EXECUTE`), this is the
//! name provided in the `LC_LOAD_DYLINKER` load command, if any. For dynamic
//! linker images (those with file type `MH_DYLINKER`), this is the name
//! provided in the `LC_ID_DYLINKER` load command. In other cases, this will
//! be empty.
std::string DylinkerName() const { return dylinker_name_; }
private:
// A generic helper routine for the other Read*Command() methods.
template <typename T>
bool ReadLoadCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info,
uint32_t expected_load_command_id,
T* load_command);
// The Read*Command() methods are subroutines called by Initialize(). They are
// responsible for reading a single load command. They may update the member
// fields of their MachOImageReader object. If they cant make sense of a load
// command, they return false.
bool ReadSegmentCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadSymTabCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadDySymTabCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadIdDylibCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadDylinkerCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadUUIDCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadSourceVersionCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
bool ReadUnexpectedCommand(mach_vm_address_t load_command_address,
const std::string& load_command_info);
PointerVector<MachOImageSegmentReader> segments_;
std::map<std::string, size_t> segment_map_;
std::string module_info_;
std::string dylinker_name_;
crashpad::UUID uuid_;
mach_vm_address_t address_;
mach_vm_size_t size_;
mach_vm_size_t slide_;
uint64_t source_version_;
scoped_ptr<process_types::symtab_command> symtab_command_;
scoped_ptr<process_types::dysymtab_command> dysymtab_command_;
scoped_ptr<process_types::dylib_command> id_dylib_command_;
ProcessReader* process_reader_; // weak
uint32_t file_type_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(MachOImageReader);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_MAC_MACH_O_IMAGE_READER_H_

View File

@ -0,0 +1,448 @@
// 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/mac/mach_o_image_reader.h"
#include <dlfcn.h>
#include <mach-o/dyld_images.h>
#include <mach-o/getsect.h>
#include <mach-o/loader.h>
#include <stdint.h>
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/mac/mach_o_image_segment_reader.h"
#include "util/mac/process_reader.h"
#include "util/mac/process_types.h"
#include "util/misc/uuid.h"
#include "util/test/mac/dyld.h"
namespace {
using namespace crashpad;
// Native types and constants, in cases where the 32-bit and 64-bit versions
// are different.
#if defined(ARCH_CPU_64_BITS)
typedef mach_header_64 MachHeader;
const uint32_t kMachMagic = MH_MAGIC_64;
typedef segment_command_64 SegmentCommand;
const uint32_t kSegmentCommand = LC_SEGMENT_64;
typedef section_64 Section;
#else
typedef mach_header MachHeader;
const uint32_t kMachMagic = MH_MAGIC;
typedef segment_command SegmentCommand;
const uint32_t kSegmentCommand = LC_SEGMENT;
typedef section Section;
#endif
#if defined(ARCH_CPU_X86_64)
const int kCPUType = CPU_TYPE_X86_64;
#elif defined(ARCH_CPU_X86)
const int kCPUType = CPU_TYPE_X86;
#endif
// Verifies that |expect_section| and |actual_section| agree.
void ExpectSection(const Section* expect_section,
const process_types::section* actual_section) {
ASSERT_TRUE(expect_section);
ASSERT_TRUE(actual_section);
EXPECT_EQ(
MachOImageSegmentReader::SectionNameString(expect_section->sectname),
MachOImageSegmentReader::SectionNameString(actual_section->sectname));
EXPECT_EQ(
MachOImageSegmentReader::SegmentNameString(expect_section->segname),
MachOImageSegmentReader::SegmentNameString(actual_section->segname));
EXPECT_EQ(expect_section->addr, actual_section->addr);
EXPECT_EQ(expect_section->size, actual_section->size);
EXPECT_EQ(expect_section->offset, actual_section->offset);
EXPECT_EQ(expect_section->align, actual_section->align);
EXPECT_EQ(expect_section->reloff, actual_section->reloff);
EXPECT_EQ(expect_section->nreloc, actual_section->nreloc);
EXPECT_EQ(expect_section->flags, actual_section->flags);
EXPECT_EQ(expect_section->reserved1, actual_section->reserved1);
EXPECT_EQ(expect_section->reserved2, actual_section->reserved2);
}
// Verifies that |expect_segment| is a valid Mach-O segment load command for the
// current system by checking its |cmd| field. Then, verifies that the
// information in |actual_segment| matches that in |expect_segment|. The
// |segname|, |vmaddr|, |vmsize|, and |fileoff| fields are examined. Each
// section within the segment is also examined by calling ExpectSection().
// Access to each section via both MachOImageSegmentReader::GetSectionByName()
// and MachOImageReader::GetSectionByName() is verified, expecting that each
// call produces the same section. Segment and section data addresses are
// verified against data obtained by calling getsegmentdata() and
// getsectiondata(). The segment is checked to make sure that it behaves
// correctly when attempting to look up a nonexistent section by name.
// |section_index| is used to track the last-used section index in an image on
// entry, and is reset to the last-used section index on return after the
// sections are processed. This is used to test that
// MachOImageReader::GetSectionAtIndex() returns the correct result.
void ExpectSegmentCommand(const SegmentCommand* expect_segment,
const MachHeader* expect_image,
const MachOImageSegmentReader* actual_segment,
mach_vm_address_t actual_segment_address,
mach_vm_size_t actual_segment_size,
const MachOImageReader* actual_image,
size_t* section_index) {
ASSERT_TRUE(expect_segment);
ASSERT_TRUE(actual_segment);
EXPECT_EQ(kSegmentCommand, expect_segment->cmd);
std::string segment_name = actual_segment->Name();
EXPECT_EQ(MachOImageSegmentReader::SegmentNameString(expect_segment->segname),
segment_name);
EXPECT_EQ(expect_segment->vmaddr, actual_segment->vmaddr());
EXPECT_EQ(expect_segment->vmsize, actual_segment->vmsize());
EXPECT_EQ(expect_segment->fileoff, actual_segment->fileoff());
if (actual_segment->SegmentSlides()) {
EXPECT_EQ(actual_segment_address,
actual_segment->vmaddr() + actual_image->Slide());
unsigned long expect_segment_size;
const uint8_t* expect_segment_data = getsegmentdata(
expect_image, segment_name.c_str(), &expect_segment_size);
mach_vm_address_t expect_segment_address =
reinterpret_cast<mach_vm_address_t>(expect_segment_data);
EXPECT_EQ(expect_segment_address, actual_segment_address);
EXPECT_EQ(expect_segment_size, actual_segment->vmsize());
EXPECT_EQ(actual_segment->vmsize(), actual_segment_size);
} else {
// getsegmentdata() doesnt return appropriate data for the __PAGEZERO
// segment because getsegmentdata() always adjusts for slide, but the
// __PAGEZERO segment never slides, it just grows. Skip the getsegmentdata()
// check for that segment according to the same rules that the kernel uses
// to identify __PAGEZERO. See 10.9.4 xnu-2422.110.17/bsd/kern/mach_loader.c
// load_segment().
EXPECT_EQ(actual_segment_address, actual_segment->vmaddr());
EXPECT_EQ(actual_segment->vmsize() + actual_image->Slide(),
actual_segment_size);
}
ASSERT_EQ(expect_segment->nsects, actual_segment->nsects());
// Make sure that the expected load command is big enough for the number of
// sections that it claims to have, and set up a pointer to its first section
// structure.
ASSERT_EQ(sizeof(*expect_segment) + expect_segment->nsects * sizeof(Section),
expect_segment->cmdsize);
const Section* expect_sections =
reinterpret_cast<const Section*>(&expect_segment[1]);
for (size_t index = 0; index < actual_segment->nsects(); ++index) {
const Section* expect_section = &expect_sections[index];
const process_types::section* actual_section =
actual_segment->GetSectionAtIndex(index);
ExpectSection(&expect_sections[index], actual_section);
if (testing::Test::HasFatalFailure()) {
return;
}
// Make sure that the section is accessible by GetSectionByName as well.
std::string section_name =
MachOImageSegmentReader::SectionNameString(expect_section->sectname);
const process_types::section* actual_section_by_name =
actual_segment->GetSectionByName(section_name);
EXPECT_EQ(actual_section, actual_section_by_name);
// Make sure that the section is accessible by the parent MachOImageReaders
// GetSectionByName.
mach_vm_address_t actual_section_address;
const process_types::section* actual_section_from_image_by_name =
actual_image->GetSectionByName(
segment_name, section_name, &actual_section_address);
EXPECT_EQ(actual_section, actual_section_from_image_by_name);
if (actual_segment->SegmentSlides()) {
EXPECT_EQ(actual_section_address,
actual_section->addr + actual_image->Slide());
unsigned long expect_section_size;
const uint8_t* expect_section_data = getsectiondata(expect_image,
segment_name.c_str(),
section_name.c_str(),
&expect_section_size);
mach_vm_address_t expect_section_address =
reinterpret_cast<mach_vm_address_t>(expect_section_data);
EXPECT_EQ(expect_section_address, actual_section_address);
EXPECT_EQ(expect_section_size, actual_section->size);
} else {
EXPECT_EQ(actual_section_address, actual_section->addr);
}
// Test the parent MachOImageReaders GetSectionAtIndex as well.
mach_vm_address_t actual_section_address_at_index;
const process_types::section* actual_section_from_image_at_index =
actual_image->GetSectionAtIndex(++(*section_index),
&actual_section_address_at_index);
EXPECT_EQ(actual_section, actual_section_from_image_at_index);
EXPECT_EQ(actual_section_address, actual_section_address_at_index);
}
EXPECT_EQ(NULL, actual_segment->GetSectionByName("NoSuchSection"));
}
// Walks through the load commands of |expect_image|, finding all of the
// expected segment commands. For each expected segment command, calls
// actual_image->GetSegmentByName() to obtain an actual segment command, and
// calls ExpectSegmentCommand() to compare the expected and actual segments. A
// series of by-name lookups is also performed on the segment to ensure that it
// behaves correctly when attempting to look up segment and section names that
// are not present. |test_section_indices| should be true to test
// MachOImageReader::GetSectionAtIndex() using out-of-range section indices.
// This should be tested for at least one module, but its very noisy in terms
// of logging output, so this knob is provided to suppress this portion of the
// test when looping over all modules.
void ExpectSegmentCommands(const MachHeader* expect_image,
const MachOImageReader* actual_image,
bool test_section_index_bounds) {
ASSERT_TRUE(expect_image);
ASSERT_TRUE(actual_image);
const char* commands_base = reinterpret_cast<const char*>(&expect_image[1]);
uint32_t position = 0;
size_t section_index = 0;
for (uint32_t index = 0; index < expect_image->ncmds; ++index) {
ASSERT_LT(position, expect_image->sizeofcmds);
const load_command* command =
reinterpret_cast<const load_command*>(&commands_base[position]);
ASSERT_LE(position + command->cmdsize, expect_image->sizeofcmds);
if (command->cmd == kSegmentCommand) {
const SegmentCommand* expect_segment =
reinterpret_cast<const SegmentCommand*>(command);
std::string segment_name =
MachOImageSegmentReader::SegmentNameString(expect_segment->segname);
mach_vm_address_t actual_segment_address;
mach_vm_size_t actual_segment_size;
const MachOImageSegmentReader* actual_segment =
actual_image->GetSegmentByName(
segment_name, &actual_segment_address, &actual_segment_size);
ExpectSegmentCommand(expect_segment,
expect_image,
actual_segment,
actual_segment_address,
actual_segment_size,
actual_image,
&section_index);
if (testing::Test::HasFatalFailure()) {
return;
}
}
position += command->cmdsize;
}
EXPECT_EQ(expect_image->sizeofcmds, position);
if (test_section_index_bounds) {
// GetSectionAtIndex uses a 1-based index. Make sure that the range is
// correct.
EXPECT_EQ(NULL, actual_image->GetSectionAtIndex(0, NULL));
EXPECT_EQ(NULL, actual_image->GetSectionAtIndex(section_index + 1, NULL));
}
// Make sure that by-name lookups for names that dont exist work properly:
// they should return NULL.
EXPECT_FALSE(actual_image->GetSegmentByName("NoSuchSegment", NULL, NULL));
EXPECT_FALSE(
actual_image->GetSectionByName("NoSuchSegment", "NoSuchSection", NULL));
// Make sure that theres a __TEXT segment so that this can do a valid test of
// a section that doesnt exist within a segment that does.
EXPECT_TRUE(actual_image->GetSegmentByName(SEG_TEXT, NULL, NULL));
EXPECT_FALSE(actual_image->GetSectionByName(SEG_TEXT, "NoSuchSection", NULL));
// Similarly, make sure that a section name that exists in one segment isnt
// accidentally found during a lookup for that section in a different segment.
EXPECT_TRUE(actual_image->GetSectionByName(SEG_TEXT, SECT_TEXT, NULL));
EXPECT_FALSE(
actual_image->GetSectionByName("NoSuchSegment", SECT_TEXT, NULL));
EXPECT_FALSE(actual_image->GetSectionByName(SEG_DATA, SECT_TEXT, NULL));
// The __LINKEDIT segment normally does exist but doesnt have any sections.
EXPECT_FALSE(
actual_image->GetSectionByName(SEG_LINKEDIT, "NoSuchSection", NULL));
EXPECT_FALSE(actual_image->GetSectionByName(SEG_LINKEDIT, SECT_TEXT, NULL));
}
// Verifies that |expect_image| is a vaild Mach-O header for the current system
// by checking its |magic| and |cputype| fields. Then, verifies that the
// information in |actual_image| matches that in |expect_image|. The |filetype|
// field is examined, and actual_image->Address() is compared to
// |expect_image_address|. Various other attributes of |actual_image| are
// sanity-checked depending on the Mach-O file type. Finally,
// ExpectSegmentCommands() is called to verify all that all of the segments
// match; |test_section_index_bounds| is used as an argument to that function.
void ExpectMachImage(const MachHeader* expect_image,
mach_vm_address_t expect_image_address,
const MachOImageReader* actual_image,
bool test_section_index_bounds) {
ASSERT_TRUE(expect_image);
ASSERT_TRUE(actual_image);
EXPECT_EQ(kMachMagic, expect_image->magic);
EXPECT_EQ(kCPUType, expect_image->cputype);
EXPECT_EQ(expect_image->filetype, actual_image->FileType());
EXPECT_EQ(expect_image_address, actual_image->Address());
mach_vm_address_t actual_text_segment_address;
mach_vm_size_t actual_text_segment_size;
const MachOImageSegmentReader* actual_text_segment =
actual_image->GetSegmentByName(
SEG_TEXT, &actual_text_segment_address, &actual_text_segment_size);
ASSERT_TRUE(actual_text_segment);
EXPECT_EQ(expect_image_address, actual_text_segment_address);
EXPECT_EQ(actual_image->Size(), actual_text_segment_size);
EXPECT_EQ(expect_image_address - actual_text_segment->vmaddr(),
actual_image->Slide());
uint32_t file_type = actual_image->FileType();
EXPECT_TRUE(file_type == MH_EXECUTE || file_type == MH_DYLIB ||
file_type == MH_DYLINKER || file_type == MH_BUNDLE);
if (file_type == MH_EXECUTE || file_type == MH_DYLINKER) {
EXPECT_EQ("/usr/lib/dyld", actual_image->DylinkerName());
}
// For these, just dont crash or anything.
if (file_type == MH_DYLIB) {
actual_image->DylibVersion();
}
actual_image->SourceVersion();
UUID uuid;
actual_image->UUID(&uuid);
ExpectSegmentCommands(expect_image, actual_image, test_section_index_bounds);
}
TEST(MachOImageReader, Self_MainExecutable) {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
const MachHeader* mh_execute_header = reinterpret_cast<MachHeader*>(
dlsym(RTLD_MAIN_ONLY, "_mh_execute_header"));
ASSERT_NE(static_cast<void*>(NULL), mh_execute_header);
mach_vm_address_t mh_execute_header_address =
reinterpret_cast<mach_vm_address_t>(mh_execute_header);
MachOImageReader image_reader;
ASSERT_TRUE(image_reader.Initialize(
&process_reader, mh_execute_header_address, "mh_execute_header"));
EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), image_reader.FileType());
ExpectMachImage(
mh_execute_header, mh_execute_header_address, &image_reader, true);
}
TEST(MachOImageReader, Self_DyldImages) {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
const struct dyld_all_image_infos* dyld_image_infos =
_dyld_get_all_image_infos();
ASSERT_GE(dyld_image_infos->version, 1u);
ASSERT_TRUE(dyld_image_infos->infoArray);
for (uint32_t index = 0; index < dyld_image_infos->infoArrayCount; ++index) {
const dyld_image_info* dyld_image = &dyld_image_infos->infoArray[index];
SCOPED_TRACE(base::StringPrintf(
"index %u, image %s", index, dyld_image->imageFilePath));
// dyld_image_info::imageLoadAddress is poorly-declared: its declared as
// const mach_header* in both 32-bit and 64-bit environments, but in the
// 64-bit environment, it should be const mach_header_64*.
const MachHeader* mach_header =
reinterpret_cast<const MachHeader*>(dyld_image->imageLoadAddress);
mach_vm_address_t image_address =
reinterpret_cast<mach_vm_address_t>(mach_header);
MachOImageReader image_reader;
ASSERT_TRUE(image_reader.Initialize(
&process_reader, image_address, dyld_image->imageFilePath));
uint32_t file_type = image_reader.FileType();
if (index == 0) {
EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE), file_type);
} else {
EXPECT_TRUE(file_type == MH_DYLIB || file_type == MH_BUNDLE);
}
ExpectMachImage(mach_header, image_address, &image_reader, false);
if (Test::HasFatalFailure()) {
return;
}
}
// Now that all of the modules have been verified, make sure that dyld itself
// can be read properly too.
if (dyld_image_infos->version >= 2) {
SCOPED_TRACE("dyld");
// dyld_all_image_infos::dyldImageLoadAddress is poorly-declared too.
const MachHeader* mach_header = reinterpret_cast<const MachHeader*>(
dyld_image_infos->dyldImageLoadAddress);
mach_vm_address_t image_address =
reinterpret_cast<mach_vm_address_t>(mach_header);
MachOImageReader image_reader;
ASSERT_TRUE(
image_reader.Initialize(&process_reader, image_address, "dyld"));
EXPECT_EQ(static_cast<uint32_t>(MH_DYLINKER), image_reader.FileType());
ExpectMachImage(mach_header, image_address, &image_reader, false);
if (Test::HasFatalFailure()) {
return;
}
}
// If dyld is new enough to record UUIDs, check the UUID of any module that
// it says has one. Note that dyld doesnt record UUIDs of anything that
// loaded out of the shared cache, but it should at least have a UUID for the
// main executable if it has one.
if (dyld_image_infos->version >= 8 && dyld_image_infos->uuidArray) {
for (uint32_t index = 0;
index < dyld_image_infos->uuidArrayCount;
++index) {
const dyld_uuid_info* dyld_image = &dyld_image_infos->uuidArray[index];
SCOPED_TRACE(base::StringPrintf("uuid index %u", index));
// dyld_uuid_info::imageLoadAddress is poorly-declared too.
const MachHeader* mach_header =
reinterpret_cast<const MachHeader*>(dyld_image->imageLoadAddress);
mach_vm_address_t image_address =
reinterpret_cast<mach_vm_address_t>(mach_header);
MachOImageReader image_reader;
ASSERT_TRUE(
image_reader.Initialize(&process_reader, image_address, "uuid"));
ExpectMachImage(mach_header, image_address, &image_reader, false);
UUID expected_uuid;
expected_uuid.InitializeFromBytes(dyld_image->imageUUID);
UUID actual_uuid;
image_reader.UUID(&actual_uuid);
EXPECT_EQ(expected_uuid, actual_uuid);
}
}
}
} // namespace

View File

@ -194,6 +194,17 @@ const process_types::section* MachOImageSegmentReader::GetSectionAtIndex(
return &sections_[index];
}
bool MachOImageSegmentReader::SegmentSlides() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
// These are the same rules that the kernel uses to identify __PAGEZERO. See
// 10.9.4 xnu-2422.110.17/bsd/kern/mach_loader.c load_segment().
return !(segment_command_.vmaddr == 0 && segment_command_.filesize == 0 &&
segment_command_.vmsize != 0 &&
(segment_command_.initprot & VM_PROT_ALL) == VM_PROT_NONE &&
(segment_command_.maxprot & VM_PROT_ALL) == VM_PROT_NONE);
}
// static
std::string MachOImageSegmentReader::SegmentNameString(
const char* segment_name_c) {

View File

@ -136,10 +136,23 @@ class MachOImageSegmentReader {
//! \note The process_types::section::addr field gives the sections preferred
//! load address as stored in the Mach-O image file, and is not adjusted
//! for any “slide” that may have occurred when the image was loaded.
//! \note Unlike MachOImageReader::GetSectionAtIndex(), this method does not
//! accept out-of-range values for \a index, and aborts execution instead
//! of returning `NULL` upon encountering an out-of-range value. This is
//! because this method is expected to be used in a loop that can be
//! limited to nsects() iterations, so an out-of-range error can be
//! treated more harshly as a logic error, as opposed to a data error.
//!
//! \sa MachOImageReader::GetSectionAtIndex()
const process_types::section* GetSectionAtIndex(size_t index) const;
//! Returns whether the segment slides.
//!
//! Most segments slide, but the __PAGEZERO segment does not, it grows
//! instead. This method identifies non-sliding segments in the same way that
//! the kernel does.
bool SegmentSlides() const;
//! \brief Returns a segment name string.
//!
//! Segment names may be 16 characters long, and are not necessarily

View File

@ -23,11 +23,7 @@
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "util/mac/mac_util.h"
extern "C" {
const struct dyld_all_image_infos* _dyld_get_all_image_infos();
} // extern "C"
#include "util/test/mac/dyld.h"
namespace {

View File

@ -41,6 +41,10 @@ UUID::UUID(const uint8_t* bytes) {
InitializeFromBytes(bytes);
}
bool UUID::operator==(const UUID& that) const {
return memcmp(this, &that, sizeof(UUID)) == 0;
}
void UUID::InitializeFromBytes(const uint8_t* bytes) {
memcpy(this, bytes, sizeof(*this));
data_1 = base::NetToHost32(data_1);

View File

@ -36,6 +36,9 @@ struct UUID {
//! \copydoc InitializeFromBytes()
explicit UUID(const uint8_t* bytes);
bool operator==(const UUID& that) const;
bool operator!=(const UUID& that) const { return !operator==(that); }
//! \brief Initializes the %UUID from a sequence of bytes.
//!
//! \a bytes is taken as a %UUID laid out in big-endian format in memory. On

View File

@ -70,6 +70,39 @@ TEST(UUID, UUID) {
EXPECT_EQ(0x0fu, uuid.data_5[5]);
EXPECT_EQ("00010203-0405-0607-0809-0a0b0c0d0e0f", uuid.ToString());
// Test both operator== and operator!=.
EXPECT_FALSE(uuid == uuid_zero);
EXPECT_NE(uuid, uuid_zero);
UUID uuid_2(kBytes);
EXPECT_EQ(uuid, uuid_2);
EXPECT_FALSE(uuid != uuid_2);
// Make sure that operator== and operator!= check the entire UUID.
++uuid.data_1;
EXPECT_NE(uuid, uuid_2);
--uuid.data_1;
++uuid.data_2;
EXPECT_NE(uuid, uuid_2);
--uuid.data_2;
++uuid.data_3;
EXPECT_NE(uuid, uuid_2);
--uuid.data_3;
for (size_t index = 0; index < arraysize(uuid.data_4); ++index) {
++uuid.data_4[index];
EXPECT_NE(uuid, uuid_2);
--uuid.data_4[index];
}
for (size_t index = 0; index < arraysize(uuid.data_5); ++index) {
++uuid.data_5[index];
EXPECT_NE(uuid, uuid_2);
--uuid.data_5[index];
}
// Make sure that the UUIDs are equal again, otherwise the test above may not
// have been valid.
EXPECT_EQ(uuid, uuid_2);
const uint8_t kMoreBytes[16] = {0xff,
0xee,
0xdd,
@ -100,6 +133,9 @@ TEST(UUID, UUID) {
EXPECT_EQ(0x00u, uuid.data_5[5]);
EXPECT_EQ("ffeeddcc-bbaa-9988-7766-554433221100", uuid.ToString());
EXPECT_NE(uuid, uuid_2);
EXPECT_NE(uuid, uuid_zero);
// Test that UUID is standard layout.
memset(&uuid, 0x45, 16);
EXPECT_EQ(0x45454545u, uuid.data_1);

29
util/test/mac/dyld.h Normal file
View File

@ -0,0 +1,29 @@
// 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_TEST_MAC_DYLD_H_
#define CRASHPAD_UTIL_TEST_MAC_DYLD_H_
#include <mach-o/dyld_images.h>
extern "C" {
// Returns a pointer to this process dyld_all_image_infos structure. This is
// implemented as a non-public dyld API, declared in 10.9.2
// dyld-239.4/include/mach-o/dyld_priv.h.
const dyld_all_image_infos* _dyld_get_all_image_infos();
} // extern "C"
#endif // CRASHPAD_UTIL_TEST_MAC_DYLD_H_

View File

@ -38,6 +38,8 @@
'mac/launchd.mm',
'mac/mac_util.cc',
'mac/mac_util.h',
'mac/mach_o_image_reader.cc',
'mac/mach_o_image_reader.h',
'mac/mach_o_image_segment_reader.cc',
'mac/mach_o_image_segment_reader.h',
'mac/service_management.cc',
@ -102,6 +104,7 @@
'test/errors.h',
'test/executable_path.h',
'test/executable_path_mac.cc',
'test/mac/dyld.h',
'test/mac/mach_errors.cc',
'test/mac/mach_errors.h',
'test/mac/mach_multiprocess.cc',
@ -133,6 +136,7 @@
'mac/checked_mach_address_range_test.cc',
'mac/launchd_test.mm',
'mac/mac_util_test.mm',
'mac/mach_o_image_reader_test.cc',
'mac/mach_o_image_segment_reader_test.cc',
'mac/process_reader_test.cc',
'mac/process_types_test.cc',