From 87855248f1fab83caf002418196a34051d359f2c Mon Sep 17 00:00:00 2001 From: jimblandy Date: Mon, 5 Apr 2010 19:40:17 +0000 Subject: Breakpad symbol dumper: Move Linux dumping classes into src/common. The Linux symbol dumper's classes are reasonably portable, and should be usable for the Mac dumper as well. Move them to src/common, along with their unit tests. Update #include directives and Makefile. a=jimblandy, r=nealsid git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@567 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/common/stabs_reader_unittest.cc | 685 ++++++++++++++++++++++++++++++++++++ 1 file changed, 685 insertions(+) create mode 100644 src/common/stabs_reader_unittest.cc (limited to 'src/common/stabs_reader_unittest.cc') diff --git a/src/common/stabs_reader_unittest.cc b/src/common/stabs_reader_unittest.cc new file mode 100644 index 00000000..a22d9d0e --- /dev/null +++ b/src/common/stabs_reader_unittest.cc @@ -0,0 +1,685 @@ +// 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" + +using std::istream; +using std::istringstream; +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; + +using ::testing::_; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::Sequence; +using ::testing::StrEq; + +using google_breakpad::StabsHandler; +using google_breakpad::StabsReader; + +namespace { + +// Mock stabs file parser +// +// In order to test StabsReader, we parse a human-readable input file +// describing STABS entries into in-memory .stab and .stabstr +// sections, and then pass those to StabsReader to look at. The +// human-readable file is called a "mock stabs file". +// +// Except for compilation unit boundary lines (described below), each +// line of a mock stabs file should have the following form: +// +// TYPE OTHER DESC VALUE NAME +// +// where all data is Latin-1 bytes and fields are separated by single +// space characters, except for NAME, which may contain spaces and +// continues to the end of the line. The fields have the following +// meanings: +// +// - TYPE: the name of the stabs symbol type; like SO or FUN. These are +// the names from /usr/include/bits/stab.def, without the leading N_. +// +// - OTHER, DESC, VALUE: numeric values for the n_other, n_desc, and +// n_value fields of the stab. These can be decimal or hex, +// using C++ notation (10, 0x10) +// +// - NAME: textual data for the entry. STABS packs all kinds of +// interesting data into entries' NAME fields, so calling it a NAME +// is misleading, but that's how it is. For SO, this may be a +// filename; for FUN, this is the function name, plus type data; and +// so on. +// +// A compilation unit boundary line has the form: +// +// cu-boundary FILENAME + +// I don't know if the whole parser/handler pattern is really worth +// the bureaucracy in this case. But just writing it out as +// old-fashioned functions wasn't astonishingly clear either, so it +// seemed worth a try. + +// A handler class for mock stabs data. +class MockStabsHandler { + public: + MockStabsHandler() { } + virtual ~MockStabsHandler() { } + // The mock stabs parser calls this member function for each entry + // it parses, passing it the contents of the entry. If this function + // returns true, the parser continues; if it returns false, the parser + // stops, and its Process member function returns false. + virtual bool Entry(enum __stab_debug_code type, char other, short desc, + unsigned long value, const string &name) { return true; } + // Report a compilation unit boundary whose filename is FILENAME. As + // for the Entry function, this should return true to continue + // parsing, or false to stop processing. + virtual bool CUBoundary(const string &filename) { return true; } + + // Report an error in parsing the mock stabs data. If this returns true, + // the parser continues; if it returns false, the parser stops and + // its Process member function returns false. + virtual bool Error(const char *format, ...) = 0; +}; + +// A class for parsing mock stabs files. +class MockStabsParser { + public: + // Create a parser reading input from STREAM and passing data to HANDLER. + // Use FILENAME when reporting errors. + MockStabsParser(const string &filename, istream *stream, + MockStabsHandler *handler); + // Parse data from the STREAM, invoking HANDLER->Entry for each + // entry we get. Return true if we parsed all the data succesfully, + // or false if we stopped early because Entry returned false, or if + // there were any errors during parsing. + bool Process(); + private: + // A type for maps from stab type names ("SO", "SLINE", etc.) to + // n_type values. + typedef map StabTypeNameTable; + + // Initialize the table mapping STAB type names to n_type values. + void InitializeTypeNames(); + + // Parse LINE, one line of input from a mock stabs file, and pass + // its contents to handler_->Entry and return the boolean value that + // returns. If we encounter an error parsing the line, report it + // using handler->Error. + bool ParseLine(const string &line); + + const string &filename_; + istream *stream_; + MockStabsHandler *handler_; + int line_number_; + StabTypeNameTable type_names_; +}; + +MockStabsParser::MockStabsParser(const string &filename, istream *stream, + MockStabsHandler *handler): + filename_(filename), stream_(stream), handler_(handler), + line_number_(0) { + InitializeTypeNames(); +} + +bool MockStabsParser::Process() { + // Iterate once per line, including a line at EOF without a + // terminating newline. + for(;;) { + string line; + getline(*stream_, line, '\n'); + if (line.empty() && stream_->eof()) + break; + line_number_++; + if (! ParseLine(line)) + return false; + } + return true; +} + +void MockStabsParser::InitializeTypeNames() { + // On GLIBC-based systems, is a file containing a + // call to an unspecified macro __define_stab for each stab type. + // uses it to define the __stab_debug_code enum type. We + // use it here to initialize our mapping from type names to enum + // values. + // + // This isn't portable to non-GLIBC systems. Feel free to just + // hard-code the values if this becomes a problem. +# define __define_stab(name, code, str) type_names_[string(str)] = code; +# include +# undef __define_stab +} + +bool MockStabsParser::ParseLine(const string &line) { + istringstream linestream(line); + // Allow "0x" prefix for hex, and so on. + linestream.unsetf(istringstream::basefield); + // Parse and validate the stabs type. + string typeName; + linestream >> typeName; + if (typeName == "cu-boundary") { + if (linestream.peek() == ' ') + linestream.get(); + string filename; + getline(linestream, filename, '\n'); + return handler_->CUBoundary(filename); + } else { + StabTypeNameTable::const_iterator typeIt = type_names_.find(typeName); + if (typeIt == type_names_.end()) + return handler_->Error("%s:%d: unrecognized stab type: %s\n", + filename_.c_str(), line_number_, typeName.c_str()); + // These are int, not char and unsigned char, to ensure they're parsed + // as decimal numbers, not characters. + int otherInt, descInt; + unsigned long value; + linestream >> otherInt >> descInt >> value; + if (linestream.fail()) + return handler_->Error("%s:%d: malformed mock stabs input line\n", + filename_.c_str(), line_number_); + if (linestream.peek() == ' ') + linestream.get(); + string name; + getline(linestream, name, '\n'); + return handler_->Entry(static_cast<__stab_debug_code>(typeIt->second), + otherInt, descInt, value, name); + } +} + +// A class for constructing .stab sections. +// +// A .stab section is an array of struct nlist entries. These +// entries' n_un.n_strx fields are indices into an accompanying +// .stabstr section. +class StabSection { + public: + StabSection(): used_(0), size_(1) { + entries_ = (struct nlist *) malloc(sizeof(*entries_) * size_); + } + ~StabSection() { free(entries_); } + + // Append a new 'struct nlist' entry to the end of the section, and + // return a pointer to it. This pointer is valid until the next + // call to Append. The caller should initialize the returned entry + // as needed. + struct nlist *Append(); + // Set the first entry's n_desc field to COUNT, and set its n_value field + // to STRING_SIZE. + void SetHeader(short count, unsigned long string_size); + // Set SECTION to the contents of a .stab section holding the + // accumulated list of entries added with Append. + void GetSection(string *section); + // Clear the array, and prepare this StabSection to accumulate a fresh + // set of entries. + void Clear(); + + private: + // The array of stabs entries, + struct nlist *entries_; + // The number of elements of entries_ that are used, and the allocated size + // of the array. + size_t used_, size_; +}; + +struct nlist *StabSection::Append() { + if (used_ == size_) { + size_ *= 2; + entries_ = (struct nlist *) realloc(entries_, sizeof(*entries_) * size_); + } + assert(used_ < size_); + return &entries_[used_++]; +} + +void StabSection::SetHeader(short count, unsigned long string_size) { + assert(used_ >= 1); + entries_[0].n_desc = count; + entries_[0].n_value = string_size; +} + +void StabSection::GetSection(string *section) { + section->assign(reinterpret_cast(entries_), + sizeof(*entries_) * used_); +} + +void StabSection::Clear() { + used_ = 0; + size_ = 1; + entries_ = (struct nlist *) realloc(entries_, sizeof(*entries_) * size_); +} + +// A class for building .stabstr sections. +// +// A .stabstr section is an array of characters containing a bunch of +// null-terminated strings. A string is identified by the index of +// its initial character in the array. The array always starts with a +// null byte, so that an index of zero refers to the empty string. +// +// This implementation also ensures that if two strings are equal, we +// assign them the same indices; most linkers do this, and some +// clients may rely upon it. (Note that this is not quite the same as +// ensuring that a string only appears once in the section; you could +// share space when one string is a suffix of another, but we don't.) +class StabstrSection { + public: + StabstrSection(): next_byte_(1) { string_indices_[""] = 0; } + // Ensure STR is present in the string section, and return its index. + size_t Insert(const string &str); + // Set SECTION to the contents of a .stabstr section in which the + // strings passed to Insert appear at the indices we promised. + void GetSection(string *section); + // Clear the contents of this StabstrSection, and prepare it to + // accumulate a new set of strings. + void Clear(); + private: + // Maps from strings to .stabstr indices and back. + typedef map StringToIndex; + typedef map IndexToString; + + // A map from strings to the indices we've assigned them. + StringToIndex string_indices_; + + // The next unused byte in the section. The next string we add + // will get this index. + size_t next_byte_; +}; + +size_t StabstrSection::Insert(const string &str) { + StringToIndex::iterator it = string_indices_.find(str); + size_t index; + if (it != string_indices_.end()) { + index = it->second; + } else { + // This is the first time we've seen STR; add it to the table. + string_indices_[str] = next_byte_; + index = next_byte_; + next_byte_ += str.size() + 1; + } + return index; +} + +void StabstrSection::GetSection(string *section) { + // First we have to invert the map. + IndexToString byIndex; + for (StringToIndex::const_iterator it = string_indices_.begin(); + it != string_indices_.end(); it++) + byIndex[it->second] = &it->first; + // Now we build the .stabstr section. + section->clear(); + for (IndexToString::const_iterator it = byIndex.begin(); + it != byIndex.end(); it++) { + // Make sure we're actually assigning it the index we claim to be. + assert(it->first == section->size()); + *section += *(it->second); + *section += '\0'; + } +} + +void StabstrSection::Clear() { + string_indices_.clear(); + string_indices_[""] = 0; + next_byte_ = 1; +} + +// A mock stabs parser handler class that builds .stab and .stabstr +// sections. +class StabsSectionsBuilder: public MockStabsHandler { + public: + // Construct a handler that will receive data from a MockStabsParser + // and construct .stab and .stabstr sections. FILENAME should be + // the name of the mock stabs input file; we use it in error + // messages. + StabsSectionsBuilder(const string &filename) + : filename_(filename), error_count_(0), has_header_(false), + entry_count_(0) { } + + // Overridden virtual member functions. + bool Entry(enum __stab_debug_code type, char other, short desc, + unsigned long value, const string &name); + bool CUBoundary(const string &filename); + bool Error(const char *format, ...); + + // Set SECTION to the contents of a .stab or .stabstr section + // reflecting the entries that have been passed to us via Entry. + void GetStab(string *section); + void GetStabstr(string *section); + + private: + // Finish a compilation unit. If there are any entries accumulated in + // stab_ and stabstr_, add them as a new compilation unit to + // finished_cu_stabs_ and finished_cu_stabstr_, and then clear stab_ and + // stabstr_. + void FinishCU(); + + const string &filename_; // input filename, for error messages + int error_count_; // number of errors we've seen so far + + // The following members accumulate the contents of a single compilation + // unit, possibly headed by an N_UNDF stab. + bool has_header_; // true if we have an N_UNDF header + int entry_count_; // the number of entries we've seen + StabSection stab_; // 'struct nlist' entries + StabstrSection stabstr_; // and the strings they love + + // Accumulated .stab and .stabstr content for all compilation units. + string finished_cu_stab_, finished_cu_stabstr_; +}; + +bool StabsSectionsBuilder::Entry(enum __stab_debug_code type, char other, + short desc, unsigned long value, + const string &name) { + struct nlist *entry = stab_.Append(); + entry->n_type = type; + entry->n_other = other; + entry->n_desc = desc; + entry->n_value = value; + entry->n_un.n_strx = stabstr_.Insert(name); + entry_count_++; + return true; +} + +bool StabsSectionsBuilder::CUBoundary(const string &filename) { + FinishCU(); + // Add a header for the compilation unit. + assert(!has_header_); + assert(entry_count_ == 0); + struct nlist *entry = stab_.Append(); + entry->n_type = N_UNDF; + entry->n_other = 0; + entry->n_desc = 0; // will be set to number of entries + entry->n_value = 0; // will be set to size of .stabstr data + entry->n_un.n_strx = stabstr_.Insert(filename); + has_header_ = true; + // The N_UNDF header isn't included in the symbol count, so we + // shouldn't bump entry_count_ here. + return true; +} + +void StabsSectionsBuilder::FinishCU() { + if (entry_count_ > 0) { + // Get the strings first, so we can record their size in the header. + string stabstr; + stabstr_.GetSection(&stabstr); + finished_cu_stabstr_ += stabstr; + + // Initialize our header, if we have one, and extract the .stab data. + if (has_header_) + stab_.SetHeader(entry_count_, stabstr.size()); + string stab; + stab_.GetSection(&stab); + finished_cu_stab_ += stab; + } + + stab_.Clear(); + stabstr_.Clear(); + has_header_ = false; + entry_count_ = 0; +} + +bool StabsSectionsBuilder::Error(const char *format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + error_count_++; + if (error_count_ >= 20) { + fprintf(stderr, + "%s: lots of errors; is this really a mock stabs file?\n", + filename_.c_str()); + return false; + } + return true; +} + +void StabsSectionsBuilder::GetStab(string *section) { + FinishCU(); + *section = finished_cu_stab_; +} + +void StabsSectionsBuilder::GetStabstr(string *section) { + FinishCU(); + *section = finished_cu_stabstr_; +} + +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 std::string &, uint64_t)); + MOCK_METHOD1(EndFunction, bool(uint64_t)); + MOCK_METHOD3(Line, bool(uint64_t, const char *, int)); + void Warning(const char *format, ...) { MockWarning(format); } + MOCK_METHOD1(MockWarning, void(const char *)); +}; + +// Create a StabsReader to parse the mock stabs data in INPUT_FILE, +// passing the parsed information to HANDLER. If all goes well, return +// the result of calling the reader's Process member function. +// Otherwise, return false. INPUT_FILE should be relative to the top +// of the source tree. +static bool ApplyHandlerToMockStabsData(StabsHandler *handler, + const string &input_file) { + string full_input_file + = string(getenv("srcdir") ? getenv("srcdir") : ".") + "/" + input_file; + + // Open the input file. + std::ifstream stream(full_input_file.c_str()); + if (stream.fail()) { + fprintf(stderr, "error opening mock stabs input file %s: %s\n", + full_input_file.c_str(), strerror(errno)); + return false; + } + + // Parse the mock stabs data, and produce stabs sections to use as + // test input to the reader. + StabsSectionsBuilder builder(full_input_file); + MockStabsParser mock_parser(full_input_file, &stream, &builder); + if (!mock_parser.Process()) + return false; + string stab, stabstr; + builder.GetStab(&stab); + builder.GetStabstr(&stabstr); + + // Run the parser on the test input, passing whatever we find to HANDLER. + StabsReader reader( + reinterpret_cast(stab.data()), stab.size(), + reinterpret_cast(stabstr.data()), stabstr.size(), + handler); + return reader.Process(); +} + +TEST(StabsReader, MockStabsInput) { + MockStabsReaderHandler mock_handler; + + { + InSequence s; + + EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file1.c"), + 0x42, StrEq("builddir1/"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, StartFunction(StrEq("fun1"), 0x62)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, Line(0xe4, StrEq("file1.c"), 91)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, Line(0x164, StrEq("header.h"), 111)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndFunction(0x112)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, StartFunction(StrEq("fun2"), 0x112)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, Line(0x234, StrEq("header.h"), 131)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, Line(0x254, StrEq("file1.c"), 151)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndFunction(0x152)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(0x152)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, StartCompilationUnit(StrEq("file3.c"), + 0x182, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(0x192)) + .WillOnce(Return(true)); + } + + ASSERT_TRUE(ApplyHandlerToMockStabsData( + &mock_handler, + "common/testdata/stabs_reader_unittest.input1")); +} + +TEST(StabsReader, AbruptCU) { + MockStabsReaderHandler mock_handler; + + { + InSequence s; + + EXPECT_CALL(mock_handler, + StartCompilationUnit(StrEq("file2-1.c"), 0x12, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) + .WillOnce(Return(true)); + } + + ASSERT_TRUE(ApplyHandlerToMockStabsData( + &mock_handler, + "common/testdata/stabs_reader_unittest.input2")); +} + +TEST(StabsReader, AbruptFunction) { + MockStabsReaderHandler mock_handler; + + { + InSequence s; + + EXPECT_CALL(mock_handler, + StartCompilationUnit(StrEq("file3-1.c"), 0x12, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, StartFunction(StrEq("fun3_1"), 0x22)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndFunction(NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) + .WillOnce(Return(true)); + } + + ASSERT_TRUE(ApplyHandlerToMockStabsData( + &mock_handler, + "common/testdata/stabs_reader_unittest.input3")); +} + +TEST(StabsReader, NoCU) { + MockStabsReaderHandler mock_handler; + + EXPECT_CALL(mock_handler, StartCompilationUnit(_, _, _)) + .Times(0); + EXPECT_CALL(mock_handler, StartFunction(_, _)) + .Times(0); + + ASSERT_TRUE(ApplyHandlerToMockStabsData( + &mock_handler, + "common/testdata/stabs_reader_unittest.input4")); + +} + +TEST(StabsReader, NoCUEnd) { + MockStabsReaderHandler mock_handler; + + { + InSequence s; + + EXPECT_CALL(mock_handler, + StartCompilationUnit(StrEq("file5-1.c"), 0x12, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, + StartCompilationUnit(StrEq("file5-2.c"), 0x22, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) + .WillOnce(Return(true)); + } + + ASSERT_TRUE(ApplyHandlerToMockStabsData( + &mock_handler, + "common/testdata/stabs_reader_unittest.input5")); + +} + +TEST(StabsReader, MultipleCUs) { + MockStabsReaderHandler mock_handler; + + { + InSequence s; + EXPECT_CALL(mock_handler, + StartCompilationUnit(StrEq("antimony"), 0x12, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, StartFunction(Eq("arsenic"), 0x22)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndFunction(0x32)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(0x32)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, + StartCompilationUnit(StrEq("aluminum"), 0x42, NULL)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, StartFunction(Eq("selenium"), 0x52)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndFunction(0x62)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_handler, EndCompilationUnit(0x62)) + .WillOnce(Return(true)); + } + + ASSERT_TRUE(ApplyHandlerToMockStabsData( + &mock_handler, + "common/testdata/stabs_reader_unittest.input6")); +} + +// name duplication + +} // anonymous namespace -- cgit v1.2.1