From 4f74716f6df9464383db75ab97aa216d08cca6b9 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Thu, 4 Sep 2014 11:45:40 -0400 Subject: [PATCH] Add MachOImageReader and its test, which also tests MachOImageSegmentReader. TEST=util_test MachOImageReader.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/535343004 --- util/mac/mach_o_image_reader.cc | 580 ++++++++++++++++++++++++ util/mac/mach_o_image_reader.h | 279 ++++++++++++ util/mac/mach_o_image_reader_test.cc | 448 ++++++++++++++++++ util/mac/mach_o_image_segment_reader.cc | 11 + util/mac/mach_o_image_segment_reader.h | 13 + util/mac/process_types_test.cc | 6 +- util/misc/uuid.cc | 4 + util/misc/uuid.h | 3 + util/misc/uuid_test.cc | 36 ++ util/test/mac/dyld.h | 29 ++ util/util.gyp | 4 + 11 files changed, 1408 insertions(+), 5 deletions(-) create mode 100644 util/mac/mach_o_image_reader.cc create mode 100644 util/mac/mach_o_image_reader.h create mode 100644 util/mac/mach_o_image_reader_test.cc create mode 100644 util/test/mac/dyld.h diff --git a/util/mac/mach_o_image_reader.cc b/util/mac/mach_o_image_reader.cc new file mode 100644 index 00000000..243976ee --- /dev/null +++ b/util/mac/mach_o_image_reader.cc @@ -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 +#include +#include + +#include +#include + +#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::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 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(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 +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 diff --git a/util/mac/mach_o_image_reader.h b/util/mac/mach_o_image_reader.h new file mode 100644 index 00000000..eb243548 --- /dev/null +++ b/util/mac/mach_o_image_reader.h @@ -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 +#include + +#include +#include + +#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 + 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 segments_; + std::map 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 symtab_command_; + scoped_ptr dysymtab_command_; + scoped_ptr 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_ diff --git a/util/mac/mach_o_image_reader_test.cc b/util/mac/mach_o_image_reader_test.cc new file mode 100644 index 00000000..fbc7b50b --- /dev/null +++ b/util/mac/mach_o_image_reader_test.cc @@ -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 +#include +#include +#include +#include + +#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(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(&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(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(&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(&commands_base[position]); + ASSERT_LE(position + command->cmdsize, expect_image->sizeofcmds); + if (command->cmd == kSegmentCommand) { + const SegmentCommand* expect_segment = + reinterpret_cast(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( + dlsym(RTLD_MAIN_ONLY, "_mh_execute_header")); + ASSERT_NE(static_cast(NULL), mh_execute_header); + mach_vm_address_t mh_execute_header_address = + reinterpret_cast(mh_execute_header); + + MachOImageReader image_reader; + ASSERT_TRUE(image_reader.Initialize( + &process_reader, mh_execute_header_address, "mh_execute_header")); + + EXPECT_EQ(static_cast(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(dyld_image->imageLoadAddress); + mach_vm_address_t image_address = + reinterpret_cast(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(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( + dyld_image_infos->dyldImageLoadAddress); + mach_vm_address_t image_address = + reinterpret_cast(mach_header); + + MachOImageReader image_reader; + ASSERT_TRUE( + image_reader.Initialize(&process_reader, image_address, "dyld")); + + EXPECT_EQ(static_cast(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(dyld_image->imageLoadAddress); + mach_vm_address_t image_address = + reinterpret_cast(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 diff --git a/util/mac/mach_o_image_segment_reader.cc b/util/mac/mach_o_image_segment_reader.cc index 7469497f..ceb9a90d 100644 --- a/util/mac/mach_o_image_segment_reader.cc +++ b/util/mac/mach_o_image_segment_reader.cc @@ -194,6 +194,17 @@ const process_types::section* MachOImageSegmentReader::GetSectionAtIndex( 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 std::string MachOImageSegmentReader::SegmentNameString( const char* segment_name_c) { diff --git a/util/mac/mach_o_image_segment_reader.h b/util/mac/mach_o_image_segment_reader.h index 66fab9a8..1a97d2ff 100644 --- a/util/mac/mach_o_image_segment_reader.h +++ b/util/mac/mach_o_image_segment_reader.h @@ -136,10 +136,23 @@ class MachOImageSegmentReader { //! \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. + //! \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 diff --git a/util/mac/process_types_test.cc b/util/mac/process_types_test.cc index fb445f33..2bafdcae 100644 --- a/util/mac/process_types_test.cc +++ b/util/mac/process_types_test.cc @@ -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 { diff --git a/util/misc/uuid.cc b/util/misc/uuid.cc index 1af79d83..a7ba4a8f 100644 --- a/util/misc/uuid.cc +++ b/util/misc/uuid.cc @@ -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); diff --git a/util/misc/uuid.h b/util/misc/uuid.h index 04270e3e..c2529dfc 100644 --- a/util/misc/uuid.h +++ b/util/misc/uuid.h @@ -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 diff --git a/util/misc/uuid_test.cc b/util/misc/uuid_test.cc index 2e3bea32..5d515cc6 100644 --- a/util/misc/uuid_test.cc +++ b/util/misc/uuid_test.cc @@ -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); diff --git a/util/test/mac/dyld.h b/util/test/mac/dyld.h new file mode 100644 index 00000000..854d7776 --- /dev/null +++ b/util/test/mac/dyld.h @@ -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 + +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_ diff --git a/util/util.gyp b/util/util.gyp index 08eef382..7b2978e8 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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',