mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
52064fdd1b
commit
4f74716f6d
580
util/mac/mach_o_image_reader.cc
Normal file
580
util/mac/mach_o_image_reader.cc
Normal 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 doesn’t 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 command’s stated size is known, make sure that it
|
||||||
|
// doesn’t 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 don’t 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 doesn’t render a module unusable, it’s 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 doesn’t need to bother
|
||||||
|
// removing the segment from segments_. The segment will be properly released
|
||||||
|
// when the image is destroyed, and the image won’t be usable because
|
||||||
|
// initialization won’t 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 segment’s
|
||||||
|
// 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
|
279
util/mac/mach_o_image_reader.h
Normal file
279
util/mac/mach_o_image_reader.h
Normal 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 file’s 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 module’s 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 image’s 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 image’s __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 segment’s 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 image’s “slide,” the difference between its
|
||||||
|
//! actual load address and its preferred load address.
|
||||||
|
//!
|
||||||
|
//! “Slide” is computed by subtracting the __TEXT segment’s 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 segment’s 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
|
||||||
|
//! segment’s 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 section’s size, because it can be
|
||||||
|
//! obtained from the returned process_types::section::size field.
|
||||||
|
//!
|
||||||
|
//! \note The process_types::section::addr field gives the section’s 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 section’s 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 section’s size, because it can be
|
||||||
|
//! obtained from the returned process_types::section::size field.
|
||||||
|
//!
|
||||||
|
//! \note The process_types::section::addr field gives the section’s 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 section’s 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 file’s 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 code’s 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 image’s current version.
|
||||||
|
//!
|
||||||
|
//! This information comes from the `dylib_current_version` field of a dylib’s
|
||||||
|
//! `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 image’s source version.
|
||||||
|
//!
|
||||||
|
//! This information comes from a Mach-O image’s `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 image’s UUID.
|
||||||
|
//!
|
||||||
|
//! This information comes from a Mach-O image’s `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 parameter’s
|
||||||
|
// type needs to be qualified with |crashpad::|.
|
||||||
|
void UUID(crashpad::UUID* uuid) const;
|
||||||
|
|
||||||
|
//! \brief Returns the dynamic linker’s 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 can’t 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_
|
448
util/mac/mach_o_image_reader_test.cc
Normal file
448
util/mac/mach_o_image_reader_test.cc
Normal 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() doesn’t 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 MachOImageReader’s
|
||||||
|
// 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 MachOImageReader’s 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 it’s 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,
|
||||||
|
§ion_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 don’t 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 there’s a __TEXT segment so that this can do a valid test of
|
||||||
|
// a section that doesn’t 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 isn’t
|
||||||
|
// 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 doesn’t 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 don’t 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: it’s 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 doesn’t 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
|
@ -194,6 +194,17 @@ const process_types::section* MachOImageSegmentReader::GetSectionAtIndex(
|
|||||||
return §ions_[index];
|
return §ions_[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
|
// static
|
||||||
std::string MachOImageSegmentReader::SegmentNameString(
|
std::string MachOImageSegmentReader::SegmentNameString(
|
||||||
const char* segment_name_c) {
|
const char* segment_name_c) {
|
||||||
|
@ -136,10 +136,23 @@ class MachOImageSegmentReader {
|
|||||||
//! \note The process_types::section::addr field gives the section’s preferred
|
//! \note The process_types::section::addr field gives the section’s preferred
|
||||||
//! load address as stored in the Mach-O image file, and is not adjusted
|
//! 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.
|
//! 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()
|
//! \sa MachOImageReader::GetSectionAtIndex()
|
||||||
const process_types::section* GetSectionAtIndex(size_t index) const;
|
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.
|
//! \brief Returns a segment name string.
|
||||||
//!
|
//!
|
||||||
//! Segment names may be 16 characters long, and are not necessarily
|
//! Segment names may be 16 characters long, and are not necessarily
|
||||||
|
@ -23,11 +23,7 @@
|
|||||||
#include "base/strings/stringprintf.h"
|
#include "base/strings/stringprintf.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "util/mac/mac_util.h"
|
#include "util/mac/mac_util.h"
|
||||||
|
#include "util/test/mac/dyld.h"
|
||||||
extern "C" {
|
|
||||||
const struct dyld_all_image_infos* _dyld_get_all_image_infos();
|
|
||||||
|
|
||||||
} // extern "C"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -41,6 +41,10 @@ UUID::UUID(const uint8_t* bytes) {
|
|||||||
InitializeFromBytes(bytes);
|
InitializeFromBytes(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UUID::operator==(const UUID& that) const {
|
||||||
|
return memcmp(this, &that, sizeof(UUID)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
void UUID::InitializeFromBytes(const uint8_t* bytes) {
|
void UUID::InitializeFromBytes(const uint8_t* bytes) {
|
||||||
memcpy(this, bytes, sizeof(*this));
|
memcpy(this, bytes, sizeof(*this));
|
||||||
data_1 = base::NetToHost32(data_1);
|
data_1 = base::NetToHost32(data_1);
|
||||||
|
@ -36,6 +36,9 @@ struct UUID {
|
|||||||
//! \copydoc InitializeFromBytes()
|
//! \copydoc InitializeFromBytes()
|
||||||
explicit UUID(const uint8_t* bytes);
|
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.
|
//! \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
|
//! \a bytes is taken as a %UUID laid out in big-endian format in memory. On
|
||||||
|
@ -70,6 +70,39 @@ TEST(UUID, UUID) {
|
|||||||
EXPECT_EQ(0x0fu, uuid.data_5[5]);
|
EXPECT_EQ(0x0fu, uuid.data_5[5]);
|
||||||
EXPECT_EQ("00010203-0405-0607-0809-0a0b0c0d0e0f", uuid.ToString());
|
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,
|
const uint8_t kMoreBytes[16] = {0xff,
|
||||||
0xee,
|
0xee,
|
||||||
0xdd,
|
0xdd,
|
||||||
@ -100,6 +133,9 @@ TEST(UUID, UUID) {
|
|||||||
EXPECT_EQ(0x00u, uuid.data_5[5]);
|
EXPECT_EQ(0x00u, uuid.data_5[5]);
|
||||||
EXPECT_EQ("ffeeddcc-bbaa-9988-7766-554433221100", uuid.ToString());
|
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.
|
// Test that UUID is standard layout.
|
||||||
memset(&uuid, 0x45, 16);
|
memset(&uuid, 0x45, 16);
|
||||||
EXPECT_EQ(0x45454545u, uuid.data_1);
|
EXPECT_EQ(0x45454545u, uuid.data_1);
|
||||||
|
29
util/test/mac/dyld.h
Normal file
29
util/test/mac/dyld.h
Normal 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_
|
@ -38,6 +38,8 @@
|
|||||||
'mac/launchd.mm',
|
'mac/launchd.mm',
|
||||||
'mac/mac_util.cc',
|
'mac/mac_util.cc',
|
||||||
'mac/mac_util.h',
|
'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.cc',
|
||||||
'mac/mach_o_image_segment_reader.h',
|
'mac/mach_o_image_segment_reader.h',
|
||||||
'mac/service_management.cc',
|
'mac/service_management.cc',
|
||||||
@ -102,6 +104,7 @@
|
|||||||
'test/errors.h',
|
'test/errors.h',
|
||||||
'test/executable_path.h',
|
'test/executable_path.h',
|
||||||
'test/executable_path_mac.cc',
|
'test/executable_path_mac.cc',
|
||||||
|
'test/mac/dyld.h',
|
||||||
'test/mac/mach_errors.cc',
|
'test/mac/mach_errors.cc',
|
||||||
'test/mac/mach_errors.h',
|
'test/mac/mach_errors.h',
|
||||||
'test/mac/mach_multiprocess.cc',
|
'test/mac/mach_multiprocess.cc',
|
||||||
@ -133,6 +136,7 @@
|
|||||||
'mac/checked_mach_address_range_test.cc',
|
'mac/checked_mach_address_range_test.cc',
|
||||||
'mac/launchd_test.mm',
|
'mac/launchd_test.mm',
|
||||||
'mac/mac_util_test.mm',
|
'mac/mac_util_test.mm',
|
||||||
|
'mac/mach_o_image_reader_test.cc',
|
||||||
'mac/mach_o_image_segment_reader_test.cc',
|
'mac/mach_o_image_segment_reader_test.cc',
|
||||||
'mac/process_reader_test.cc',
|
'mac/process_reader_test.cc',
|
||||||
'mac/process_types_test.cc',
|
'mac/process_types_test.cc',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user