aboutsummaryrefslogtreecommitdiff
path: root/src/common/mac
diff options
context:
space:
mode:
authorted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-06-25 16:56:16 +0000
committerted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-06-25 16:56:16 +0000
commit35c41e00ee2cf9280fe0122c75877ba70b41bb46 (patch)
tree5b3df3085b0f8743b1c93ffca297062eb28efbb4 /src/common/mac
parentConvert files in .nib format to .xib format. (diff)
downloadbreakpad-35c41e00ee2cf9280fe0122c75877ba70b41bb46.tar.xz
Breakpad Mac symbol dumper: Add new Mach-O reader class.
This patch adds files defining new classes in the google_breakpad::Mach_O namespace for parsing fat binaries and Mach-O files. These are used in the new dumper to handle STABS debugging information, DWARF call frame information, and .eh_frame exception handling stack walking information. These new classes are independent of endianness and word size, and therefore can be used on binaries of all the relevant architectures: x86, x86_64, ppc, and ARM. The patch adds a complete set of unit tests for the new classes. A=jimb R=mark (http://breakpad.appspot.com/93001/show, http://breakpad.appspot.com/115001/show) git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@610 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/common/mac')
-rw-r--r--src/common/mac/macho_reader.cc524
-rw-r--r--src/common/mac/macho_reader.h459
-rw-r--r--src/common/mac/macho_reader_unittest.cc1898
3 files changed, 2881 insertions, 0 deletions
diff --git a/src/common/mac/macho_reader.cc b/src/common/mac/macho_reader.cc
new file mode 100644
index 00000000..53da1807
--- /dev/null
+++ b/src/common/mac/macho_reader.cc
@@ -0,0 +1,524 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// macho_reader.cc: Implementation of google_breakpad::Mach_O::FatReader and
+// google_breakpad::Mach_O::Reader. See macho_reader.h for details.
+
+#include "common/mac/macho_reader.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace google_breakpad {
+namespace mach_o {
+
+// If NDEBUG is #defined, then the 'assert' macro doesn't evaluate its
+// arguments, so you can't place expressions that do necessary work in
+// the argument of an assert. Nor can you assign the result of the
+// expression to a variable and assert that the variable's value is
+// true: you'll get unused variable warnings when NDEBUG is #defined.
+//
+// ASSERT_ALWAYS_EVAL always evaluates its argument, and asserts that
+// the result is true if NDEBUG is not #defined.
+#if defined(NDEBUG)
+#define ASSERT_ALWAYS_EVAL(x) (x)
+#else
+#define ASSERT_ALWAYS_EVAL(x) assert(x)
+#endif
+
+void FatReader::Reporter::BadHeader() {
+ fprintf(stderr, "%s: file is neither a fat binary file"
+ " nor a Mach-O object file\n", filename_.c_str());
+}
+
+void FatReader::Reporter::TooShort() {
+ fprintf(stderr, "%s: file too short for the data it claims to contain\n",
+ filename_.c_str());
+}
+
+void FatReader::Reporter::MisplacedObjectFile() {
+ fprintf(stderr, "%s: file too short for the object files it claims"
+ " to contain\n", filename_.c_str());
+}
+
+bool FatReader::Read(const uint8_t *buffer, size_t size) {
+ buffer_.start = buffer;
+ buffer_.end = buffer + size;
+ ByteCursor cursor(&buffer_);
+
+ // Fat binaries always use big-endian, so read the magic number in
+ // that endianness. To recognize Mach-O magic numbers, which can use
+ // either endianness, check for both the proper and reversed forms
+ // of the magic numbers.
+ cursor.set_big_endian(true);
+ if (cursor >> magic_) {
+ if (magic_ == FAT_MAGIC) {
+ // How many object files does this fat binary contain?
+ uint32_t object_files_count;
+ if (!(cursor >> object_files_count)) { // nfat_arch
+ reporter_->TooShort();
+ return false;
+ }
+
+ // Read the list of object files.
+ object_files_.resize(object_files_count);
+ for (size_t i = 0; i < object_files_count; i++) {
+ struct fat_arch *objfile = &object_files_[i];
+
+ // Read this object file entry, byte-swapping as appropriate.
+ cursor >> objfile->cputype
+ >> objfile->cpusubtype
+ >> objfile->offset
+ >> objfile->size
+ >> objfile->align;
+ if (!cursor) {
+ reporter_->TooShort();
+ return false;
+ }
+ // Does the file actually have the bytes this entry refers to?
+ size_t fat_size = buffer_.Size();
+ if (objfile->offset > fat_size ||
+ objfile->size > fat_size - objfile->offset) {
+ reporter_->MisplacedObjectFile();
+ return false;
+ }
+ }
+
+ return true;
+ } else if (magic_ == MH_MAGIC || magic_ == MH_MAGIC_64 ||
+ magic_ == MH_CIGAM || magic_ == MH_CIGAM_64) {
+ // If this is a little-endian Mach-O file, fix the cursor's endianness.
+ if (magic_ == MH_CIGAM || magic_ == MH_CIGAM_64)
+ cursor.set_big_endian(false);
+ // Record the entire file as a single entry in the object file list.
+ object_files_.resize(1);
+
+ // Get the cpu type and subtype from the Mach-O header.
+ if (!(cursor >> object_files_[0].cputype
+ >> object_files_[0].cpusubtype)) {
+ reporter_->TooShort();
+ return false;
+ }
+
+ object_files_[0].offset = 0;
+ object_files_[0].size = buffer_.Size();
+ // This alignment is correct for 32 and 64-bit x86 and ppc.
+ // See get_align in the lipo source for other architectures:
+ // http://www.opensource.apple.com/source/cctools/cctools-773/misc/lipo.c
+ object_files_[0].align = 12; // 2^12 == 4096
+
+ return true;
+ }
+ }
+
+ reporter_->BadHeader();
+ return false;
+}
+
+void Reader::Reporter::BadHeader() {
+ fprintf(stderr, "%s: file is not a Mach-O object file\n", filename_.c_str());
+}
+
+void Reader::Reporter::CPUTypeMismatch(cpu_type_t cpu_type,
+ cpu_subtype_t cpu_subtype,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype) {
+ fprintf(stderr, "%s: CPU type %d, subtype %d does not match expected"
+ " type %d, subtype %d\n",
+ filename_.c_str(), cpu_type, cpu_subtype,
+ expected_cpu_type, expected_cpu_subtype);
+}
+
+void Reader::Reporter::HeaderTruncated() {
+ fprintf(stderr, "%s: file does not contain a complete Mach-O header\n",
+ filename_.c_str());
+}
+
+void Reader::Reporter::LoadCommandRegionTruncated() {
+ fprintf(stderr, "%s: file too short to hold load command region"
+ " given in Mach-O header\n", filename_.c_str());
+}
+
+void Reader::Reporter::LoadCommandsOverrun(size_t claimed, size_t i,
+ LoadCommandType type) {
+ fprintf(stderr, "%s: file's header claims there are %ld"
+ " load commands, but load command #%ld",
+ filename_.c_str(), claimed, i);
+ if (type) fprintf(stderr, ", of type %d,", type);
+ fprintf(stderr, " extends beyond the end of the load command region\n");
+}
+
+void Reader::Reporter::LoadCommandTooShort(size_t i, LoadCommandType type) {
+ fprintf(stderr, "%s: the contents of load command #%ld, of type %d,"
+ " extend beyond the size given in the load command's header\n",
+ filename_.c_str(), i, type);
+}
+
+void Reader::Reporter::SectionsMissing(const string &name) {
+ fprintf(stderr, "%s: the load command for segment '%s'"
+ " is too short to hold the section headers it claims to have\n",
+ filename_.c_str(), name.c_str());
+}
+
+void Reader::Reporter::MisplacedSegmentData(const string &name) {
+ fprintf(stderr, "%s: the segment '%s' claims its contents lie beyond"
+ " the end of the file\n", filename_.c_str(), name.c_str());
+}
+
+void Reader::Reporter::MisplacedSectionData(const string &section,
+ const string &segment) {
+ fprintf(stderr, "%s: the section '%s' in segment '%s'"
+ " claims its contents lie outside the segment's contents\n",
+ filename_.c_str(), section.c_str(), segment.c_str());
+}
+
+void Reader::Reporter::MisplacedSymbolTable() {
+ fprintf(stderr, "%s: the LC_SYMTAB load command claims that the symbol"
+ " table's contents are located beyond the end of the file\n",
+ filename_.c_str());
+}
+
+void Reader::Reporter::UnsupportedCPUType(cpu_type_t cpu_type) {
+ fprintf(stderr, "%s: CPU type %d is not supported\n",
+ filename_.c_str(), cpu_type);
+}
+
+bool Reader::Read(const uint8_t *buffer,
+ size_t size,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype) {
+ assert(!buffer_.start);
+ buffer_.start = buffer;
+ buffer_.end = buffer + size;
+ ByteCursor cursor(&buffer_, true);
+ uint32_t magic;
+ if (!(cursor >> magic)) {
+ reporter_->HeaderTruncated();
+ return false;
+ }
+
+ if (expected_cpu_type != CPU_TYPE_ANY) {
+ uint32_t expected_magic;
+ // validate that magic matches the expected cpu type
+ switch (expected_cpu_type) {
+ case CPU_TYPE_I386:
+ expected_magic = MH_CIGAM;
+ break;
+ case CPU_TYPE_POWERPC:
+ expected_magic = MH_MAGIC;
+ break;
+ case CPU_TYPE_X86_64:
+ expected_magic = MH_CIGAM_64;
+ break;
+ case CPU_TYPE_POWERPC64:
+ expected_magic = MH_MAGIC_64;
+ break;
+ default:
+ reporter_->UnsupportedCPUType(expected_cpu_type);
+ return false;
+ }
+
+ if (expected_magic != magic) {
+ reporter_->BadHeader();
+ return false;
+ }
+ }
+
+ // Since the byte cursor is in big-endian mode, a reversed magic number
+ // always indicates a little-endian file, regardless of our own endianness.
+ switch (magic) {
+ case MH_MAGIC: big_endian_ = true; bits_64_ = false; break;
+ case MH_CIGAM: big_endian_ = false; bits_64_ = false; break;
+ case MH_MAGIC_64: big_endian_ = true; bits_64_ = true; break;
+ case MH_CIGAM_64: big_endian_ = false; bits_64_ = true; break;
+ default:
+ reporter_->BadHeader();
+ return false;
+ }
+ cursor.set_big_endian(big_endian_);
+ uint32_t commands_size, reserved;
+ cursor >> cpu_type_ >> cpu_subtype_ >> file_type_ >> load_command_count_
+ >> commands_size >> flags_;
+ if (bits_64_)
+ cursor >> reserved;
+ if (!cursor) {
+ reporter_->HeaderTruncated();
+ return false;
+ }
+
+ if (expected_cpu_type != CPU_TYPE_ANY &&
+ (expected_cpu_type != cpu_type_ ||
+ expected_cpu_subtype != cpu_subtype_)) {
+ reporter_->CPUTypeMismatch(cpu_type_, cpu_subtype_,
+ expected_cpu_type, expected_cpu_subtype);
+ return false;
+ }
+
+ cursor
+ .PointTo(&load_commands_.start, commands_size)
+ .PointTo(&load_commands_.end, 0);
+ if (!cursor) {
+ reporter_->LoadCommandRegionTruncated();
+ return false;
+ }
+
+ return true;
+}
+
+bool Reader::WalkLoadCommands(Reader::LoadCommandHandler *handler) const {
+ ByteCursor list_cursor(&load_commands_, big_endian_);
+
+ for (size_t index = 0; index < load_command_count_; ++index) {
+ // command refers to this load command alone, so that cursor will
+ // refuse to read past the load command's end. But since we haven't
+ // read the size yet, let command initially refer to the entire
+ // remainder of the load command series.
+ ByteBuffer command(list_cursor.here(), list_cursor.Available());
+ ByteCursor cursor(&command, big_endian_);
+
+ // Read the command type and size --- fields common to all commands.
+ uint32_t type, size;
+ if (!(cursor >> type)) {
+ reporter_->LoadCommandsOverrun(load_command_count_, index, 0);
+ return false;
+ }
+ if (!(cursor >> size) || size > command.Size()) {
+ reporter_->LoadCommandsOverrun(load_command_count_, index, type);
+ return false;
+ }
+
+ // Now that we've read the length, restrict command's range to this
+ // load command only.
+ command.end = command.start + size;
+
+ switch (type) {
+ case LC_SEGMENT:
+ case LC_SEGMENT_64: {
+ Segment segment;
+ segment.bits_64 = (type == LC_SEGMENT_64);
+ size_t word_size = segment.bits_64 ? 8 : 4;
+ cursor.CString(&segment.name, 16);
+ size_t file_offset, file_size;
+ cursor
+ .Read(word_size, false, &segment.vmaddr)
+ .Read(word_size, false, &segment.vmsize)
+ .Read(word_size, false, &file_offset)
+ .Read(word_size, false, &file_size);
+ cursor >> segment.maxprot
+ >> segment.initprot
+ >> segment.nsects
+ >> segment.flags;
+ if (!cursor) {
+ reporter_->LoadCommandTooShort(index, type);
+ return false;
+ }
+ if (file_offset > buffer_.Size() ||
+ file_size > buffer_.Size() - file_offset) {
+ reporter_->MisplacedSegmentData(segment.name);
+ return false;
+ }
+ // Mach-O files in .dSYM bundles have the contents of the loaded
+ // segments removed, and their file offsets and file sizes zeroed
+ // out. To help us handle this special case properly, give such
+ // segments' contents NULL starting and ending pointers.
+ if (file_offset == 0 && file_size == 0) {
+ segment.contents.start = segment.contents.end = NULL;
+ } else {
+ segment.contents.start = buffer_.start + file_offset;
+ segment.contents.end = segment.contents.start + file_size;
+ }
+ // The section list occupies the remainder of this load command's space.
+ segment.section_list.start = cursor.here();
+ segment.section_list.end = command.end;
+
+ if (!handler->SegmentCommand(segment))
+ return false;
+ break;
+ }
+
+ case LC_SYMTAB: {
+ uint32_t symoff, nsyms, stroff, strsize;
+ cursor >> symoff >> nsyms >> stroff >> strsize;
+ if (!cursor) {
+ reporter_->LoadCommandTooShort(index, type);
+ return false;
+ }
+ // How big are the entries in the symbol table?
+ // sizeof(struct nlist_64) : sizeof(struct nlist),
+ // but be paranoid about alignment vs. target architecture.
+ size_t symbol_size = bits_64_ ? 16 : 12;
+ // How big is the entire symbol array?
+ size_t symbols_size = nsyms * symbol_size;
+ if (symoff > buffer_.Size() || symbols_size > buffer_.Size() - symoff ||
+ stroff > buffer_.Size() || strsize > buffer_.Size() - stroff) {
+ reporter_->MisplacedSymbolTable();
+ return false;
+ }
+ ByteBuffer entries(buffer_.start + symoff, symbols_size);
+ ByteBuffer names(buffer_.start + stroff, strsize);
+ if (!handler->SymtabCommand(entries, names))
+ return false;
+ break;
+ }
+
+ default: {
+ if (!handler->UnknownCommand(type, command))
+ return false;
+ break;
+ }
+ }
+
+ list_cursor.set_here(command.end);
+ }
+
+ return true;
+}
+
+// A load command handler that looks for a segment of a given name.
+class Reader::SegmentFinder : public LoadCommandHandler {
+ public:
+ // Create a load command handler that looks for a segment named NAME,
+ // and sets SEGMENT to describe it if found.
+ SegmentFinder(const string &name, Segment *segment)
+ : name_(name), segment_(segment), found_() { }
+
+ // Return true if the traversal found the segment, false otherwise.
+ bool found() const { return found_; }
+
+ bool SegmentCommand(const Segment &segment) {
+ if (segment.name == name_) {
+ *segment_ = segment;
+ found_ = true;
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ // The name of the segment our creator is looking for.
+ const string &name_;
+
+ // Where we should store the segment if found. (WEAK)
+ Segment *segment_;
+
+ // True if we found the segment.
+ bool found_;
+};
+
+bool Reader::FindSegment(const string &name, Segment *segment) const {
+ SegmentFinder finder(name, segment);
+ WalkLoadCommands(&finder);
+ return finder.found();
+}
+
+bool Reader::WalkSegmentSections(const Segment &segment,
+ SectionHandler *handler) const {
+ size_t word_size = segment.bits_64 ? 8 : 4;
+ ByteCursor cursor(&segment.section_list, big_endian_);
+
+ for (size_t i = 0; i < segment.nsects; i++) {
+ Section section;
+ section.bits_64 = segment.bits_64;
+ uint64_t size;
+ uint32_t offset, dummy32;
+ cursor
+ .CString(&section.section_name, 16)
+ .CString(&section.segment_name, 16)
+ .Read(word_size, false, &section.address)
+ .Read(word_size, false, &size)
+ >> offset
+ >> section.align
+ >> dummy32
+ >> dummy32
+ >> section.flags
+ >> dummy32
+ >> dummy32;
+ if (section.bits_64)
+ cursor >> dummy32;
+ if (!cursor) {
+ reporter_->SectionsMissing(segment.name);
+ return false;
+ }
+ if ((section.flags & SECTION_TYPE) == S_ZEROFILL) {
+ // Zero-fill sections have a size, but no contents.
+ section.contents.start = section.contents.end = NULL;
+ } else if (segment.contents.start == NULL &&
+ segment.contents.end == NULL) {
+ // Mach-O files in .dSYM bundles have the contents of the loaded
+ // segments removed, and their file offsets and file sizes zeroed
+ // out. However, the sections within those segments still have
+ // non-zero sizes. There's no reason to call MisplacedSectionData in
+ // this case; the caller may just need the section's load
+ // address. But do set the contents' limits to NULL, for safety.
+ section.contents.start = section.contents.end = NULL;
+ } else {
+ if (offset < size_t(segment.contents.start - buffer_.start) ||
+ offset > size_t(segment.contents.end - buffer_.start) ||
+ size > size_t(segment.contents.end - buffer_.start - offset)) {
+ reporter_->MisplacedSectionData(section.section_name,
+ section.segment_name);
+ return false;
+ }
+ section.contents.start = buffer_.start + offset;
+ section.contents.end = section.contents.start + size;
+ }
+ if (!handler->HandleSection(section))
+ return false;
+ }
+ return true;
+}
+
+// A SectionHandler that builds a SectionMap for the sections within a
+// given segment.
+class Reader::SectionMapper: public SectionHandler {
+ public:
+ // Create a SectionHandler that populates MAP with an entry for
+ // each section it is given.
+ SectionMapper(SectionMap *map) : map_(map) { }
+ bool HandleSection(const Section &section) {
+ (*map_)[section.section_name] = section;
+ return true;
+ }
+ private:
+ // The map under construction. (WEAK)
+ SectionMap *map_;
+};
+
+bool Reader::MapSegmentSections(const Segment &segment,
+ SectionMap *section_map) const {
+ section_map->clear();
+ SectionMapper mapper(section_map);
+ return WalkSegmentSections(segment, &mapper);
+}
+
+} // namespace mach_o
+} // namespace google_breakpad
diff --git a/src/common/mac/macho_reader.h b/src/common/mac/macho_reader.h
new file mode 100644
index 00000000..75376482
--- /dev/null
+++ b/src/common/mac/macho_reader.h
@@ -0,0 +1,459 @@
+// -*- mode: C++ -*-
+
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// macho_reader.h: A class for parsing Mach-O files.
+
+#ifndef BREAKPAD_COMMON_MAC_MACHO_READER_H_
+#define BREAKPAD_COMMON_MAC_MACHO_READER_H_
+
+#include <mach-o/loader.h>
+#include <mach-o/fat.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "common/byte_cursor.h"
+
+namespace google_breakpad {
+namespace mach_o {
+
+using std::map;
+using std::string;
+using std::vector;
+
+// The Mac headers don't specify particular types for these groups of
+// constants, but defining them here provides some documentation
+// value. We also give them the same width as the fields in which
+// they appear, which makes them a bit easier to use with ByteCursors.
+typedef uint32_t Magic;
+typedef uint32_t FileType;
+typedef uint32_t FileFlags;
+typedef uint32_t LoadCommandType;
+typedef uint32_t SegmentFlags;
+typedef uint32_t SectionFlags;
+
+// A parser for fat binary files, used to store universal binaries.
+// When applied to a (non-fat) Mach-O file, this behaves as if the
+// file were a fat file containing a single object file.
+class FatReader {
+ public:
+
+ // A class for reporting errors found while parsing fat binary files. The
+ // default definitions of these methods print messages to stderr.
+ class Reporter {
+ public:
+ // Create a reporter that attributes problems to |filename|.
+ explicit Reporter(const string &filename) : filename_(filename) { }
+
+ virtual ~Reporter() { }
+
+ // The data does not begin with a fat binary or Mach-O magic number.
+ // This is a fatal error.
+ virtual void BadHeader();
+
+ // The Mach-O fat binary file ends abruptly, without enough space
+ // to contain an object file it claims is present.
+ virtual void MisplacedObjectFile();
+
+ // The file ends abruptly: either it is not large enough to hold a
+ // complete header, or the header implies that contents are present
+ // beyond the actual end of the file.
+ virtual void TooShort();
+
+ private:
+ // The filename to which the reader should attribute problems.
+ string filename_;
+ };
+
+ // Create a fat binary file reader that uses |reporter| to report problems.
+ explicit FatReader(Reporter *reporter) : reporter_(reporter) { }
+
+ // Read the |size| bytes at |buffer| as a fat binary file. On success,
+ // return true; on failure, report the problem to reporter_ and return
+ // false.
+ //
+ // If the data is a plain Mach-O file, rather than a fat binary file,
+ // then the reader behaves as if it had found a fat binary file whose
+ // single object file is the Mach-O file.
+ bool Read(const uint8_t *buffer, size_t size);
+
+ // Return an array of 'struct fat_arch' structures describing the
+ // object files present in this fat binary file. Set |size| to the
+ // number of elements in the array.
+ //
+ // Assuming Read returned true, the entries are validated: it is
+ // safe to assume that the offsets and sizes in each 'struct
+ // fat_arch' refer to subranges of the bytes passed to Read.
+ //
+ // If there are no object files in this fat binary, then this
+ // function can return NULL.
+ //
+ // The array is owned by this FatReader instance; it will be freed when
+ // this FatReader is destroyed.
+ //
+ // This function returns a C-style array instead of a vector to make it
+ // possible to use the result with OS X functions like NXFindBestFatArch,
+ // so that the symbol dumper will behave consistently with other OS X
+ // utilities that work with fat binaries.
+ const struct fat_arch *object_files(size_t *count) const {
+ *count = object_files_.size();
+ if (object_files_.size() > 0)
+ return &object_files_[0];
+ return NULL;
+ }
+
+ private:
+ // We use this to report problems parsing the file's contents. (WEAK)
+ Reporter *reporter_;
+
+ // The contents of the fat binary or Mach-O file we're parsing. We do not
+ // own the storage it refers to.
+ ByteBuffer buffer_;
+
+ // The magic number of this binary, in host byte order.
+ Magic magic_;
+
+ // The list of object files in this binary.
+ // object_files_.size() == fat_header.nfat_arch
+ vector<struct fat_arch> object_files_;
+};
+
+// A segment in a Mach-O file. All these fields have been byte-swapped as
+// appropriate for use by the executing architecture.
+struct Segment {
+ // The ByteBuffers below point into the bytes passed to the Reader that
+ // created this Segment.
+
+ ByteBuffer section_list; // This segment's section list.
+ ByteBuffer contents; // This segment's contents.
+
+ // This segment's name.
+ string name;
+
+ // The address at which this segment should be loaded in memory. If
+ // bits_64 is false, only the bottom 32 bits of this value are valid.
+ uint64_t vmaddr;
+
+ // The size of this segment when loaded into memory. This may be larger
+ // than contents.Size(), in which case the extra area will be
+ // initialized with zeros. If bits_64 is false, only the bottom 32 bits
+ // of this value are valid.
+ uint64_t vmsize;
+
+ // The maximum and initial VM protection of this segment's contents.
+ uint32_t maxprot;
+ uint32_t initprot;
+
+ // The number of sections in section_list.
+ uint32_t nsects;
+
+ // Flags describing this segment, from SegmentFlags.
+ uint32_t flags;
+
+ // True if this is a 64-bit section; false if it is a 32-bit section.
+ bool bits_64;
+};
+
+// A section in a Mach-O file. All these fields have been byte-swapped as
+// appropriate for use by the executing architecture.
+struct Section {
+ // This section's contents. This points into the bytes passed to the
+ // Reader that created this Section.
+ ByteBuffer contents;
+
+ // This section's name.
+ string section_name; // section[_64].sectname
+ // The name of the segment this section belongs to.
+ string segment_name; // section[_64].segname
+
+ // The address at which this section's contents should be loaded in
+ // memory. If bits_64 is false, only the bottom 32 bits of this value
+ // are valid.
+ uint64_t address;
+
+ // The contents of this section should be loaded into memory at an
+ // address which is a multiple of (two raised to this power).
+ uint32_t align;
+
+ // Flags from SectionFlags describing the section's contents.
+ uint32_t flags;
+
+ // We don't support reading relocations yet.
+
+ // True if this is a 64-bit section; false if it is a 32-bit section.
+ bool bits_64;
+};
+
+// A map from section names to Sections.
+typedef map<string, Section> SectionMap;
+
+// A reader for a Mach-O file.
+//
+// This does not handle fat binaries; see FatReader above. FatReader
+// provides a friendly interface for parsing data that could be either a
+// fat binary or a Mach-O file.
+class Reader {
+ public:
+
+ // A class for reporting errors found while parsing Mach-O files. The
+ // default definitions of these member functions print messages to
+ // stderr.
+ class Reporter {
+ public:
+ // Create a reporter that attributes problems to |filename|.
+ explicit Reporter(const string &filename) : filename_(filename) { }
+ virtual ~Reporter() { }
+
+ // Reporter functions for fatal errors return void; the reader will
+ // definitely return an error to its caller after calling them
+
+ // The data does not begin with a Mach-O magic number, or the magic
+ // number does not match the expected value for the cpu architecture.
+ // This is a fatal error.
+ virtual void BadHeader();
+
+ // The data contained in a Mach-O fat binary (|cpu_type|, |cpu_subtype|)
+ // does not match the expected CPU architecture
+ // (|expected_cpu_type|, |expected_cpu_subtype|).
+ virtual void CPUTypeMismatch(cpu_type_t cpu_type,
+ cpu_subtype_t cpu_subtype,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype);
+
+ // The file ends abruptly: either it is not large enough to hold a
+ // complete header, or the header implies that contents are present
+ // beyond the actual end of the file.
+ virtual void HeaderTruncated();
+
+ // The file's load command region, as given in the Mach-O header, is
+ // too large for the file.
+ virtual void LoadCommandRegionTruncated();
+
+ // The file's Mach-O header claims the file contains |claimed| load
+ // commands, but the I'th load command, of type |type|, extends beyond
+ // the end of the load command region, as given by the Mach-O header.
+ // If |type| is zero, the command's type was unreadable.
+ virtual void LoadCommandsOverrun(size_t claimed, size_t i,
+ LoadCommandType type);
+
+ // The contents of the |i|'th load command, of type |type|, extend beyond
+ // the size given in the load command's header.
+ virtual void LoadCommandTooShort(size_t i, LoadCommandType type);
+
+ // The LC_SEGMENT or LC_SEGMENT_64 load command for the segment named
+ // |name| is too short to hold the sections that its header says it does.
+ // (This more specific than LoadCommandTooShort.)
+ virtual void SectionsMissing(const string &name);
+
+ // The segment named |name| claims that its contents lie beyond the end
+ // of the file.
+ virtual void MisplacedSegmentData(const string &name);
+
+ // The section named |section| in the segment named |segment| claims that
+ // its contents do not lie entirely within the segment.
+ virtual void MisplacedSectionData(const string &section,
+ const string &segment);
+
+ // The LC_SYMTAB command claims that symbol table contents are located
+ // beyond the end of the file.
+ virtual void MisplacedSymbolTable();
+
+ // An attempt was made to read a Mach-O file of the unsupported
+ // CPU architecture |cpu_type|.
+ virtual void UnsupportedCPUType(cpu_type_t cpu_type);
+
+ private:
+ string filename_;
+ };
+
+ // A handler for sections parsed from a segment. The WalkSegmentSections
+ // member function accepts an instance of this class, and applies it to
+ // each section defined in a given segment.
+ class SectionHandler {
+ public:
+ virtual ~SectionHandler() { }
+
+ // Called to report that the segment's section list contains |section|.
+ // This should return true if the iteration should continue, or false
+ // if it should stop.
+ virtual bool HandleSection(const Section &section) = 0;
+ };
+
+ // A handler for the load commands in a Mach-O file.
+ class LoadCommandHandler {
+ public:
+ LoadCommandHandler() { }
+ virtual ~LoadCommandHandler() { }
+
+ // When called from WalkLoadCommands, the following handler functions
+ // should return true if they wish to continue iterating over the load
+ // command list, or false if they wish to stop iterating.
+ //
+ // When called from LoadCommandIterator::Handle or Reader::Handle,
+ // these functions' return values are simply passed through to Handle's
+ // caller.
+ //
+ // The definitions provided by this base class simply return true; the
+ // default is to silently ignore sections whose member functions the
+ // subclass doesn't override.
+
+ // COMMAND is load command we don't recognize. We provide only the
+ // command type and a ByteBuffer enclosing the command's data (If we
+ // cannot parse the command type or its size, we call
+ // reporter_->IncompleteLoadCommand instead.)
+ virtual bool UnknownCommand(LoadCommandType type,
+ const ByteBuffer &contents) {
+ return true;
+ }
+
+ // The load command is LC_SEGMENT or LC_SEGMENT_64, defining a segment
+ // with the properties given in |segment|.
+ virtual bool SegmentCommand(const Segment &segment) {
+ return true;
+ }
+
+ // The load command is LC_SYMTAB. |entries| holds the array of nlist
+ // entries, and |names| holds the strings the entries refer to.
+ virtual bool SymtabCommand(const ByteBuffer &entries,
+ const ByteBuffer &names) {
+ return true;
+ }
+
+ // Add handler functions for more load commands here as needed.
+ };
+
+ // Create a Mach-O file reader that reports problems to |reporter|.
+ explicit Reader(Reporter *reporter)
+ : reporter_(reporter) { }
+
+ // Read the given data as a Mach-O file. The reader retains pointers
+ // into the data passed, so the data should live as long as the reader
+ // does. On success, return true; on failure, return false.
+ //
+ // At most one of these functions should be invoked once on each Reader
+ // instance.
+ bool Read(const uint8_t *buffer,
+ size_t size,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype);
+ bool Read(const ByteBuffer &buffer,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype) {
+ return Read(buffer.start,
+ buffer.Size(),
+ expected_cpu_type,
+ expected_cpu_subtype);
+ }
+
+ // Return this file's characteristics, as found in the Mach-O header.
+ cpu_type_t cpu_type() const { return cpu_type_; }
+ cpu_subtype_t cpu_subtype() const { return cpu_subtype_; }
+ FileType file_type() const { return file_type_; }
+ FileFlags flags() const { return flags_; }
+
+ // Return true if this is a 64-bit Mach-O file, false if it is a 32-bit
+ // Mach-O file.
+ bool bits_64() const { return bits_64_; }
+
+ // Return true if this is a big-endian Mach-O file, false if it is
+ // little-endian.
+ bool big_endian() const { return big_endian_; }
+
+ // Apply |handler| to each load command in this Mach-O file, stopping when
+ // a handler function returns false. If we encounter a malformed load
+ // command, report it via reporter_ and return false. Return true if all
+ // load commands were parseable and all handlers returned true.
+ bool WalkLoadCommands(LoadCommandHandler *handler) const;
+
+ // Set |segment| to describe the segment named |name|, if present. If
+ // found, |segment|'s byte buffers refer to a subregion of the bytes
+ // passed to Read. If we find the section, return true; otherwise,
+ // return false.
+ bool FindSegment(const string &name, Segment *segment) const;
+
+ // Apply |handler| to each section defined in |segment|. If |handler| returns
+ // false, stop iterating and return false. If all calls to |handler| return
+ // true and we reach the end of the section list, return true.
+ bool WalkSegmentSections(const Segment &segment, SectionHandler *handler)
+ const;
+
+ // Clear |section_map| and then populate it with a map of the sections
+ // in |segment|, from section names to Section structures.
+ // Each Section's contents refer to bytes in |segment|'s contents.
+ // On success, return true; if a problem occurs, report it and return false.
+ bool MapSegmentSections(const Segment &segment, SectionMap *section_map)
+ const;
+
+ private:
+ // Used internally.
+ class SegmentFinder;
+ class SectionMapper;
+
+ // We use this to report problems parsing the file's contents. (WEAK)
+ Reporter *reporter_;
+
+ // The contents of the Mach-O file we're parsing. We do not own the
+ // storage it refers to.
+ ByteBuffer buffer_;
+
+ // True if this file is big-endian.
+ bool big_endian_;
+
+ // True if this file is a 64-bit Mach-O file.
+ bool bits_64_;
+
+ // This file's cpu type and subtype.
+ cpu_type_t cpu_type_; // mach_header[_64].cputype
+ cpu_subtype_t cpu_subtype_; // mach_header[_64].cpusubtype
+
+ // This file's type.
+ FileType file_type_; // mach_header[_64].filetype
+
+ // The region of buffer_ occupied by load commands.
+ ByteBuffer load_commands_;
+
+ // The number of load commands in load_commands_.
+ uint32_t load_command_count_; // mach_header[_64].ncmds
+
+ // This file's header flags.
+ FileFlags flags_;
+};
+
+} // namespace mach_o
+} // namespace google_breakpad
+
+#endif // BREAKPAD_COMMON_MAC_MACHO_READER_H_
diff --git a/src/common/mac/macho_reader_unittest.cc b/src/common/mac/macho_reader_unittest.cc
new file mode 100644
index 00000000..4e478d79
--- /dev/null
+++ b/src/common/mac/macho_reader_unittest.cc
@@ -0,0 +1,1898 @@
+// Copyright (c) 2010 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// macho_reader_unittest.cc: Unit tests for google_breakpad::Mach_O::FatReader
+// and google_breakpad::Mach_O::Reader.
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "breakpad_googletest_includes.h"
+#include "common/mac/macho_reader.h"
+#include "common/test_assembler.h"
+
+namespace mach_o = google_breakpad::mach_o;
+namespace test_assembler = google_breakpad::test_assembler;
+
+using mach_o::FatReader;
+using mach_o::FileFlags;
+using mach_o::FileType;
+using mach_o::LoadCommandType;
+using mach_o::Reader;
+using mach_o::Section;
+using mach_o::SectionMap;
+using mach_o::Segment;
+using test_assembler::Endianness;
+using test_assembler::Label;
+using test_assembler::kBigEndian;
+using test_assembler::kLittleEndian;
+using test_assembler::kUnsetEndian;
+using google_breakpad::ByteBuffer;
+using std::map;
+using std::string;
+using std::vector;
+using testing::AllOf;
+using testing::DoAll;
+using testing::Field;
+using testing::InSequence;
+using testing::Matcher;
+using testing::Return;
+using testing::SaveArg;
+using testing::Test;
+using testing::_;
+
+
+// Mock classes for the reader's various reporters and handlers.
+
+class MockFatReaderReporter: public FatReader::Reporter {
+ public:
+ MockFatReaderReporter(const string &filename)
+ : FatReader::Reporter(filename) { }
+ MOCK_METHOD0(BadHeader, void());
+ MOCK_METHOD0(MisplacedObjectFile, void());
+ MOCK_METHOD0(TooShort, void());
+};
+
+class MockReaderReporter: public Reader::Reporter {
+ public:
+ MockReaderReporter(const string &filename) : Reader::Reporter(filename) { }
+ MOCK_METHOD0(BadHeader, void());
+ MOCK_METHOD4(CPUTypeMismatch, void(cpu_type_t cpu_type,
+ cpu_subtype_t cpu_subtype,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype));
+ MOCK_METHOD0(HeaderTruncated, void());
+ MOCK_METHOD0(LoadCommandRegionTruncated, void());
+ MOCK_METHOD3(LoadCommandsOverrun, void(size_t claimed, size_t i,
+ LoadCommandType type));
+ MOCK_METHOD2(LoadCommandTooShort, void(size_t i, LoadCommandType type));
+ MOCK_METHOD1(SectionsMissing, void(const string &name));
+ MOCK_METHOD1(MisplacedSegmentData, void(const string &name));
+ MOCK_METHOD2(MisplacedSectionData, void(const string &section,
+ const string &segment));
+ MOCK_METHOD0(MisplacedSymbolTable, void());
+ MOCK_METHOD1(UnsupportedCPUType, void(cpu_type_t cpu_type));
+};
+
+class MockLoadCommandHandler: public Reader::LoadCommandHandler {
+ public:
+ MOCK_METHOD2(UnknownCommand, bool(LoadCommandType, const ByteBuffer &));
+ MOCK_METHOD1(SegmentCommand, bool(const Segment &));
+ MOCK_METHOD2(SymtabCommand, bool(const ByteBuffer &, const ByteBuffer &));
+};
+
+class MockSectionHandler: public Reader::SectionHandler {
+ public:
+ MOCK_METHOD1(HandleSection, bool(const Section &section));
+};
+
+
+// Tests for mach_o::FatReader.
+
+// Since the effect of these functions is to write to stderr, the
+// results of these tests must be inspected by hand.
+TEST(FatReaderReporter, BadHeader) {
+ FatReader::Reporter reporter("filename");
+ reporter.BadHeader();
+}
+
+TEST(FatReaderReporter, MisplacedObjectFile) {
+ FatReader::Reporter reporter("filename");
+ reporter.MisplacedObjectFile();
+}
+
+TEST(FatReaderReporter, TooShort) {
+ FatReader::Reporter reporter("filename");
+ reporter.TooShort();
+}
+
+TEST(MachOReaderReporter, BadHeader) {
+ Reader::Reporter reporter("filename");
+ reporter.BadHeader();
+}
+
+TEST(MachOReaderReporter, CPUTypeMismatch) {
+ Reader::Reporter reporter("filename");
+ reporter.CPUTypeMismatch(CPU_TYPE_I386, CPU_SUBTYPE_X86_ALL,
+ CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL);
+}
+
+TEST(MachOReaderReporter, HeaderTruncated) {
+ Reader::Reporter reporter("filename");
+ reporter.HeaderTruncated();
+}
+
+TEST(MachOReaderReporter, LoadCommandRegionTruncated) {
+ Reader::Reporter reporter("filename");
+ reporter.LoadCommandRegionTruncated();
+}
+
+TEST(MachOReaderReporter, LoadCommandsOverrun) {
+ Reader::Reporter reporter("filename");
+ reporter.LoadCommandsOverrun(10, 9, LC_DYSYMTAB);
+ reporter.LoadCommandsOverrun(10, 9, 0);
+}
+
+TEST(MachOReaderReporter, LoadCommandTooShort) {
+ Reader::Reporter reporter("filename");
+ reporter.LoadCommandTooShort(11, LC_SYMTAB);
+}
+
+TEST(MachOReaderReporter, SectionsMissing) {
+ Reader::Reporter reporter("filename");
+ reporter.SectionsMissing("segment name");
+}
+
+TEST(MachOReaderReporter, MisplacedSegmentData) {
+ Reader::Reporter reporter("filename");
+ reporter.MisplacedSegmentData("segment name");
+}
+
+TEST(MachOReaderReporter, MisplacedSectionData) {
+ Reader::Reporter reporter("filename");
+ reporter.MisplacedSectionData("section name", "segment name");
+}
+
+TEST(MachOReaderReporter, MisplacedSymbolTable) {
+ Reader::Reporter reporter("filename");
+ reporter.MisplacedSymbolTable();
+}
+
+TEST(MachOReaderReporter, UnsupportedCPUType) {
+ Reader::Reporter reporter("filename");
+ reporter.UnsupportedCPUType(CPU_TYPE_HPPA);
+}
+
+struct FatReaderFixture {
+ FatReaderFixture()
+ : fat(kBigEndian),
+ reporter("reporter filename"),
+ reader(&reporter), object_files(), object_files_size() {
+ EXPECT_CALL(reporter, BadHeader()).Times(0);
+ EXPECT_CALL(reporter, TooShort()).Times(0);
+
+ // here, start, and Mark are file offsets in 'fat'.
+ fat.start() = 0;
+ }
+ // Append a 'fat_arch' entry to 'fat', with the given field values.
+ void AppendFatArch(cpu_type_t type, cpu_subtype_t subtype,
+ Label offset, Label size, uint32_t align) {
+ fat
+ .B32(type)
+ .B32(subtype)
+ .B32(offset)
+ .B32(size)
+ .B32(align);
+ }
+ // Append |n| dummy 'fat_arch' entries to 'fat'. The cpu type and
+ // subtype have unrealistic values.
+ void AppendDummyArchEntries(int n) {
+ for (int i = 0; i < n; i++)
+ AppendFatArch(0xb68ad617, 0x715a0840, 0, 0, 1);
+ }
+ void ReadFat(bool expect_parse_success = true) {
+ ASSERT_TRUE(fat.GetContents(&contents));
+ fat_bytes = reinterpret_cast<const uint8_t *>(contents.data());
+ if (expect_parse_success) {
+ EXPECT_TRUE(reader.Read(fat_bytes, contents.size()));
+ object_files = reader.object_files(&object_files_size);
+ }
+ else
+ EXPECT_FALSE(reader.Read(fat_bytes, contents.size()));
+ }
+ test_assembler::Section fat;
+ MockFatReaderReporter reporter;
+ FatReader reader;
+ string contents;
+ const uint8_t *fat_bytes;
+ const struct fat_arch *object_files;
+ size_t object_files_size;
+};
+
+class FatReaderTest: public FatReaderFixture, public Test { };
+
+TEST_F(FatReaderTest, BadMagic) {
+ EXPECT_CALL(reporter, BadHeader()).Times(1);
+ fat
+ .B32(0xcafed00d) // magic number (incorrect)
+ .B32(10); // number of architectures
+ AppendDummyArchEntries(10);
+ ReadFat(false);
+}
+
+TEST_F(FatReaderTest, HeaderTooShort) {
+ EXPECT_CALL(reporter, TooShort()).Times(1);
+ fat
+ .B32(0xcafebabe); // magic number
+ ReadFat(false);
+}
+
+TEST_F(FatReaderTest, ObjectListTooShort) {
+ EXPECT_CALL(reporter, TooShort()).Times(1);
+ fat
+ .B32(0xcafebabe) // magic number
+ .B32(10); // number of architectures
+ AppendDummyArchEntries(9); // nine dummy architecture entries...
+ fat // and a tenth, missing a byte at the end
+ .B32(0x3d46c8fc) // cpu type
+ .B32(0x8a7bfb01) // cpu subtype
+ .B32(0) // offset
+ .B32(0) // size
+ .Append(3, '*'); // one byte short of a four-byte alignment
+ ReadFat(false);
+}
+
+TEST_F(FatReaderTest, DataTooShort) {
+ EXPECT_CALL(reporter, MisplacedObjectFile()).Times(1);
+ Label arch_data;
+ fat
+ .B32(0xcafebabe) // magic number
+ .B32(1); // number of architectures
+ AppendFatArch(0xb4d4a366, 0x4ba4f525, arch_data, 40, 0);
+ fat
+ .Mark(&arch_data) // file data begins here
+ .Append(30, '*'); // only 30 bytes, not 40 as header claims
+ ReadFat(false);
+}
+
+TEST_F(FatReaderTest, NoObjectFiles) {
+ fat
+ .B32(0xcafebabe) // magic number
+ .B32(0); // number of architectures
+ ReadFat();
+ EXPECT_EQ(0U, object_files_size);
+}
+
+TEST_F(FatReaderTest, OneObjectFile) {
+ Label obj1_offset;
+ fat
+ .B32(0xcafebabe) // magic number
+ .B32(1); // number of architectures
+ // First object file list entry
+ AppendFatArch(0x5e3a6e91, 0x52ccd852, obj1_offset, 0x42, 0x355b15b2);
+ // First object file data
+ fat
+ .Mark(&obj1_offset)
+ .Append(0x42, '*'); // dummy contents
+ ReadFat();
+ ASSERT_EQ(1U, object_files_size);
+ EXPECT_EQ(0x5e3a6e91, object_files[0].cputype);
+ EXPECT_EQ(0x52ccd852, object_files[0].cpusubtype);
+ EXPECT_EQ(obj1_offset.Value(), object_files[0].offset);
+ EXPECT_EQ(0x42U, object_files[0].size);
+ EXPECT_EQ(0x355b15b2U, object_files[0].align);
+}
+
+TEST_F(FatReaderTest, ThreeObjectFiles) {
+ Label obj1, obj2, obj3;
+ fat
+ .B32(0xcafebabe) // magic number
+ .B32(3); // number of architectures
+ // Three object file list entries.
+ AppendFatArch(0x0cb92c30, 0x6a159a71, obj1, 0xfb4, 0x2615dbe8);
+ AppendFatArch(0x0f3f1cbb, 0x6c55e90f, obj2, 0xc31, 0x83af6ffd);
+ AppendFatArch(0x3717276d, 0x10ecdc84, obj3, 0x4b3, 0x035267d7);
+ fat
+ // First object file data
+ .Mark(&obj1)
+ .Append(0xfb4, '*') // dummy contents
+ // Second object file data
+ .Mark(&obj2)
+ .Append(0xc31, '%') // dummy contents
+ // Third object file data
+ .Mark(&obj3)
+ .Append(0x4b3, '^'); // dummy contents
+
+ ReadFat();
+
+ ASSERT_EQ(3U, object_files_size);
+
+ // First object file.
+ EXPECT_EQ(0x0cb92c30, object_files[0].cputype);
+ EXPECT_EQ(0x6a159a71, object_files[0].cpusubtype);
+ EXPECT_EQ(obj1.Value(), object_files[0].offset);
+ EXPECT_EQ(0xfb4U, object_files[0].size);
+ EXPECT_EQ(0x2615dbe8U, object_files[0].align);
+
+ // Second object file.
+ EXPECT_EQ(0x0f3f1cbb, object_files[1].cputype);
+ EXPECT_EQ(0x6c55e90f, object_files[1].cpusubtype);
+ EXPECT_EQ(obj2.Value(), object_files[1].offset);
+ EXPECT_EQ(0xc31U, object_files[1].size);
+ EXPECT_EQ(0x83af6ffdU, object_files[1].align);
+
+ // Third object file.
+ EXPECT_EQ(0x3717276d, object_files[2].cputype);
+ EXPECT_EQ(0x10ecdc84, object_files[2].cpusubtype);
+ EXPECT_EQ(obj3.Value(), object_files[2].offset);
+ EXPECT_EQ(0x4b3U, object_files[2].size);
+ EXPECT_EQ(0x035267d7U, object_files[2].align);
+}
+
+TEST_F(FatReaderTest, BigEndianMachO32) {
+ fat.set_endianness(kBigEndian);
+ fat
+ .D32(0xfeedface) // Mach-O file magic number
+ .D32(0x1a9d0518) // cpu type
+ .D32(0x1b779357) // cpu subtype
+ .D32(0x009df67e) // file type
+ .D32(0) // no load commands
+ .D32(0) // the load commands occupy no bytes
+ .D32(0x21987a99); // flags
+
+ ReadFat();
+
+ // FatReader should treat a Mach-O file as if it were a fat binary file
+ // containing one object file --- the whole thing.
+ ASSERT_EQ(1U, object_files_size);
+ EXPECT_EQ(0x1a9d0518, object_files[0].cputype);
+ EXPECT_EQ(0x1b779357, object_files[0].cpusubtype);
+ EXPECT_EQ(0U, object_files[0].offset);
+ EXPECT_EQ(contents.size(), object_files[0].size);
+}
+
+TEST_F(FatReaderTest, BigEndianMachO64) {
+ fat.set_endianness(kBigEndian);
+ fat
+ .D32(0xfeedfacf) // Mach-O 64-bit file magic number
+ .D32(0x5aff8487) // cpu type
+ .D32(0x4c6a57f7) // cpu subtype
+ .D32(0x4392d2c8) // file type
+ .D32(0) // no load commands
+ .D32(0) // the load commands occupy no bytes
+ .D32(0x1b033eea); // flags
+
+ ReadFat();
+
+ // FatReader should treat a Mach-O file as if it were a fat binary file
+ // containing one object file --- the whole thing.
+ ASSERT_EQ(1U, object_files_size);
+ EXPECT_EQ(0x5aff8487, object_files[0].cputype);
+ EXPECT_EQ(0x4c6a57f7, object_files[0].cpusubtype);
+ EXPECT_EQ(0U, object_files[0].offset);
+ EXPECT_EQ(contents.size(), object_files[0].size);
+}
+
+TEST_F(FatReaderTest, LittleEndianMachO32) {
+ fat.set_endianness(kLittleEndian);
+ fat
+ .D32(0xfeedface) // Mach-O file magic number
+ .D32(0x1a9d0518) // cpu type
+ .D32(0x1b779357) // cpu subtype
+ .D32(0x009df67e) // file type
+ .D32(0) // no load commands
+ .D32(0) // the load commands occupy no bytes
+ .D32(0x21987a99); // flags
+
+ ReadFat();
+
+ // FatReader should treat a Mach-O file as if it were a fat binary file
+ // containing one object file --- the whole thing.
+ ASSERT_EQ(1U, object_files_size);
+ EXPECT_EQ(0x1a9d0518, object_files[0].cputype);
+ EXPECT_EQ(0x1b779357, object_files[0].cpusubtype);
+ EXPECT_EQ(0U, object_files[0].offset);
+ EXPECT_EQ(contents.size(), object_files[0].size);
+}
+
+TEST_F(FatReaderTest, LittleEndianMachO64) {
+ fat.set_endianness(kLittleEndian);
+ fat
+ .D32(0xfeedfacf) // Mach-O 64-bit file magic number
+ .D32(0x5aff8487) // cpu type
+ .D32(0x4c6a57f7) // cpu subtype
+ .D32(0x4392d2c8) // file type
+ .D32(0) // no load commands
+ .D32(0) // the load commands occupy no bytes
+ .D32(0x1b033eea); // flags
+
+ ReadFat();
+
+ // FatReader should treat a Mach-O file as if it were a fat binary file
+ // containing one object file --- the whole thing.
+ ASSERT_EQ(1U, object_files_size);
+ EXPECT_EQ(0x5aff8487, object_files[0].cputype);
+ EXPECT_EQ(0x4c6a57f7, object_files[0].cpusubtype);
+ EXPECT_EQ(0U, object_files[0].offset);
+ EXPECT_EQ(contents.size(), object_files[0].size);
+}
+
+TEST_F(FatReaderTest, IncompleteMach) {
+ fat.set_endianness(kLittleEndian);
+ fat
+ .D32(0xfeedfacf) // Mach-O 64-bit file magic number
+ .D32(0x5aff8487); // cpu type
+ // Truncated!
+
+ EXPECT_CALL(reporter, TooShort()).WillOnce(Return());
+
+ ReadFat(false);
+}
+
+
+// General mach_o::Reader tests.
+
+// Dynamically scoped configuration data.
+class WithConfiguration {
+ public:
+ // Establish the given parameters as the default for SizedSections
+ // created within the dynamic scope of this instance.
+ WithConfiguration(Endianness endianness, size_t word_size)
+ : endianness_(endianness), word_size_(word_size), saved_(current_) {
+ current_ = this;
+ }
+ ~WithConfiguration() { current_ = saved_; }
+ static Endianness endianness() {
+ assert(current_);
+ return current_->endianness_;
+ }
+ static size_t word_size() {
+ assert(current_);
+ return current_->word_size_;
+ }
+
+ private:
+ // The innermost WithConfiguration in whose dynamic scope we are
+ // currently executing.
+ static WithConfiguration *current_;
+
+ // The innermost WithConfiguration whose dynamic scope encloses this
+ // WithConfiguration.
+ Endianness endianness_;
+ size_t word_size_;
+ WithConfiguration *saved_;
+};
+
+WithConfiguration *WithConfiguration::current_ = NULL;
+
+// A test_assembler::Section with a size that we can cite. The start(),
+// Here() and Mark() member functions of a SizedSection always represent
+// offsets within the overall file.
+class SizedSection: public test_assembler::Section {
+ public:
+ // Construct a section of the given endianness and word size.
+ explicit SizedSection(Endianness endianness, size_t word_size)
+ : test_assembler::Section(endianness), word_size_(word_size) {
+ assert(word_size_ == 32 || word_size_ == 64);
+ }
+ SizedSection()
+ : test_assembler::Section(WithConfiguration::endianness()),
+ word_size_(WithConfiguration::word_size()) {
+ assert(word_size_ == 32 || word_size_ == 64);
+ }
+
+ // Access/set this section's word size.
+ size_t word_size() const { return word_size_; }
+ void set_word_size(size_t word_size) {
+ assert(word_size_ == 32 || word_size_ == 64);
+ word_size_ = word_size;
+ }
+
+ // Return a label representing the size this section will have when it
+ // is Placed in some containing section.
+ Label final_size() const { return final_size_; }
+
+ // Append SECTION to the end of this section, and call its Finish member.
+ // Return a reference to this section.
+ SizedSection &Place(SizedSection *section) {
+ assert(section->endianness() == endianness());
+ section->Finish();
+ section->start() = Here();
+ test_assembler::Section::Append(*section);
+ return *this;
+ }
+
+ protected:
+ // Mark this section's contents as complete. For plain SizedSections, we
+ // set SECTION's start to its position in this section, and its final_size
+ // label to its current size. Derived classes can extend this as needed
+ // for their additional semantics.
+ virtual void Finish() {
+ final_size_ = Size();
+ }
+
+ // The word size for this data: either 32 or 64.
+ size_t word_size_;
+
+ private:
+ // This section's final size, set when we are placed in some other
+ // SizedSection.
+ Label final_size_;
+};
+
+// A SizedSection that is loaded into memory at a particular address.
+class LoadedSection: public SizedSection {
+ public:
+ explicit LoadedSection(Label address = Label()) : address_(address) { }
+
+ // Return a label representing this section's address.
+ Label address() const { return address_; }
+
+ // Placing a loaded section within a loaded section sets the relationship
+ // between their addresses.
+ LoadedSection &Place(LoadedSection *section) {
+ section->address() = address() + Size();
+ SizedSection::Place(section);
+ return *this;
+ }
+
+ protected:
+ // The address at which this section's contents will be loaded.
+ Label address_;
+};
+
+// A SizedSection representing a segment load command.
+class SegmentLoadCommand: public SizedSection {
+ public:
+ SegmentLoadCommand() : section_count_(0) { }
+
+ // Append a segment load command header with the given characteristics.
+ // The load command will refer to CONTENTS, which must be Placed in the
+ // file separately, at the desired position. Return a reference to this
+ // section.
+ SegmentLoadCommand &Header(const string &name, const LoadedSection &contents,
+ uint32_t maxprot, uint32_t initprot,
+ uint32_t flags) {
+ assert(contents.word_size() == word_size());
+ D32(word_size() == 32 ? LC_SEGMENT : LC_SEGMENT_64);
+ D32(final_size());
+ AppendCString(name, 16);
+ Append(endianness(), word_size() / 8, contents.address());
+ Append(endianness(), word_size() / 8, vmsize_);
+ Append(endianness(), word_size() / 8, contents.start());
+ Append(endianness(), word_size() / 8, contents.final_size());
+ D32(maxprot);
+ D32(initprot);
+ D32(final_section_count_);
+ D32(flags);
+
+ content_final_size_ = contents.final_size();
+
+ return *this;
+ }
+
+ // Return a label representing the size of this segment when loaded into
+ // memory. If this label is still undefined by the time we place this
+ // segment, it defaults to the final size of the segment's in-file
+ // contents. Return a reference to this load command.
+ Label &vmsize() { return vmsize_; }
+
+ // Add a section entry with the given characteristics to this segment
+ // load command. Return a reference to this. The section entry will refer
+ // to CONTENTS, which must be Placed in the segment's contents
+ // separately, at the desired position.
+ SegmentLoadCommand &AppendSectionEntry(const string &section_name,
+ const string &segment_name,
+ uint32_t alignment, uint32_t flags,
+ const LoadedSection &contents) {
+ AppendCString(section_name, 16);
+ AppendCString(segment_name, 16);
+ Append(endianness(), word_size() / 8, contents.address());
+ Append(endianness(), word_size() / 8, contents.final_size());
+ D32(contents.start());
+ D32(alignment);
+ D32(0); // relocations start
+ D32(0); // relocations size
+ D32(flags);
+ D32(0x93656b95); // reserved1
+ D32(0xc35a2473); // reserved2
+ if (word_size() == 64)
+ D32(0x70284b95); // reserved3
+
+ section_count_++;
+
+ return *this;
+ }
+
+ protected:
+ void Finish() {
+ final_section_count_ = section_count_;
+ if (!vmsize_.IsKnownConstant())
+ vmsize_ = content_final_size_;
+ SizedSection::Finish();
+ }
+
+ private:
+ // The number of sections that have been added to this segment so far.
+ size_t section_count_;
+
+ // A label representing the final number of sections this segment will hold.
+ Label final_section_count_;
+
+ // The size of the contents for this segment present in the file.
+ Label content_final_size_;
+
+ // A label representing the size of this segment when loaded; this can be
+ // larger than the size of its file contents, the difference being
+ // zero-filled. If not set explicitly by calling set_vmsize, this is set
+ // equal to the size of the contents.
+ Label vmsize_;
+};
+
+// A SizedSection holding a list of Mach-O load commands.
+class LoadCommands: public SizedSection {
+ public:
+ LoadCommands() : command_count_(0) { }
+
+ // Return a label representing the final load command count.
+ Label final_command_count() const { return final_command_count_; }
+
+ // Increment the command count; return a reference to this section.
+ LoadCommands &CountCommand() {
+ command_count_++;
+ return *this;
+ }
+
+ // Place COMMAND, containing a load command, at the end of this section.
+ // Return a reference to this section.
+ LoadCommands &Place(SizedSection *section) {
+ SizedSection::Place(section);
+ CountCommand();
+ return *this;
+ }
+
+ protected:
+ // Mark this load command list as complete.
+ void Finish() {
+ SizedSection::Finish();
+ final_command_count_ = command_count_;
+ }
+
+ private:
+ // The number of load commands we have added to this file so far.
+ size_t command_count_;
+
+ // A label representing the final command count.
+ Label final_command_count_;
+};
+
+// A SizedSection holding the contents of a Mach-O file. Within a
+// MachOFile, the start, Here, and Mark members refer to file offsets.
+class MachOFile: public SizedSection {
+ public:
+ MachOFile() {
+ start() = 0;
+ }
+
+ // Create a Mach-O file header using the given characteristics and load
+ // command list. This Places COMMANDS immediately after the header.
+ // Return a reference to this section.
+ MachOFile &Header(LoadCommands *commands,
+ cpu_type_t cpu_type = CPU_TYPE_X86,
+ cpu_subtype_t cpu_subtype = CPU_SUBTYPE_I386_ALL,
+ FileType file_type = MH_EXECUTE,
+ uint32_t file_flags = (MH_TWOLEVEL |
+ MH_DYLDLINK |
+ MH_NOUNDEFS)) {
+ D32(word_size() == 32 ? 0xfeedface : 0xfeedfacf); // magic number
+ D32(cpu_type); // cpu type
+ D32(cpu_subtype); // cpu subtype
+ D32(file_type); // file type
+ D32(commands->final_command_count()); // number of load commands
+ D32(commands->final_size()); // their size in bytes
+ D32(file_flags); // flags
+ if (word_size() == 64)
+ D32(0x55638b90); // reserved
+ Place(commands);
+ return *this;
+ }
+};
+
+
+struct ReaderFixture {
+ ReaderFixture()
+ : reporter("reporter filename"),
+ reader(&reporter) {
+ EXPECT_CALL(reporter, BadHeader()).Times(0);
+ EXPECT_CALL(reporter, CPUTypeMismatch(_, _, _, _)).Times(0);
+ EXPECT_CALL(reporter, HeaderTruncated()).Times(0);
+ EXPECT_CALL(reporter, LoadCommandRegionTruncated()).Times(0);
+ EXPECT_CALL(reporter, LoadCommandsOverrun(_, _, _)).Times(0);
+ EXPECT_CALL(reporter, LoadCommandTooShort(_, _)).Times(0);
+ EXPECT_CALL(reporter, SectionsMissing(_)).Times(0);
+ EXPECT_CALL(reporter, MisplacedSegmentData(_)).Times(0);
+ EXPECT_CALL(reporter, MisplacedSectionData(_, _)).Times(0);
+ EXPECT_CALL(reporter, MisplacedSymbolTable()).Times(0);
+ EXPECT_CALL(reporter, UnsupportedCPUType(_)).Times(0);
+
+ EXPECT_CALL(load_command_handler, UnknownCommand(_, _)).Times(0);
+ EXPECT_CALL(load_command_handler, SegmentCommand(_)).Times(0);
+ }
+
+ void ReadFile(MachOFile *file,
+ bool expect_parse_success,
+ cpu_type_t expected_cpu_type,
+ cpu_subtype_t expected_cpu_subtype) {
+ ASSERT_TRUE(file->GetContents(&file_contents));
+ file_bytes = reinterpret_cast<const uint8_t *>(file_contents.data());
+ if (expect_parse_success) {
+ EXPECT_TRUE(reader.Read(file_bytes,
+ file_contents.size(),
+ expected_cpu_type,
+ expected_cpu_subtype));
+ } else {
+ EXPECT_FALSE(reader.Read(file_bytes,
+ file_contents.size(),
+ expected_cpu_type,
+ expected_cpu_subtype));
+ }
+ }
+
+ string file_contents;
+ const uint8_t *file_bytes;
+ MockReaderReporter reporter;
+ Reader reader;
+ MockLoadCommandHandler load_command_handler;
+ MockSectionHandler section_handler;
+};
+
+class ReaderTest: public ReaderFixture, public Test { };
+
+TEST_F(ReaderTest, BadMagic) {
+ WithConfiguration config(kLittleEndian, 32);
+ const cpu_type_t kCPUType = 0x46b760df;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0x67bdebe1) // Not a proper magic number.
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ EXPECT_CALL(reporter, BadHeader()).WillOnce(Return());
+ ReadFile(&file, false, CPU_TYPE_ANY, kCPUSubType);
+}
+
+TEST_F(ReaderTest, MismatchedMagic) {
+ WithConfiguration config(kLittleEndian, 32);
+ const cpu_type_t kCPUType = CPU_TYPE_I386;
+ const cpu_subtype_t kCPUSubType = CPU_SUBTYPE_X86_ALL;
+ MachOFile file;
+ file
+ .D32(MH_CIGAM) // Right magic, but winds up wrong
+ // due to bitswapping
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ EXPECT_CALL(reporter, BadHeader()).WillOnce(Return());
+ ReadFile(&file, false, kCPUType, kCPUSubType);
+}
+
+TEST_F(ReaderTest, ShortMagic) {
+ WithConfiguration config(kBigEndian, 32);
+ MachOFile file;
+ file
+ .D16(0xfeed); // magic number
+ // truncated!
+ EXPECT_CALL(reporter, HeaderTruncated()).WillOnce(Return());
+ ReadFile(&file, false, CPU_TYPE_ANY, 0);
+}
+
+TEST_F(ReaderTest, ShortHeader) {
+ WithConfiguration config(kBigEndian, 32);
+ const cpu_type_t kCPUType = CPU_TYPE_ANY;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0xfeedface) // magic number
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0); // they occupy no bytes
+ EXPECT_CALL(reporter, HeaderTruncated()).WillOnce(Return());
+ ReadFile(&file, false, CPU_TYPE_ANY, kCPUSubType);
+}
+
+TEST_F(ReaderTest, MismatchedCPU) {
+ WithConfiguration config(kBigEndian, 32);
+ const cpu_type_t kCPUType = CPU_TYPE_I386;
+ const cpu_subtype_t kCPUSubType = CPU_SUBTYPE_X86_ALL;
+ MachOFile file;
+ file
+ .D32(MH_MAGIC) // Right magic for PPC (once bitswapped)
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ EXPECT_CALL(reporter,
+ CPUTypeMismatch(CPU_TYPE_I386, CPU_SUBTYPE_X86_ALL,
+ CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL))
+ .WillOnce(Return());
+ ReadFile(&file, false, CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL);
+}
+
+TEST_F(ReaderTest, LittleEndian32Bit) {
+ WithConfiguration config(kLittleEndian, 32);
+ const cpu_type_t kCPUType = 0x46b760df;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0xfeedface) // magic number
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ ReadFile(&file, true, CPU_TYPE_ANY, kCPUSubType);
+ EXPECT_FALSE(reader.bits_64());
+ EXPECT_FALSE(reader.big_endian());
+ EXPECT_EQ(kCPUType, reader.cpu_type());
+ EXPECT_EQ(kCPUSubType, reader.cpu_subtype());
+ EXPECT_EQ(FileType(0x149fc717), reader.file_type());
+ EXPECT_EQ(FileFlags(0x80e71d64), reader.flags());
+}
+
+TEST_F(ReaderTest, LittleEndian64Bit) {
+ WithConfiguration config(kLittleEndian, 64);
+ const cpu_type_t kCPUType = 0x46b760df;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0xfeedfacf) // magic number
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ ReadFile(&file, true, CPU_TYPE_ANY, kCPUSubType);
+ EXPECT_TRUE(reader.bits_64());
+ EXPECT_FALSE(reader.big_endian());
+ EXPECT_EQ(kCPUType, reader.cpu_type());
+ EXPECT_EQ(kCPUSubType, reader.cpu_subtype());
+ EXPECT_EQ(FileType(0x149fc717), reader.file_type());
+ EXPECT_EQ(FileFlags(0x80e71d64), reader.flags());
+}
+
+TEST_F(ReaderTest, BigEndian32Bit) {
+ WithConfiguration config(kBigEndian, 32);
+ const cpu_type_t kCPUType = 0x46b760df;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0xfeedface) // magic number
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ ReadFile(&file, true, CPU_TYPE_ANY, kCPUSubType);
+ EXPECT_FALSE(reader.bits_64());
+ EXPECT_TRUE(reader.big_endian());
+ EXPECT_EQ(kCPUType, reader.cpu_type());
+ EXPECT_EQ(kCPUSubType, reader.cpu_subtype());
+ EXPECT_EQ(FileType(0x149fc717), reader.file_type());
+ EXPECT_EQ(FileFlags(0x80e71d64), reader.flags());
+}
+
+TEST_F(ReaderTest, BigEndian64Bit) {
+ WithConfiguration config(kBigEndian, 64);
+ const cpu_type_t kCPUType = 0x46b760df;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0xfeedfacf) // magic number
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(0) // no load commands
+ .D32(0) // they occupy no bytes
+ .D32(0x80e71d64) // flags
+ .D32(0); // reserved
+ ReadFile(&file, true, CPU_TYPE_ANY, kCPUSubType);
+ EXPECT_TRUE(reader.bits_64());
+ EXPECT_TRUE(reader.big_endian());
+ EXPECT_EQ(kCPUType, reader.cpu_type());
+ EXPECT_EQ(kCPUSubType, reader.cpu_subtype());
+ EXPECT_EQ(FileType(0x149fc717), reader.file_type());
+ EXPECT_EQ(FileFlags(0x80e71d64), reader.flags());
+}
+
+
+// Load command tests.
+
+class LoadCommand: public ReaderFixture, public Test { };
+
+TEST_F(LoadCommand, RegionTruncated) {
+ WithConfiguration config(kBigEndian, 64);
+ const cpu_type_t kCPUType = 0x46b760df;
+ const cpu_subtype_t kCPUSubType = 0x76a0e7f7;
+ MachOFile file;
+ file
+ .D32(0xfeedfacf) // magic number
+ .D32(kCPUType) // cpu type
+ .D32(kCPUSubType) // cpu subtype
+ .D32(0x149fc717) // file type
+ .D32(1) // one load command
+ .D32(40) // occupying 40 bytes
+ .D32(0x80e71d64) // flags
+ .D32(0) // reserved
+ .Append(20, 0); // load command region, not as long as
+ // Mach-O header promised
+
+ EXPECT_CALL(reporter, LoadCommandRegionTruncated()).WillOnce(Return());
+
+ ReadFile(&file, false, CPU_TYPE_ANY, kCPUSubType);
+}
+
+TEST_F(LoadCommand, None) {
+ WithConfiguration config(kLittleEndian, 32);
+ LoadCommands load_commands;
+ MachOFile file;
+ file.Header(&load_commands);
+
+ ReadFile(&file, true, CPU_TYPE_X86, CPU_SUBTYPE_I386_ALL);
+
+ EXPECT_FALSE(reader.bits_64());
+ EXPECT_FALSE(reader.big_endian());
+ EXPECT_EQ(CPU_TYPE_X86, reader.cpu_type());
+ EXPECT_EQ(CPU_SUBTYPE_I386_ALL, reader.cpu_subtype());
+ EXPECT_EQ(MH_EXECUTE, reader.file_type());
+ EXPECT_EQ(FileFlags(MH_TWOLEVEL |
+ MH_DYLDLINK |
+ MH_NOUNDEFS),
+ FileFlags(reader.flags()));
+
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, Unknown) {
+ WithConfiguration config(kBigEndian, 32);
+ LoadCommands load_commands;
+ load_commands
+ .CountCommand()
+ .D32(0x33293d4a) // unknown load command
+ .D32(40) // total size in bytes
+ .Append(32, '*'); // dummy data
+ MachOFile file;
+ file.Header(&load_commands);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_FALSE(reader.bits_64());
+ EXPECT_TRUE(reader.big_endian());
+ EXPECT_EQ(CPU_TYPE_X86, reader.cpu_type());
+ EXPECT_EQ(CPU_SUBTYPE_I386_ALL, reader.cpu_subtype());
+ EXPECT_EQ(MH_EXECUTE, reader.file_type());
+ EXPECT_EQ(FileFlags(MH_TWOLEVEL |
+ MH_DYLDLINK |
+ MH_NOUNDEFS),
+ reader.flags());
+
+ ByteBuffer expected;
+ expected.start = file_bytes + load_commands.start().Value();
+ expected.end = expected.start + load_commands.final_size().Value();
+ EXPECT_CALL(load_command_handler, UnknownCommand(0x33293d4a,
+ expected))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, TypeIncomplete) {
+ WithConfiguration config(kLittleEndian, 32);
+ LoadCommands load_commands;
+ load_commands
+ .CountCommand()
+ .Append(3, 0); // load command type, incomplete
+
+ MachOFile file;
+ file.Header(&load_commands);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, LoadCommandsOverrun(1, 0, 0))
+ .WillOnce(Return());
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, LengthIncomplete) {
+ WithConfiguration config(kBigEndian, 64);
+ LoadCommands load_commands;
+ load_commands
+ .CountCommand()
+ .D32(LC_SEGMENT); // load command
+ // no length
+ MachOFile file;
+ file.Header(&load_commands);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, LoadCommandsOverrun(1, 0, LC_SEGMENT))
+ .WillOnce(Return());
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, ContentIncomplete) {
+ WithConfiguration config(kLittleEndian, 64);
+ LoadCommands load_commands;
+ load_commands
+ .CountCommand()
+ .D32(LC_SEGMENT) // load command
+ .D32(40) // total size in bytes
+ .Append(28, '*'); // not enough dummy data
+ MachOFile file;
+ file.Header(&load_commands);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, LoadCommandsOverrun(1, 0, LC_SEGMENT))
+ .WillOnce(Return());
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, SegmentBE32) {
+ WithConfiguration config(kBigEndian, 32);
+ LoadedSection segment;
+ segment.address() = 0x1891139c;
+ segment.Append(42, '*'); // segment contents
+ SegmentLoadCommand segment_command;
+ segment_command
+ .Header("froon", segment, 0x94d6dd22, 0x8bdbc319, 0x990a16dd);
+ segment_command.vmsize() = 0xcb76584fU;
+ LoadCommands load_commands;
+ load_commands.Place(&segment_command);
+ MachOFile file;
+ file
+ .Header(&load_commands)
+ .Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_CALL(load_command_handler, SegmentCommand(_))
+ .WillOnce(DoAll(SaveArg<0>(&actual_segment),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ EXPECT_EQ(false, actual_segment.bits_64);
+ EXPECT_EQ("froon", actual_segment.name);
+ EXPECT_EQ(0x1891139cU, actual_segment.vmaddr);
+ EXPECT_EQ(0xcb76584fU, actual_segment.vmsize);
+ EXPECT_EQ(0x94d6dd22U, actual_segment.maxprot);
+ EXPECT_EQ(0x8bdbc319U, actual_segment.initprot);
+ EXPECT_EQ(0x990a16ddU, actual_segment.flags);
+ EXPECT_EQ(0U, actual_segment.nsects);
+ EXPECT_EQ(0U, actual_segment.section_list.Size());
+ EXPECT_EQ(segment.final_size().Value(), actual_segment.contents.Size());
+}
+
+TEST_F(LoadCommand, SegmentLE32) {
+ WithConfiguration config(kLittleEndian, 32);
+ LoadedSection segment;
+ segment.address() = 0x4b877866;
+ segment.Append(42, '*'); // segment contents
+ SegmentLoadCommand segment_command;
+ segment_command
+ .Header("sixteenprecisely", segment,
+ 0x350759ed, 0x6cf5a62e, 0x990a16dd);
+ segment_command.vmsize() = 0xcb76584fU;
+ LoadCommands load_commands;
+ load_commands.Place(&segment_command);
+ MachOFile file;
+ file
+ .Header(&load_commands)
+ .Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_CALL(load_command_handler, SegmentCommand(_))
+ .WillOnce(DoAll(SaveArg<0>(&actual_segment),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ EXPECT_EQ(false, actual_segment.bits_64);
+ EXPECT_EQ("sixteenprecisely", actual_segment.name);
+ EXPECT_EQ(0x4b877866U, actual_segment.vmaddr);
+ EXPECT_EQ(0xcb76584fU, actual_segment.vmsize);
+ EXPECT_EQ(0x350759edU, actual_segment.maxprot);
+ EXPECT_EQ(0x6cf5a62eU, actual_segment.initprot);
+ EXPECT_EQ(0x990a16ddU, actual_segment.flags);
+ EXPECT_EQ(0U, actual_segment.nsects);
+ EXPECT_EQ(0U, actual_segment.section_list.Size());
+ EXPECT_EQ(segment.final_size().Value(), actual_segment.contents.Size());
+}
+
+TEST_F(LoadCommand, SegmentBE64) {
+ WithConfiguration config(kBigEndian, 64);
+ LoadedSection segment;
+ segment.address() = 0x79f484f77009e511ULL;
+ segment.Append(42, '*'); // segment contents
+ SegmentLoadCommand segment_command;
+ segment_command
+ .Header("froon", segment, 0x42b45da5, 0x8bdbc319, 0xb2335220);
+ segment_command.vmsize() = 0x8d92397ce6248abaULL;
+ LoadCommands load_commands;
+ load_commands.Place(&segment_command);
+ MachOFile file;
+ file
+ .Header(&load_commands)
+ .Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_CALL(load_command_handler, SegmentCommand(_))
+ .WillOnce(DoAll(SaveArg<0>(&actual_segment),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ EXPECT_EQ(true, actual_segment.bits_64);
+ EXPECT_EQ("froon", actual_segment.name);
+ EXPECT_EQ(0x79f484f77009e511ULL, actual_segment.vmaddr);
+ EXPECT_EQ(0x8d92397ce6248abaULL, actual_segment.vmsize);
+ EXPECT_EQ(0x42b45da5U, actual_segment.maxprot);
+ EXPECT_EQ(0x8bdbc319U, actual_segment.initprot);
+ EXPECT_EQ(0xb2335220U, actual_segment.flags);
+ EXPECT_EQ(0U, actual_segment.nsects);
+ EXPECT_EQ(0U, actual_segment.section_list.Size());
+ EXPECT_EQ(segment.final_size().Value(), actual_segment.contents.Size());
+}
+
+TEST_F(LoadCommand, SegmentLE64) {
+ WithConfiguration config(kLittleEndian, 64);
+ LoadedSection segment;
+ segment.address() = 0x50c0501dc5922d35ULL;
+ segment.Append(42, '*'); // segment contents
+ SegmentLoadCommand segment_command;
+ segment_command
+ .Header("sixteenprecisely", segment,
+ 0x917c339d, 0xdbc446fa, 0xb650b563);
+ segment_command.vmsize() = 0x84ae73e7c75469bfULL;
+ LoadCommands load_commands;
+ load_commands.Place(&segment_command);
+ MachOFile file;
+ file
+ .Header(&load_commands)
+ .Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_CALL(load_command_handler, SegmentCommand(_))
+ .WillOnce(DoAll(SaveArg<0>(&actual_segment),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ EXPECT_EQ(true, actual_segment.bits_64);
+ EXPECT_EQ("sixteenprecisely", actual_segment.name);
+ EXPECT_EQ(0x50c0501dc5922d35ULL, actual_segment.vmaddr);
+ EXPECT_EQ(0x84ae73e7c75469bfULL, actual_segment.vmsize);
+ EXPECT_EQ(0x917c339dU, actual_segment.maxprot);
+ EXPECT_EQ(0xdbc446faU, actual_segment.initprot);
+ EXPECT_EQ(0xb650b563U, actual_segment.flags);
+ EXPECT_EQ(0U, actual_segment.nsects);
+ EXPECT_EQ(0U, actual_segment.section_list.Size());
+ EXPECT_EQ(segment.final_size().Value(), actual_segment.contents.Size());
+}
+
+TEST_F(LoadCommand, SegmentCommandTruncated) {
+ WithConfiguration config(kBigEndian, 32);
+ LoadedSection segment_contents;
+ segment_contents.Append(20, '*'); // lah di dah
+ SizedSection command;
+ command
+ .D32(LC_SEGMENT) // command type
+ .D32(command.final_size()) // command size
+ .AppendCString("too-short", 16) // segment name
+ .D32(0x9c759211) // vmaddr
+ .D32(segment_contents.final_size()) // vmsize
+ .D32(segment_contents.start()) // file offset
+ .D32(segment_contents.final_size()) // file size
+ .D32(0x56f28446) // max protection
+ .D32(0xe7910dcb) // initial protection
+ .D32(0) // no sections
+ .Append(3, 0); // flags (one byte short!)
+ LoadCommands load_commands;
+ load_commands.Place(&command);
+ MachOFile file;
+ file
+ .Header(&load_commands)
+ .Place(&segment_contents);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, LoadCommandTooShort(0, LC_SEGMENT))
+ .WillOnce(Return());
+
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, SegmentBadContentOffset) {
+ WithConfiguration config(kLittleEndian, 32);
+ // Instead of letting a Place call set the segment's file offset and size,
+ // set them ourselves, to check that the parser catches invalid offsets
+ // instead of handing us bogus pointers.
+ LoadedSection segment;
+ segment.address() = 0x4db5489c;
+ segment.start() = 0x7e189e76; // beyond end of file
+ segment.final_size() = 0x98b9c3ab;
+ SegmentLoadCommand segment_command;
+ segment_command
+ .Header("notmerelyfifteen", segment, 0xcbab25ee, 0x359a20db, 0x68a3933f);
+ LoadCommands load_commands;
+ load_commands.Place(&segment_command);
+ MachOFile file;
+ file.Header(&load_commands);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, MisplacedSegmentData("notmerelyfifteen"))
+ .WillOnce(Return());
+
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(LoadCommand, ThreeLoadCommands) {
+ WithConfiguration config(kBigEndian, 32);
+ LoadedSection seg1, seg2, seg3;
+ SegmentLoadCommand cmd1, cmd2, cmd3;
+
+ seg1.Append(128, '@');
+ seg1.address() = 0xa7f61ef6;
+ cmd1.Header("head", seg1, 0x88bf1cc7, 0x889a26a4, 0xe9b80d87);
+ // Include some dummy data at the end of the load command. Since we
+ // didn't claim to have any sections, the reader should ignore this. But
+ // making sure the commands have different lengths ensures that we're
+ // using the right command's length to advance the LoadCommandIterator.
+ cmd1.Append(128, '!');
+
+ seg2.Append(42, '*');
+ seg2.address() = 0xc70fc909;
+ cmd2.Header("thorax", seg2, 0xde7327f4, 0xfdaf771d, 0x65e74b30);
+ // More dummy data at the end of the load command.
+ cmd2.Append(32, '^');
+
+ seg3.Append(42, '%');
+ seg3.address() = 0x46b3ab05;
+ cmd3.Header("abdomen", seg3, 0x7098b70d, 0x8d8d7728, 0x5131419b);
+ // More dummy data at the end of the load command.
+ cmd3.Append(64, '&');
+
+ LoadCommands load_commands;
+ load_commands.Place(&cmd1).Place(&cmd2).Place(&cmd3);
+
+ MachOFile file;
+ file.Header(&load_commands).Place(&seg1).Place(&seg2).Place(&seg3);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ {
+ InSequence s;
+ EXPECT_CALL(load_command_handler,
+ SegmentCommand(Field(&Segment::name, "head")))
+ .WillOnce(Return(true));
+ EXPECT_CALL(load_command_handler,
+ SegmentCommand(Field(&Segment::name, "thorax")))
+ .WillOnce(Return(true));
+ EXPECT_CALL(load_command_handler,
+ SegmentCommand(Field(&Segment::name, "abdomen")))
+ .WillOnce(Return(true));
+ }
+
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+static inline Matcher<const Section &> MatchSection(
+ Matcher<bool> bits_64,
+ Matcher<const string &> section_name,
+ Matcher<const string &> segment_name,
+ Matcher<uint64_t> address,
+ Matcher<uint32_t> alignment,
+ Matcher<uint32_t> flags,
+ Matcher<const ByteBuffer &> contents) {
+ return AllOf(AllOf(Field(&Section::bits_64, bits_64),
+ Field(&Section::section_name, section_name),
+ Field(&Section::segment_name, segment_name),
+ Field(&Section::address, address)),
+ AllOf(Field(&Section::align, alignment),
+ Field(&Section::flags, flags),
+ Field(&Section::contents, contents)));
+}
+
+static inline Matcher<const Section &> MatchSection(
+ Matcher<bool> bits_64,
+ Matcher<const string &> section_name,
+ Matcher<const string &> segment_name,
+ Matcher<uint64_t> address) {
+ return AllOf(Field(&Section::bits_64, bits_64),
+ Field(&Section::section_name, section_name),
+ Field(&Section::segment_name, segment_name),
+ Field(&Section::address, address));
+}
+
+TEST_F(LoadCommand, OneSegmentTwoSections) {
+ WithConfiguration config(kBigEndian, 64);
+
+ // Create some sections with some data.
+ LoadedSection section1, section2;
+ section1.Append("buddha's hand");
+ section2.Append("kumquat");
+
+ // Create a segment to hold them.
+ LoadedSection segment;
+ segment.address() = 0xe1d0eeec;
+ segment.Place(&section2).Place(&section1);
+
+ SegmentLoadCommand segment_command;
+ segment_command
+ .Header("head", segment, 0x92c9568c, 0xa89f2627, 0x4dc7a1e2)
+ .AppendSectionEntry("mandarin", "kishu", 12, 0x8cd4604bU, section1)
+ .AppendSectionEntry("bergamot", "cara cara", 12, 0x98746efaU, section2);
+
+ LoadCommands commands;
+ commands.Place(&segment_command);
+
+ MachOFile file;
+ file.Header(&commands).Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_CALL(load_command_handler, SegmentCommand(_))
+ .WillOnce(DoAll(SaveArg<0>(&actual_segment),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ {
+ InSequence s;
+ ByteBuffer contents1;
+ contents1.start = file_bytes + section1.start().Value();
+ contents1.end = contents1.start + section1.final_size().Value();
+ EXPECT_EQ("buddha's hand",
+ string(reinterpret_cast<const char *>(contents1.start),
+ contents1.Size()));
+ EXPECT_CALL(section_handler,
+ HandleSection(MatchSection(true, "mandarin", "kishu",
+ section1.address().Value(), 12,
+ 0x8cd4604bU, contents1)))
+ .WillOnce(Return(true));
+
+ ByteBuffer contents2;
+ contents2.start = file_bytes + section2.start().Value();
+ contents2.end = contents2.start + section2.final_size().Value();
+ EXPECT_EQ("kumquat",
+ string(reinterpret_cast<const char *>(contents2.start),
+ contents2.Size()));
+ EXPECT_CALL(section_handler,
+ HandleSection(MatchSection(true, "bergamot", "cara cara",
+ section2.address().Value(), 12,
+ 0x98746efaU, contents2)))
+ .WillOnce(Return(true));
+ }
+
+ EXPECT_TRUE(reader.WalkSegmentSections(actual_segment, &section_handler));
+}
+
+TEST_F(LoadCommand, MisplacedSectionBefore) {
+ WithConfiguration config(kLittleEndian, 64);
+
+ // The segment.
+ LoadedSection segment;
+ segment.address() = 0x696d83cc;
+ segment.Append(10, '0');
+
+ // The contents of the following sections don't matter, because
+ // we're not really going to Place them in segment; we're just going
+ // to set all their labels by hand to get the (impossible)
+ // configurations we want.
+
+ // A section whose starting offset is before that of its section.
+ LoadedSection before;
+ before.Append(10, '1');
+ before.start() = segment.start() - 1;
+ before.address() = segment.address() - 1;
+ before.final_size() = before.Size();
+
+ SegmentLoadCommand command;
+ command
+ .Header("segment", segment, 0x173baa29, 0x8407275d, 0xed8f7057)
+ .AppendSectionEntry("before", "segment", 0, 0x686c6921, before);
+
+ LoadCommands commands;
+ commands.Place(&command);
+
+ MachOFile file;
+ file.Header(&commands).Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_TRUE(reader.FindSegment("segment", &actual_segment));
+
+ EXPECT_CALL(reporter, MisplacedSectionData("before", "segment"))
+ .WillOnce(Return());
+ EXPECT_FALSE(reader.WalkSegmentSections(actual_segment, &section_handler));
+}
+
+TEST_F(LoadCommand, MisplacedSectionAfter) {
+ WithConfiguration config(kLittleEndian, 64);
+
+ // The segment.
+ LoadedSection segment;
+ segment.address() = 0x696d83cc;
+ segment.Append(10, '0');
+
+ // The contents of the following sections don't matter, because
+ // we're not really going to Place them in segment; we're just going
+ // to set all their labels by hand to get the (impossible)
+ // configurations we want.
+
+ // A section whose starting offset is after the end of its section.
+ LoadedSection after;
+ after.Append(10, '2');
+ after.start() = segment.start() + 11;
+ after.address() = segment.address() + 11;
+ after.final_size() = after.Size();
+
+ SegmentLoadCommand command;
+ command
+ .Header("segment", segment, 0x173baa29, 0x8407275d, 0xed8f7057)
+ .AppendSectionEntry("after", "segment", 0, 0x2ee50124, after);
+
+ LoadCommands commands;
+ commands.Place(&command);
+
+ MachOFile file;
+ file.Header(&commands).Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_TRUE(reader.FindSegment("segment", &actual_segment));
+
+ EXPECT_CALL(reporter, MisplacedSectionData("after", "segment"))
+ .WillOnce(Return());
+ EXPECT_FALSE(reader.WalkSegmentSections(actual_segment, &section_handler));
+}
+
+TEST_F(LoadCommand, MisplacedSectionTooBig) {
+ WithConfiguration config(kLittleEndian, 64);
+
+ // The segment.
+ LoadedSection segment;
+ segment.address() = 0x696d83cc;
+ segment.Append(10, '0');
+
+ // The contents of the following sections don't matter, because
+ // we're not really going to Place them in segment; we're just going
+ // to set all their labels by hand to get the (impossible)
+ // configurations we want.
+
+ // A section that extends beyond the end of its section.
+ LoadedSection too_big;
+ too_big.Append(10, '3');
+ too_big.start() = segment.start() + 1;
+ too_big.address() = segment.address() + 1;
+ too_big.final_size() = too_big.Size();
+
+ SegmentLoadCommand command;
+ command
+ .Header("segment", segment, 0x173baa29, 0x8407275d, 0xed8f7057)
+ .AppendSectionEntry("too big", "segment", 0, 0x8b53ae5c, too_big);
+
+ LoadCommands commands;
+ commands.Place(&command);
+
+ MachOFile file;
+ file.Header(&commands).Place(&segment);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_TRUE(reader.FindSegment("segment", &actual_segment));
+
+ EXPECT_CALL(reporter, MisplacedSectionData("too big", "segment"))
+ .WillOnce(Return());
+ EXPECT_FALSE(reader.WalkSegmentSections(actual_segment, &section_handler));
+}
+
+
+// The segments in a .dSYM bundle's Mach-O file have their file offset
+// and size set to zero, but the sections don't. The reader shouldn't
+// report an error in this case.
+TEST_F(LoadCommand, ZappedSegment) {
+ WithConfiguration config(kBigEndian, 32);
+
+ // The segment.
+ LoadedSection segment;
+ segment.address() = 0x696d83cc;
+ segment.start() = 0;
+ segment.final_size() = 0;
+
+ // The section.
+ LoadedSection section;
+ section.address() = segment.address();
+ section.start() = 0;
+ section.final_size() = 1000; // extends beyond its segment
+
+ SegmentLoadCommand command;
+ command
+ .Header("zapped", segment, 0x0861a5cb, 0x68ccff67, 0x0b66255c)
+ .AppendSectionEntry("twitching", "zapped", 0, 0x93b3bd42, section);
+
+ LoadCommands commands;
+ commands.Place(&command);
+
+ MachOFile file;
+ file.Header(&commands);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+ EXPECT_TRUE(reader.FindSegment("zapped", &actual_segment));
+
+ ByteBuffer zapped_extent(NULL, 0);
+ EXPECT_CALL(section_handler,
+ HandleSection(MatchSection(false, "twitching", "zapped",
+ 0x696d83cc, 0, 0x93b3bd42,
+ zapped_extent)))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(reader.WalkSegmentSections(actual_segment, &section_handler));
+}
+
+TEST_F(LoadCommand, MapSegmentSections) {
+ WithConfiguration config(kLittleEndian, 32);
+
+ // Create some sections with some data.
+ LoadedSection section1, section2, section3, section4;
+ section1.Append("buddha's hand");
+ section2.start() = 0; // Section 2 is an S_ZEROFILL section.
+ section2.final_size() = 0;
+ section3.Append("shasta gold");
+ section4.Append("satsuma");
+
+ // Create two segments to hold them.
+ LoadedSection segment1, segment2;
+ segment1.address() = 0x13e6c8a9;
+ segment1.Place(&section3).Place(&section1);
+ segment2.set_word_size(64);
+ segment2.address() = 0x04d462e2;
+ segment2.Place(&section4);
+ section2.address() = segment2.address() + segment2.Size();
+
+ SegmentLoadCommand segment_command1, segment_command2;
+ segment_command1
+ .Header("head", segment1, 0x67d955a6, 0x7a61c13e, 0xe3e50c64)
+ .AppendSectionEntry("mandarin", "head", 12, 0x5bb565d7, section1)
+ .AppendSectionEntry("bergamot", "head", 12, 0x8620de73, section3);
+ segment_command2.set_word_size(64);
+ segment_command2
+ .Header("thorax", segment2, 0x7aab2419, 0xe908007f, 0x17961d33)
+ .AppendSectionEntry("sixteenprecisely", "thorax",
+ 12, S_ZEROFILL, section2)
+ .AppendSectionEntry("cara cara", "thorax", 12, 0xb6c5dd8a, section4);
+
+ LoadCommands commands;
+ commands.Place(&segment_command1).Place(&segment_command2);
+
+ MachOFile file;
+ file.Header(&commands).Place(&segment1).Place(&segment2);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment segment;
+ SectionMap section_map;
+
+ EXPECT_FALSE(reader.FindSegment("smoot", &segment));
+
+ ASSERT_TRUE(reader.FindSegment("thorax", &segment));
+ ASSERT_TRUE(reader.MapSegmentSections(segment, &section_map));
+
+ EXPECT_FALSE(section_map.find("sixteenpreciselyandthensome")
+ != section_map.end());
+ EXPECT_FALSE(section_map.find("mandarin") != section_map.end());
+ ASSERT_TRUE(section_map.find("cara cara") != section_map.end());
+ EXPECT_THAT(section_map["cara cara"],
+ MatchSection(true, "cara cara", "thorax", 0x04d462e2));
+ ASSERT_TRUE(section_map.find("sixteenprecisely")
+ != section_map.end());
+ ByteBuffer sixteenprecisely_contents(NULL, 0);
+ EXPECT_THAT(section_map["sixteenprecisely"],
+ MatchSection(true, "sixteenprecisely", "thorax",
+ 0x04d462e2 + 7, 12, S_ZEROFILL,
+ sixteenprecisely_contents));
+
+ ASSERT_TRUE(reader.FindSegment("head", &segment));
+ ASSERT_TRUE(reader.MapSegmentSections(segment, &section_map));
+
+ ASSERT_TRUE(section_map.find("mandarin") != section_map.end());
+ EXPECT_THAT(section_map["mandarin"],
+ MatchSection(false, "mandarin", "head", 0x13e6c8a9 + 11));
+ ASSERT_TRUE(section_map.find("bergamot") != section_map.end());
+ EXPECT_THAT(section_map["bergamot"],
+ MatchSection(false, "bergamot", "head", 0x13e6c8a9));
+}
+
+TEST_F(LoadCommand, FindSegment) {
+ WithConfiguration config(kBigEndian, 32);
+
+ LoadedSection segment1, segment2, segment3;
+ segment1.address() = 0xb8ae5752;
+ segment1.Append("Some contents!");
+ segment2.address() = 0xd6b0ce83;
+ segment2.Append("Different stuff.");
+ segment3.address() = 0x7374fd2a;
+ segment3.Append("Further materials.");
+
+ SegmentLoadCommand cmd1, cmd2, cmd3;
+ cmd1.Header("first", segment1, 0xfadb6932, 0x175bf529, 0x0de790ad);
+ cmd2.Header("second", segment2, 0xeef716e0, 0xe103a9d7, 0x7d38a8ef);
+ cmd3.Header("third", segment3, 0xe172b39e, 0x86012f07, 0x080ac94d);
+
+ LoadCommands commands;
+ commands.Place(&cmd1).Place(&cmd2).Place(&cmd3);
+
+ MachOFile file;
+ file.Header(&commands).Place(&segment1).Place(&segment2).Place(&segment3);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ Segment actual_segment;
+
+ EXPECT_FALSE(reader.FindSegment("murphy", &actual_segment));
+
+ EXPECT_TRUE(reader.FindSegment("second", &actual_segment));
+ EXPECT_EQ(0xd6b0ce83, actual_segment.vmaddr);
+}
+
+
+// Symtab tests.
+
+// A StringAssembler is a class for generating .stabstr sections to present
+// as input to the STABS parser.
+class StringAssembler: public SizedSection {
+ public:
+ // Add the string S to this StringAssembler, and return the string's
+ // offset within this compilation unit's strings.
+ size_t Add(const string &s) {
+ size_t offset = Size();
+ AppendCString(s);
+ return offset;
+ }
+};
+
+// A SymbolAssembler is a class for generating .stab sections to present as
+// test input for the STABS parser.
+class SymbolAssembler: public SizedSection {
+ public:
+ // Create a SymbolAssembler that uses StringAssembler for its strings.
+ explicit SymbolAssembler(StringAssembler *string_assembler)
+ : string_assembler_(string_assembler),
+ entry_count_(0) { }
+
+ // Append a STAB entry to the end of this section with the given
+ // characteristics. NAME is the offset of this entry's name string within
+ // its compilation unit's portion of the .stabstr section; this can be a
+ // value generated by a StringAssembler. Return a reference to this
+ // SymbolAssembler.
+ SymbolAssembler &Symbol(uint8_t type, uint8_t other, Label descriptor,
+ Label value, Label name) {
+ D32(name);
+ D8(type);
+ D8(other);
+ D16(descriptor);
+ Append(endianness(), word_size_ / 8, value);
+ entry_count_++;
+ return *this;
+ }
+
+ // As above, but automatically add NAME to our StringAssembler.
+ SymbolAssembler &Symbol(uint8_t type, uint8_t other, Label descriptor,
+ Label value, const string &name) {
+ return Symbol(type, other, descriptor, value, string_assembler_->Add(name));
+ }
+
+ private:
+ // The strings for our STABS entries.
+ StringAssembler *string_assembler_;
+
+ // The number of entries in this compilation unit so far.
+ size_t entry_count_;
+};
+
+class Symtab: public ReaderFixture, public Test { };
+
+TEST_F(Symtab, Symtab32) {
+ WithConfiguration config(kLittleEndian, 32);
+
+ StringAssembler strings;
+ SymbolAssembler symbols(&strings);
+ symbols
+ .Symbol(0x52, 0x7c, 0x3470, 0x9bb02e7c, "hrududu")
+ .Symbol(0x50, 0x90, 0x7520, 0x1122525d, "Frith");
+
+ SizedSection symtab_command;
+ symtab_command
+ .D32(LC_SYMTAB) // command
+ .D32(symtab_command.final_size()) // size
+ .D32(symbols.start()) // file offset of symbols
+ .D32(2) // symbol count
+ .D32(strings.start()) // file offset of strings
+ .D32(strings.final_size()); // strings size
+
+ LoadCommands load_commands;
+ load_commands.Place(&symtab_command);
+
+ MachOFile file;
+ file.Header(&load_commands).Place(&symbols).Place(&strings);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ ByteBuffer symbols_found, strings_found;
+ EXPECT_CALL(load_command_handler, SymtabCommand(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&symbols_found),
+ SaveArg<1>(&strings_found),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ EXPECT_EQ(24U, symbols_found.Size());
+ EXPECT_EQ(14U, strings_found.Size());
+}
+
+TEST_F(Symtab, Symtab64) {
+ WithConfiguration config(kBigEndian, 64);
+
+ StringAssembler strings;
+ SymbolAssembler symbols(&strings);
+ symbols
+ .Symbol(0xa7, 0xaf, 0x03af, 0x42f3072c74335181ULL, "foo")
+ .Symbol(0xb0, 0x9a, 0x2aa7, 0x2e2d349b3d5744a0ULL, "bar");
+
+ SizedSection symtab_command;
+ symtab_command
+ .D32(LC_SYMTAB) // command
+ .D32(symtab_command.final_size()) // size
+ .D32(symbols.start()) // file offset of symbols
+ .D32(2) // symbol count
+ .D32(strings.start()) // file offset of strings
+ .D32(strings.final_size()); // strings size
+
+ LoadCommands load_commands;
+ load_commands.Place(&symtab_command);
+
+ MachOFile file;
+ file.Header(&load_commands).Place(&symbols).Place(&strings);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ ByteBuffer symbols_found, strings_found;
+ EXPECT_CALL(load_command_handler, SymtabCommand(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&symbols_found),
+ SaveArg<1>(&strings_found),
+ Return(true)));
+ EXPECT_TRUE(reader.WalkLoadCommands(&load_command_handler));
+
+ EXPECT_EQ(32U, symbols_found.Size());
+ EXPECT_EQ(8U, strings_found.Size());
+}
+
+TEST_F(Symtab, SymtabMisplacedSymbols) {
+ WithConfiguration config(kBigEndian, 32);
+
+ StringAssembler strings;
+ SymbolAssembler symbols(&strings);
+ symbols
+ .Symbol(0xa7, 0xaf, 0x03af, 0x42f3072c74335181ULL, "foo")
+ .Symbol(0xb0, 0x9a, 0x2aa7, 0x2e2d349b3d5744a0ULL, "bar");
+
+ SizedSection symtab_command;
+ symtab_command
+ .D32(LC_SYMTAB) // command
+ .D32(symtab_command.final_size()) // size
+ .D32(symbols.start()) // file offset of symbols
+ .D32(3) // symbol count (too many)
+ .D32(strings.start()) // file offset of strings
+ .D32(strings.final_size()); // strings size
+
+ LoadCommands load_commands;
+ load_commands.Place(&symtab_command);
+
+ MachOFile file;
+ // Put symbols at end, so the excessive length will be noticed.
+ file.Header(&load_commands).Place(&strings).Place(&symbols);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, MisplacedSymbolTable()).Times(1);
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+
+TEST_F(Symtab, SymtabMisplacedStrings) {
+ WithConfiguration config(kLittleEndian, 32);
+
+ StringAssembler strings;
+ SymbolAssembler symbols(&strings);
+ symbols
+ .Symbol(0xa7, 0xaf, 0x03af, 0x42f3072c74335181ULL, "foo")
+ .Symbol(0xb0, 0x9a, 0x2aa7, 0x2e2d349b3d5744a0ULL, "bar");
+
+ SizedSection symtab_command;
+ symtab_command
+ .D32(LC_SYMTAB) // command
+ .D32(symtab_command.final_size()) // size
+ .D32(symbols.start()) // file offset of symbols
+ .D32(2) // symbol count
+ .D32(strings.start()) // file offset of strings
+ .D32(strings.final_size() + 1); // strings size (too long)
+
+ LoadCommands load_commands;
+ load_commands.Place(&symtab_command);
+
+ MachOFile file;
+ // Put strings at end, so the excessive length will be noticed.
+ file.Header(&load_commands).Place(&symbols).Place(&strings);
+
+ ReadFile(&file, true, CPU_TYPE_ANY, 0);
+
+ EXPECT_CALL(reporter, MisplacedSymbolTable()).Times(1);
+ EXPECT_FALSE(reader.WalkLoadCommands(&load_command_handler));
+}
+