| // 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 §ion, |
| 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 §ion)); |
| }; |
| |
| |
| // 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() { |
| 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())); |
| size_t fat_files_count; |
| const SuperFatArch* fat_files = reader.object_files(&fat_files_count); |
| object_files.resize(fat_files_count); |
| for (size_t i = 0; i < fat_files_count; ++i) { |
| EXPECT_TRUE(fat_files[i].ConvertToFatArch(&object_files[i])); |
| } |
| } |
| else |
| EXPECT_FALSE(reader.Read(fat_bytes, contents.size())); |
| } |
| test_assembler::Section fat; |
| MockFatReaderReporter reporter; |
| FatReader reader; |
| string contents; |
| const uint8_t *fat_bytes; |
| vector<struct fat_arch> object_files; |
| }; |
| |
| 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 §ion_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(static_cast<uint32_t>(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(static_cast<uint32_t>(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_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_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(§ion2).Place(§ion1); |
| |
| 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, §ion_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, §ion_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, §ion_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, §ion_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, §ion_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(§ion3).Place(§ion1); |
| segment2.set_word_size(64); |
| segment2.address() = 0x04d462e2; |
| segment2.Place(§ion4); |
| 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, §ion_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, §ion_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)); |
| } |
| |