diff options
-rw-r--r-- | src/common/linux/module.cc | 20 | ||||
-rw-r--r-- | src/common/linux/module.h | 36 | ||||
-rw-r--r-- | src/common/linux/module_unittest.cc | 286 | ||||
-rw-r--r-- | src/tools/linux/dump_syms/Makefile | 20 |
4 files changed, 350 insertions, 12 deletions
diff --git a/src/common/linux/module.cc b/src/common/linux/module.cc index a4cb3bfa..70b70ab5 100644 --- a/src/common/linux/module.cc +++ b/src/common/linux/module.cc @@ -29,6 +29,7 @@ #include <cerrno> #include <cstring> + #include "common/linux/module.h" namespace google_breakpad { @@ -62,6 +63,11 @@ void Module::AddFunctions(vector<Function *>::iterator begin, functions_.insert(functions_.end(), begin, end); } +void Module::GetFunctions(vector<Function *> *vec, + vector<Function *>::iterator i) { + vec->insert(i, functions_.begin(), functions_.end()); +} + Module::File *Module::FindFile(const string &name) { // A tricky bit here. The key of each map entry needs to be a // pointer to the entry's File's name string. This means that we @@ -90,6 +96,17 @@ Module::File *Module::FindFile(const char *name) { return FindFile(name_string); } +Module::File *Module::FindExistingFile(const string &name) { + FileByNameMap::iterator it = files_.find(&name); + return (it == files_.end()) ? NULL : it->second; +} + +void Module::GetFiles(vector<File *> *vec) { + vec->clear(); + for (FileByNameMap::iterator it = files_.begin(); it != files_.end(); it++) + vec->push_back(it->second); +} + void Module::AssignSourceIds() { // First, give every source file an id of -1. for (FileByNameMap::iterator file_it = files_.begin(); @@ -129,8 +146,9 @@ bool Module::Write(FILE *stream) { name_.c_str())) return ReportError(); - // Write out files. AssignSourceIds(); + + // Write out files. for (FileByNameMap::iterator file_it = files_.begin(); file_it != files_.end(); file_it++) { File *file = file_it->second; diff --git a/src/common/linux/module.h b/src/common/linux/module.h index b91c0f90..89ff06e5 100644 --- a/src/common/linux/module.h +++ b/src/common/linux/module.h @@ -140,9 +140,32 @@ class Module { File *FindFile(const string &name); File *FindFile(const char *name); - // Write this module to STREAM in the breakpad symbol format. - // Return true if all goes well, or false if an error occurs. This - // method writes out: + // If this module has a file named NAME, return a pointer to it. + // Otherwise, return NULL. + File *FindExistingFile(const string &name); + + // Insert pointers to the functions added to this module at I in + // VEC. (Since this is effectively a copy of the function list, this + // is mostly useful for testing; other uses should probably get a + // more appropriate interface.) + void GetFunctions(vector<Function *> *vec, vector<Function *>::iterator i); + + // Clear VEC and fill it with pointers to the Files added to this + // module, sorted by name. (Since this is effectively a copy of the + // function list, this is mostly useful for testing; other uses + // should probably get a more appropriate interface.) + void GetFiles(vector<File *> *vec); + + // Find those files in this module that are actually referred to by + // functions' line number data, and assign them source id numbers. + // Set the source id numbers for all other files --- unused by the + // source line data --- to -1. We do this before writing out the + // symbol file, at which point we omit any unused files. + void AssignSourceIds(); + + // Call AssignSourceIds, and write this module to STREAM in the + // breakpad symbol format. Return true if all goes well, or false if + // an error occurs. This method writes out: // - a header based on the values given to the constructor, // - the source files added via FindFile, and finally // - the functions added via AddFunctions, each with its lines. @@ -152,13 +175,6 @@ class Module { private: - // Find those files in this module that are actually referred to by - // functions' line number data, and assign them source id numbers. - // Set the source id numbers for all other files --- unused by the - // source line data --- to -1. We do this before writing out the - // symbol file, at which point we omit any unused files. - void AssignSourceIds(); - // Report an error that has occurred writing the symbol file, using // errno to find the appropriate cause. Return false. static bool ReportError(); diff --git a/src/common/linux/module_unittest.cc b/src/common/linux/module_unittest.cc new file mode 100644 index 00000000..2520a236 --- /dev/null +++ b/src/common/linux/module_unittest.cc @@ -0,0 +1,286 @@ +// Copyright (c) 2009, 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. + +// module_unittest.cc: Unit tests for google_breakpad::Module + +#include <cerrno> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include <algorithm> +#include <string> + +#include "breakpad_googletest_includes.h" +#include "common/linux/module.h" + +using std::string; +using std::vector; +using google_breakpad::Module; + +// Return a FILE * referring to a temporary file that will be deleted +// automatically when the stream is closed or the program exits. +FILE *checked_tmpfile() { + FILE *f = tmpfile(); + if (!f) { + fprintf(stderr, "error creating temporary file: %s\n", strerror(errno)); + exit(1); + } + return f; +} + +// Read from STREAM until end of file, and return the contents as a +// string. +string checked_read(FILE *stream) { + string contents; + int c; + while ((c = getc(stream)) != EOF) + contents.push_back(c); + if (ferror(stream)) { + fprintf(stderr, "error reading temporary file contents: %s\n", + strerror(errno)); + exit(1); + } + return contents; +} + +// Apply 'fflush' to STREAM, and check for errors. +void checked_fflush(FILE *stream) { + if (fflush(stream) == EOF) { + fprintf(stderr, "error flushing temporary file stream: %s\n", + strerror(errno)); + exit(1); + } +} + +// Apply 'fclose' to STREAM, and check for errors. +void checked_fclose(FILE *stream) { + if (fclose(stream) == EOF) { + fprintf(stderr, "error closing temporary file stream: %s\n", + strerror(errno)); + exit(1); + } +} + +#define MODULE_NAME "name with spaces" +#define MODULE_OS "os-name" +#define MODULE_ARCH "architecture" +#define MODULE_ID "id-string" + +TEST(Write, Header) { + FILE *f = checked_tmpfile(); + Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID); + m.Write(f); + checked_fflush(f); + rewind(f); + string contents = checked_read(f); + checked_fclose(f); + EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n", + contents.c_str()); +} + +TEST(Write, OneLineFunc) { + FILE *f = checked_tmpfile(); + Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID); + + Module::File *file = m.FindFile("file_name.cc"); + Module::Function *function = new(Module::Function); + function->name_ = "function_name"; + function->address_ = 0xe165bf8023b9d9abLL; + function->size_ = 0x1e4bb0eb1cbf5b09LL; + function->parameter_size_ = 0x772beee89114358aLL; + Module::Line line = { 0xe165bf8023b9d9abLL, 0x1e4bb0eb1cbf5b09LL, + file, 67519080 }; + function->lines_.push_back(line); + m.AddFunction(function); + + m.Write(f); + checked_fflush(f); + rewind(f); + string contents = checked_read(f); + checked_fclose(f); + EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n" + "FILE 0 file_name.cc\n" + "FUNC e165bf8023b9d9ab 1e4bb0eb1cbf5b09 772beee89114358a" + " function_name\n" + "e165bf8023b9d9ab 1e4bb0eb1cbf5b09 67519080 0\n", + contents.c_str()); +} + +TEST(Write, RelativeLoadAddress) { + FILE *f = checked_tmpfile(); + Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID); + + m.SetLoadAddress(0x2ab698b0b6407073LL); + + // Some source files. We will expect to see them in lexicographic order. + Module::File *file1 = m.FindFile("filename-b.cc"); + Module::File *file2 = m.FindFile("filename-a.cc"); + + // A function. + Module::Function *function = new(Module::Function); + function->name_ = "A_FLIBBERTIJIBBET::a_will_o_the_wisp(a clown)"; + function->address_ = 0xbec774ea5dd935f3LL; + function->size_ = 0x2922088f98d3f6fcLL; + function->parameter_size_ = 0xe5e9aa008bd5f0d0LL; + + // Some source lines. The module should not sort these. + Module::Line line1 = { 0xbec774ea5dd935f3LL, 0x1c2be6d6c5af2611LL, + file1, 41676901 }; + Module::Line line2 = { 0xdaf35bc123885c04LL, 0xcf621b8d324d0ebLL, + file2, 67519080 }; + function->lines_.push_back(line2); + function->lines_.push_back(line1); + + m.AddFunction(function); + + m.Write(f); + checked_fflush(f); + rewind(f); + string contents = checked_read(f); + checked_fclose(f); + EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n" + "FILE 0 filename-a.cc\n" + "FILE 1 filename-b.cc\n" + "FUNC 9410dc39a798c580 2922088f98d3f6fc e5e9aa008bd5f0d0" + " A_FLIBBERTIJIBBET::a_will_o_the_wisp(a clown)\n" + "b03cc3106d47eb91 cf621b8d324d0eb 67519080 0\n" + "9410dc39a798c580 1c2be6d6c5af2611 41676901 1\n", + contents.c_str()); +} + +TEST(Write, OmitUnusedFiles) { + Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID); + + // Create some source files. + Module::File *file1 = m.FindFile("filename1"); + m.FindFile("filename2"); // not used by any line + Module::File *file3 = m.FindFile("filename3"); + + // Create a function. + Module::Function *function = new(Module::Function); + function->name_ = "function_name"; + function->address_ = 0x9b926d464f0b9384LL; + function->size_ = 0x4f524a4ba795e6a6LL; + function->parameter_size_ = 0xbbe8133a6641c9b7LL; + + // Source files that refer to some files, but not others. + Module::Line line1 = { 0x595fa44ebacc1086LL, 0x1e1e0191b066c5b3LL, + file1, 137850127 }; + Module::Line line2 = { 0x401ce8c8a12d25e3LL, 0x895751c41b8d2ce2LL, + file3, 28113549 }; + function->lines_.push_back(line1); + function->lines_.push_back(line2); + m.AddFunction(function); + + m.AssignSourceIds(); + + vector<Module::File *> vec; + m.GetFiles(&vec); + EXPECT_EQ((size_t) 3, vec.size()); + EXPECT_STREQ("filename1", vec[0]->name_.c_str()); + EXPECT_NE(-1, vec[0]->source_id_); + // Expect filename2 not to be used. + EXPECT_STREQ("filename2", vec[1]->name_.c_str()); + EXPECT_EQ(-1, vec[1]->source_id_); + EXPECT_STREQ("filename3", vec[2]->name_.c_str()); + EXPECT_NE(-1, vec[2]->source_id_); + + FILE *f = checked_tmpfile(); + m.Write(f); + checked_fflush(f); + rewind(f); + string contents = checked_read(f); + checked_fclose(f); + EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n" + "FILE 0 filename1\n" + "FILE 1 filename3\n" + "FUNC 9b926d464f0b9384 4f524a4ba795e6a6 bbe8133a6641c9b7" + " function_name\n" + "595fa44ebacc1086 1e1e0191b066c5b3 137850127 0\n" + "401ce8c8a12d25e3 895751c41b8d2ce2 28113549 1\n", + contents.c_str()); +} + +TEST(Construct, AddFunctions) { + FILE *f = checked_tmpfile(); + Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID); + + // Two functions. + Module::Function *function1 = new(Module::Function); + function1->name_ = "_without_form"; + function1->address_ = 0xd35024aa7ca7da5cLL; + function1->size_ = 0x200b26e605f99071LL; + function1->parameter_size_ = 0xf14ac4fed48c4a99LL; + + Module::Function *function2 = new(Module::Function); + function2->name_ = "_and_void"; + function2->address_ = 0x2987743d0b35b13fLL; + function2->size_ = 0xb369db048deb3010LL; + function2->parameter_size_ = 0x938e556cb5a79988LL; + + // Put them in a vector. + vector<Module::Function *> vec; + vec.push_back(function1); + vec.push_back(function2); + + m.AddFunctions(vec.begin(), vec.end()); + + m.Write(f); + checked_fflush(f); + rewind(f); + string contents = checked_read(f); + checked_fclose(f); + EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n" + "FUNC d35024aa7ca7da5c 200b26e605f99071 f14ac4fed48c4a99" + " _without_form\n" + "FUNC 2987743d0b35b13f b369db048deb3010 938e556cb5a79988" + " _and_void\n", + contents.c_str()); + + // Check that m.GetFunctions returns the functions we expect. + vec.clear(); + m.GetFunctions(&vec, vec.end()); + EXPECT_TRUE(vec.end() != find(vec.begin(), vec.end(), function1)); + EXPECT_TRUE(vec.end() != find(vec.begin(), vec.end(), function2)); + EXPECT_EQ((size_t) 2, vec.size()); +} + +TEST(Construct, UniqueFiles) { + Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID); + Module::File *file1 = m.FindFile("foo"); + Module::File *file2 = m.FindFile(string("bar")); + Module::File *file3 = m.FindFile(string("foo")); + Module::File *file4 = m.FindFile("bar"); + EXPECT_NE(file1, file2); + EXPECT_EQ(file1, file3); + EXPECT_EQ(file2, file4); + EXPECT_EQ(file1, m.FindExistingFile("foo")); + EXPECT_TRUE(m.FindExistingFile("baz") == NULL); +} diff --git a/src/tools/linux/dump_syms/Makefile b/src/tools/linux/dump_syms/Makefile index 239e6f7a..c7cbaf67 100644 --- a/src/tools/linux/dump_syms/Makefile +++ b/src/tools/linux/dump_syms/Makefile @@ -52,7 +52,8 @@ SRC = ../../.. # dependencies using $(CXX). Value accumulated throughout the file. CPP_EXECUTABLES = -# Source files whose coverage we are interested in. Value accumulated +# Add the names of source files whose coverage you'd like 'make +# coverage' to report on to this variable. Value accumulated # throughout the file. COVERAGE_SOURCES = @@ -79,6 +80,7 @@ dump_stabs.o: dump_stabs.cc dump_symbols.o: dump_symbols.cc file_id.o: file_id.cc module.o: module.cc +COVERAGE_SOURCES += module.cc stabs_reader.o: stabs_reader.cc COVERAGE_SOURCES += stabs_reader.cc @@ -132,6 +134,22 @@ clean:: rm -f file_id_unittest +### Unit tests for google_breakpad::Module. +check: check-module_unittest +check-module_unittest: module_unittest +module_unittest: \ + gtest-all.o \ + gtest_main.o \ + module.o \ + module_unittest.o \ + $(empty) +CPP_EXECUTABLES += module_unittest +module_unittest.o: module_unittest.cc +module_unittest.o: override CPPFLAGS += $(GTEST_CPPFLAGS) $(GMOCK_CPPFLAGS) +clean:: + rm -f module_unittest + + ### Generic compilation rules. # Link C++ executables using the C++ compiler; see CPP_EXECUTABLES above. |