// 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 // stabs_reader_unittest.cc: Unit tests for google_breakpad::StabsReader. #include #include #include #include #include #include #include #include #include #include #include #include #include "breakpad_googletest_includes.h" #include "common/stabs_reader.h" #include "common/test_assembler.h" #include "common/using_std_string.h" using ::testing::Eq; using ::testing::InSequence; using ::testing::Return; using ::testing::StrEq; using ::testing::Test; using ::testing::_; using google_breakpad::StabsHandler; using google_breakpad::StabsReader; using google_breakpad::test_assembler::Label; using google_breakpad::test_assembler::Section; using google_breakpad::test_assembler::kBigEndian; using google_breakpad::test_assembler::kLittleEndian; using std::map; namespace { // A StringAssembler is a class for generating .stabstr sections to present // as input to the STABS parser. class StringAssembler: public Section { public: StringAssembler() : in_cu_(false) { StartCU(); } // Add the string S to this StringAssembler, and return the string's // offset within this compilation unit's strings. If S has been added // already, this returns the offset of its first instance. size_t Add(const string& s) { map::iterator it = added_.find(s); if (it != added_.end()) return it->second; size_t offset = Size() - cu_start_; AppendCString(s); added_[s] = offset; return offset; } // Start a fresh compilation unit string collection. void StartCU() { // Ignore duplicate calls to StartCU. Our test data don't always call // StartCU at all, meaning that our constructor has to take care of it, // meaning that tests that *do* call StartCU call it twice at the // beginning. This is not worth smoothing out. if (in_cu_) return; added_.clear(); cu_start_ = Size(); // Each compilation unit's strings start with an empty string. AppendCString(""); added_[""] = 0; in_cu_ = true; } // Finish off the current CU's strings. size_t EndCU() { assert(in_cu_); in_cu_ = false; return Size() - cu_start_; } private: // The offset of the start of this compilation unit's strings. size_t cu_start_; // True if we're in a CU. bool in_cu_; // A map from the strings that have been added to this section to // their starting indices within their compilation unit. map added_; }; // A StabsAssembler is a class for generating .stab sections to present as // test input for the STABS parser. class StabsAssembler: public Section { public: // Create a StabsAssembler that uses StringAssembler for its strings. StabsAssembler(StringAssembler* string_assembler) : Section(string_assembler->endianness()), string_assembler_(string_assembler), value_size_(0), entry_count_(0), cu_header_(NULL) { } ~StabsAssembler() { assert(!cu_header_); } // Accessor and setter for value_size_. size_t value_size() const { return value_size_; } StabsAssembler& set_value_size(size_t value_size) { value_size_ = value_size; return *this; } // 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 // StabsAssembler. StabsAssembler& Stab(uint8_t type, uint8_t other, Label descriptor, Label value, Label name) { D32(name); D8(type); D8(other); D16(descriptor); Append(endianness(), value_size_, value); entry_count_++; return *this; } // As above, but automatically add NAME to our StringAssembler. StabsAssembler& Stab(uint8_t type, uint8_t other, Label descriptor, Label value, const string& name) { return Stab(type, other, descriptor, value, string_assembler_->Add(name)); } // Start a compilation unit named NAME, with an N_UNDF symbol to start // it, and its own portion of the string section. Return a reference to // this StabsAssembler. StabsAssembler& StartCU(const string& name) { assert(!cu_header_); cu_header_ = new CUHeader; string_assembler_->StartCU(); entry_count_ = 0; return Stab(N_UNDF, 0, cu_header_->final_entry_count, cu_header_->final_string_size, string_assembler_->Add(name)); } // Close off the current compilation unit. Return a reference to this // StabsAssembler. StabsAssembler& EndCU() { assert(cu_header_); cu_header_->final_entry_count = entry_count_; cu_header_->final_string_size = string_assembler_->EndCU(); delete cu_header_; cu_header_ = NULL; return *this; } private: // Data used in a compilation unit header STAB that we won't know until // we've finished the compilation unit. struct CUHeader { // The final number of entries this compilation unit will hold. Label final_entry_count; // The final size of this compilation unit's strings. Label final_string_size; }; // The strings for our STABS entries. StringAssembler* string_assembler_; // The size of the 'value' field of stabs entries in this section. size_t value_size_; // The number of entries in this compilation unit so far. size_t entry_count_; // Header labels for this compilation unit, if we've started one but not // finished it. CUHeader* cu_header_; }; class MockStabsReaderHandler: public StabsHandler { public: MOCK_METHOD3(StartCompilationUnit, bool(const char*, uint64_t, const char*)); MOCK_METHOD1(EndCompilationUnit, bool(uint64_t)); MOCK_METHOD2(StartFunction, bool(const string&, uint64_t)); MOCK_METHOD1(EndFunction, bool(uint64_t)); MOCK_METHOD3(Line, bool(uint64_t, const char*, int)); MOCK_METHOD2(Extern, bool(const string&, uint64_t)); void Warning(const char* format, ...) { MockWarning(format); } MOCK_METHOD1(MockWarning, void(const char*)); }; struct StabsFixture { StabsFixture() : stabs(&strings), unitized(true) { } // Create a StabsReader to parse the mock stabs data in stabs and // strings, and pass the parsed information to mock_handler. Use the // endianness and value size of stabs to parse the data. If all goes // well, return the result of calling the reader's Process member // function. Otherwise, return false. bool ApplyHandlerToMockStabsData() { string stabs_contents, stabstr_contents; if (!stabs.GetContents(&stabs_contents) || !strings.GetContents(&stabstr_contents)) return false; // Run the parser on the test input, passing whatever we find to HANDLER. StabsReader reader( reinterpret_cast(stabs_contents.data()), stabs_contents.size(), reinterpret_cast(stabstr_contents.data()), stabstr_contents.size(), stabs.endianness() == kBigEndian, stabs.value_size(), unitized, &mock_handler); return reader.Process(); } StringAssembler strings; StabsAssembler stabs; bool unitized; MockStabsReaderHandler mock_handler; }; class Stabs: public StabsFixture, public Test { }; TEST_F(Stabs, MockStabsInput) { stabs.set_endianness(kLittleEndian); stabs.set_value_size(4); stabs .Stab(N_SO, 149, 40232, 0x18a2a72bU, "builddir/") .Stab(N_FUN, 83, 50010, 0x91a5353fU, "not the SO with source file name we expected ") .Stab(N_SO, 165, 24791, 0xfe69d23cU, "") .Stab(N_SO, 184, 34178, 0xca4d883aU, "builddir1/") .Stab(N_SO, 83, 40859, 0xd2fe5df3U, "file1.c") .Stab(N_LSYM, 147, 39565, 0x60d4bb8aU, "not the FUN we're looking for") .Stab(N_FUN, 120, 50271, 0xa049f4b1U, "fun1") .Stab(N_BINCL, 150, 15694, 0xef65c659U, "something to ignore in a FUN body") .Stab(N_SLINE, 147, 4967, 0xd904b3f, "") .Stab(N_SOL, 177, 56135, 0xbd97b1dcU, "header.h") .Stab(N_SLINE, 130, 24610, 0x90f145b, "") .Stab(N_FUN, 45, 32441, 0xbf27cf93U, "fun2:some stabs type info here:to trim from the name") .Stab(N_SLINE, 138, 39002, 0x8148b87, "") .Stab(N_SOL, 60, 49318, 0x1d06e025U, "file1.c") .Stab(N_SLINE, 29, 52163, 0x6eebbb7, "") .Stab(N_SO, 167, 4647, 0xd04b7448U, "") .Stab(N_LSYM, 58, 37837, 0xe6b14d37U, "") .Stab(N_SO, 152, 7810, 0x11759f10U, "file3.c") .Stab(N_SO, 218, 12447, 0x11cfe4b5U, ""); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file1.c"), 0xd2fe5df3U, StrEq("builddir1/"))) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(StrEq("fun1"), 0xa049f4b1U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Line(0xa049f4b1U + 0xd904b3f, StrEq("file1.c"), 4967)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Line(0xa049f4b1U + 0x90f145b, StrEq("header.h"), 24610)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xbf27cf93U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(StrEq("fun2"), 0xbf27cf93U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Line(0xbf27cf93U + 0x8148b87, StrEq("header.h"), 39002)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Line(0xbf27cf93U + 0x6eebbb7, StrEq("file1.c"), 52163)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xd04b7448U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0xd04b7448U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file3.c"), 0x11759f10U, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0x11cfe4b5U)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } TEST_F(Stabs, AbruptCU) { stabs.set_endianness(kBigEndian); stabs.set_value_size(4); stabs.Stab(N_SO, 177, 23446, 0xbf10d5e4, "file2-1.c"); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file2-1.c"), 0xbf10d5e4, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } TEST_F(Stabs, AbruptFunction) { stabs.set_endianness(kLittleEndian); stabs.set_value_size(8); stabs .Stab(N_SO, 218, 26631, 0xb83ddf10U, "file3-1.c") .Stab(N_FUN, 113, 24765, 0xbbd4a145U, "fun3_1"); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file3-1.c"), 0xb83ddf10U, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(StrEq("fun3_1"), 0xbbd4a145U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } TEST_F(Stabs, NoCU) { stabs.set_endianness(kBigEndian); stabs.set_value_size(8); stabs.Stab(N_SO, 161, 25673, 0x8f676e7bU, "build-directory/"); EXPECT_CALL(mock_handler, StartCompilationUnit(_, _, _)) .Times(0); EXPECT_CALL(mock_handler, StartFunction(_, _)) .Times(0); ASSERT_TRUE(ApplyHandlerToMockStabsData()); } TEST_F(Stabs, NoCUEnd) { stabs.set_endianness(kBigEndian); stabs.set_value_size(8); stabs .Stab(N_SO, 116, 58280, 0x2f7493c9U, "file5-1.c") .Stab(N_SO, 224, 23057, 0xf9f1d50fU, "file5-2.c"); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file5-1.c"), 0x2f7493c9U, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file5-2.c"), 0xf9f1d50fU, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } // On systems that store STABS in sections, string offsets are relative to // the beginning of that compilation unit's strings, marked with N_UNDF // symbols; see the comments for StabsReader::StabsReader. TEST_F(Stabs, Unitized) { stabs.set_endianness(kBigEndian); stabs.set_value_size(4); stabs .StartCU("antimony") .Stab(N_SO, 49, 26043, 0x7e259f1aU, "antimony") .Stab(N_FUN, 101, 63253, 0x7fbcccaeU, "arsenic") .Stab(N_SO, 124, 37175, 0x80b0014cU, "") .EndCU() .StartCU("aluminum") .Stab(N_SO, 72, 23084, 0x86756839U, "aluminum") .Stab(N_FUN, 59, 3305, 0xa8e120b0U, "selenium") .Stab(N_SO, 178, 56949, 0xbffff983U, "") .EndCU(); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("antimony"), 0x7e259f1aU, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(Eq("arsenic"), 0x7fbcccaeU)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0x80b0014cU)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0x80b0014cU)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("aluminum"), 0x86756839U, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(Eq("selenium"), 0xa8e120b0U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xbffff983U)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0xbffff983U)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } // On systems that store STABS entries in the real symbol table, the N_UNDF // entries have no special meaning, and shouldn't mess up the string // indices. TEST_F(Stabs, NonUnitized) { stabs.set_endianness(kLittleEndian); stabs.set_value_size(4); unitized = false; stabs .Stab(N_UNDF, 21, 11551, 0x9bad2b2e, "") .Stab(N_UNDF, 21, 11551, 0x9bad2b2e, "") .Stab(N_SO, 71, 45139, 0x11a97352, "Tanzania") .Stab(N_SO, 221, 41976, 0x21a97352, ""); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("Tanzania"), 0x11a97352, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0x21a97352)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } TEST_F(Stabs, FunctionEnd) { stabs.set_endianness(kLittleEndian); stabs.set_value_size(8); stabs .Stab(N_SO, 102, 62362, 0x52a830d644cd6942ULL, "compilation unit") // This function is terminated by the start of the next function. .Stab(N_FUN, 216, 38405, 0xbb5ab70ecdd23bfeULL, "function 1") // This function is terminated by an explicit end-of-function stab, // whose value is a size in bytes. .Stab(N_FUN, 240, 10973, 0xc954de9b8fb3e5e2ULL, "function 2") .Stab(N_FUN, 14, 36749, 0xc1ab, "") // This function is terminated by the end of the compilation unit. .Stab(N_FUN, 143, 64514, 0xdff98c9a35386e1fULL, "function 3") .Stab(N_SO, 164, 60142, 0xfdacb856e78bbf57ULL, ""); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("compilation unit"), 0x52a830d644cd6942ULL, NULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(Eq("function 1"), 0xbb5ab70ecdd23bfeULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xc954de9b8fb3e5e2ULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(Eq("function 2"), 0xc954de9b8fb3e5e2ULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xc954de9b8fb3e5e2ULL + 0xc1ab)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(Eq("function 3"), 0xdff98c9a35386e1fULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xfdacb856e78bbf57ULL)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0xfdacb856e78bbf57ULL)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } // On Mac OS X, SLINE records can appear before the FUN stab to which they // belong, and their values are absolute addresses, not offsets. TEST_F(Stabs, LeadingLine) { stabs.set_endianness(kBigEndian); stabs.set_value_size(4); stabs .Stab(N_SO, 179, 27357, 0x8adabc15, "build directory/") .Stab(N_SO, 52, 53058, 0x4c7e3bf4, "compilation unit") .Stab(N_SOL, 165, 12086, 0x6a797ca3, "source file name") .Stab(N_SLINE, 229, 20015, 0x4cb3d7e0, "") .Stab(N_SLINE, 89, 43802, 0x4cba8b88, "") .Stab(N_FUN, 251, 51639, 0xce1b98fa, "rutabaga") .Stab(N_FUN, 218, 16113, 0x5798, "") .Stab(N_SO, 52, 53058, 0xd4af4415, ""); { InSequence s; EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("compilation unit"), 0x4c7e3bf4, StrEq("build directory/"))) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, StartFunction(Eq("rutabaga"), 0xce1b98fa)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Line(0x4cb3d7e0, StrEq("source file name"), 20015)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Line(0x4cba8b88, StrEq("source file name"), 43802)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndFunction(0xce1b98fa + 0x5798)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, EndCompilationUnit(0xd4af4415)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } #if defined(HAVE_MACH_O_NLIST_H) // These tests have no meaning on non-Mach-O-based systems, as // only Mach-O uses N_SECT to represent public symbols. TEST_F(Stabs, OnePublicSymbol) { stabs.set_endianness(kLittleEndian); stabs.set_value_size(4); const uint32_t kExpectedAddress = 0x9000; const string kExpectedFunctionName("public_function"); stabs .Stab(N_SECT, 1, 0, kExpectedAddress, kExpectedFunctionName); { InSequence s; EXPECT_CALL(mock_handler, Extern(StrEq(kExpectedFunctionName), kExpectedAddress)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } TEST_F(Stabs, TwoPublicSymbols) { stabs.set_endianness(kLittleEndian); stabs.set_value_size(4); const uint32_t kExpectedAddress1 = 0xB0B0B0B0; const string kExpectedFunctionName1("public_function"); const uint32_t kExpectedAddress2 = 0xF0F0F0F0; const string kExpectedFunctionName2("something else"); stabs .Stab(N_SECT, 1, 0, kExpectedAddress1, kExpectedFunctionName1) .Stab(N_SECT, 1, 0, kExpectedAddress2, kExpectedFunctionName2); { InSequence s; EXPECT_CALL(mock_handler, Extern(StrEq(kExpectedFunctionName1), kExpectedAddress1)) .WillOnce(Return(true)); EXPECT_CALL(mock_handler, Extern(StrEq(kExpectedFunctionName2), kExpectedAddress2)) .WillOnce(Return(true)); } ASSERT_TRUE(ApplyHandlerToMockStabsData()); } #endif } // anonymous namespace