Breakpad Linux dumper: Make StabsReader independent of endianness and word size.

StabsReader simply applies a reinterpret_cast to treat the stab entry data
as an array of 'struct nlist' structures, making the parser specific on the
host endianness, word size, and alignment rules. On Mac OS X, a single fat
binary file may contain object files of different ABIs, of which the user
chooses one at run time.

This patch changes the parser to read the data using the google_breakpad::
ByteCursor class, which can handle different endiannesses and word sizes.
The StabsReader constructor now takes arguments indicating the endianness
of the data and the size of each entry's value field. The patch changes
src/common/linux/dump_symbols.cc to pass the new argument.

This patch changes the StabsReader unit tests to use the google_breakpad::
TestAssembler classes to generate test data, rather than reading it from a
file. This makes it easy to generate test data in various endiannesses and
word sizes. It also adds tests for the new parser behaviors.

a=jimblandy, r=thestig


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@583 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
jimblandy 2010-05-05 17:09:20 +00:00
parent 6c97801617
commit b0ec96cee2
12 changed files with 478 additions and 627 deletions

View file

@ -123,17 +123,31 @@ static const ElfW(Shdr) *FindSectionByName(const char *name,
return NULL; return NULL;
} }
static bool LoadStabs(const ElfW(Shdr) *stab_section, static bool LoadStabs(const ElfW(Ehdr) *elf_header,
const ElfW(Shdr) *stab_section,
const ElfW(Shdr) *stabstr_section, const ElfW(Shdr) *stabstr_section,
Module *module) { Module *module) {
// Figure out what endianness this file is.
bool big_endian;
if (elf_header->e_ident[EI_DATA] == ELFDATA2LSB)
big_endian = false;
else if (elf_header->e_ident[EI_DATA] == ELFDATA2MSB)
big_endian = true;
else {
fprintf(stderr, "bad data encoding in ELF header: %d\n",
elf_header->e_ident[EI_DATA]);
return false;
}
// A callback object to handle data from the STABS reader. // A callback object to handle data from the STABS reader.
DumpStabsHandler handler(module); DumpStabsHandler handler(module);
// Find the addresses of the STABS data, and create a STABS reader object. // Find the addresses of the STABS data, and create a STABS reader object.
// On Linux, STABS entries always have 32-bit values, regardless of the
// address size of the architecture whose code they're describing.
uint8_t *stabs = reinterpret_cast<uint8_t *>(stab_section->sh_offset); uint8_t *stabs = reinterpret_cast<uint8_t *>(stab_section->sh_offset);
uint8_t *stabstr = reinterpret_cast<uint8_t *>(stabstr_section->sh_offset); uint8_t *stabstr = reinterpret_cast<uint8_t *>(stabstr_section->sh_offset);
google_breakpad::StabsReader reader(stabs, stab_section->sh_size, google_breakpad::StabsReader reader(stabs, stab_section->sh_size,
stabstr, stabstr_section->sh_size, stabstr, stabstr_section->sh_size,
&handler); big_endian, 4, &handler);
// Read the STABS data, and do post-processing. // Read the STABS data, and do post-processing.
if (!reader.Process()) if (!reader.Process())
return false; return false;
@ -330,7 +344,7 @@ static bool LoadSymbols(const std::string &obj_file, ElfW(Ehdr) *elf_header,
const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections; const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections;
if (stabstr_section) { if (stabstr_section) {
found_debug_info_section = true; found_debug_info_section = true;
if (!LoadStabs(stab_section, stabstr_section, module)) if (!LoadStabs(elf_header, stab_section, stabstr_section, module))
fprintf(stderr, "%s: \".stab\" section found, but failed to load STABS" fprintf(stderr, "%s: \".stab\" section found, but failed to load STABS"
" debugging information\n", obj_file.c_str()); " debugging information\n", obj_file.c_str());
} }

View file

@ -31,48 +31,64 @@
// This file implements the google_breakpad::StabsReader class. // This file implements the google_breakpad::StabsReader class.
// See stabs_reader.h. // See stabs_reader.h.
#include <a.out.h>
#include <stab.h>
#include <cstring>
#include <cassert>
#include "common/stabs_reader.h" #include "common/stabs_reader.h"
#include <assert.h>
#include <stab.h>
#include <string.h>
namespace google_breakpad { namespace google_breakpad {
StabsReader::StabsReader(const uint8_t *stab, size_t stab_size, StabsReader::EntryIterator::EntryIterator(const ByteBuffer *buffer,
const uint8_t *stabstr, size_t stabstr_size, bool big_endian, size_t value_size)
StabsHandler *handler) : : value_size_(value_size), cursor_(buffer, big_endian) {
stabstr_(stabstr), // Actually, we could handle weird sizes just fine, but they're
stabstr_size_(stabstr_size), // probably mistakes --- expressed in bits, say.
handler_(handler), assert(value_size == 4 || value_size == 8);
string_offset_(0), entry_.index = 0;
next_cu_string_offset_(0), Fetch();
symbol_(NULL),
current_source_file_(NULL) {
symbols_ = reinterpret_cast<const struct nlist *>(stab);
symbols_end_ = symbols_ + (stab_size / sizeof (*symbols_));
} }
void StabsReader::EntryIterator::Fetch() {
cursor_
.Read(4, false, &entry_.name_offset)
.Read(1, false, &entry_.type)
.Read(1, false, &entry_.other)
.Read(2, false, &entry_.descriptor)
.Read(value_size_, false, &entry_.value);
entry_.at_end = !cursor_;
}
StabsReader::StabsReader(const uint8_t *stab, size_t stab_size,
const uint8_t *stabstr, size_t stabstr_size,
bool big_endian, size_t value_size,
StabsHandler *handler)
: entries_(stab, stab_size),
strings_(stabstr, stabstr_size),
iterator_(&entries_, big_endian, value_size),
handler_(handler),
string_offset_(0),
next_cu_string_offset_(0),
current_source_file_(NULL) { }
const char *StabsReader::SymbolString() { const char *StabsReader::SymbolString() {
ptrdiff_t offset = string_offset_ + symbol_->n_un.n_strx; ptrdiff_t offset = string_offset_ + iterator_->name_offset;
if (offset < 0 || (size_t) offset >= stabstr_size_) { if (offset < 0 || (size_t) offset >= strings_.Size()) {
handler_->Warning("symbol %d: name offset outside the string section\n", handler_->Warning("symbol %d: name offset outside the string section\n",
symbol_ - symbols_); iterator_->index);
// Return our null string, to keep our promise about all names being // Return our null string, to keep our promise about all names being
// taken from the string section. // taken from the string section.
offset = 0; offset = 0;
} }
return reinterpret_cast<const char *>(stabstr_ + offset); return reinterpret_cast<const char *>(strings_.start + offset);
} }
bool StabsReader::Process() { bool StabsReader::Process() {
symbol_ = symbols_; while (!iterator_->at_end) {
while (symbol_ < symbols_end_) { if (iterator_->type == N_SO) {
if (symbol_->n_type == N_SO) {
if (! ProcessCompilationUnit()) if (! ProcessCompilationUnit())
return false; return false;
} else if (symbol_->n_type == N_UNDF) { } else if (iterator_->type == N_UNDF) {
// At the head of each compilation unit's entries there is an // At the head of each compilation unit's entries there is an
// N_UNDF stab giving the number of symbols in the compilation // N_UNDF stab giving the number of symbols in the compilation
// unit, and the number of bytes that compilation unit's strings // unit, and the number of bytes that compilation unit's strings
@ -85,16 +101,16 @@ bool StabsReader::Process() {
// beginning. However, other linkers, like Gold, do not perform // beginning. However, other linkers, like Gold, do not perform
// this optimization. // this optimization.
string_offset_ = next_cu_string_offset_; string_offset_ = next_cu_string_offset_;
next_cu_string_offset_ = SymbolValue(); next_cu_string_offset_ = iterator_->value;
symbol_++; ++iterator_;
} else } else
symbol_++; ++iterator_;
} }
return true; return true;
} }
bool StabsReader::ProcessCompilationUnit() { bool StabsReader::ProcessCompilationUnit() {
assert(symbol_ < symbols_end_ && symbol_->n_type == N_SO); assert(!iterator_->at_end && iterator_->type == N_SO);
// There may be an N_SO entry whose name ends with a slash, // There may be an N_SO entry whose name ends with a slash,
// indicating the directory in which the compilation occurred. // indicating the directory in which the compilation occurred.
@ -104,32 +120,32 @@ bool StabsReader::ProcessCompilationUnit() {
const char *name = SymbolString(); const char *name = SymbolString();
if (name[0] && name[strlen(name) - 1] == '/') { if (name[0] && name[strlen(name) - 1] == '/') {
build_directory = name; build_directory = name;
symbol_++; ++iterator_;
} }
} }
// We expect to see an N_SO entry with a filename next, indicating // We expect to see an N_SO entry with a filename next, indicating
// the start of the compilation unit. // the start of the compilation unit.
{ {
if (symbol_ >= symbols_end_ || symbol_->n_type != N_SO) if (iterator_->at_end || iterator_->type != N_SO)
return true; return true;
const char *name = SymbolString(); const char *name = SymbolString();
if (name[0] == '\0') { if (name[0] == '\0') {
// This seems to be a stray end-of-compilation-unit marker; // This seems to be a stray end-of-compilation-unit marker;
// consume it, but don't report the end, since we didn't see a // consume it, but don't report the end, since we didn't see a
// beginning. // beginning.
symbol_++; ++iterator_;
return true; return true;
} }
current_source_file_ = name; current_source_file_ = name;
} }
if (! handler_->StartCompilationUnit(current_source_file_, if (! handler_->StartCompilationUnit(current_source_file_,
SymbolValue(), iterator_->value,
build_directory)) build_directory))
return false; return false;
symbol_++; ++iterator_;
// The STABS documentation says that some compilers may emit // The STABS documentation says that some compilers may emit
// additional N_SO entries with names immediately following the // additional N_SO entries with names immediately following the
@ -137,24 +153,24 @@ bool StabsReader::ProcessCompilationUnit() {
// Breakpad STABS reader doesn't ignore them, so we won't either. // Breakpad STABS reader doesn't ignore them, so we won't either.
// Process the body of the compilation unit, up to the next N_SO. // Process the body of the compilation unit, up to the next N_SO.
while (symbol_ < symbols_end_ && symbol_->n_type != N_SO) { while (!iterator_->at_end && iterator_->type != N_SO) {
if (symbol_->n_type == N_FUN) { if (iterator_->type == N_FUN) {
if (! ProcessFunction()) if (! ProcessFunction())
return false; return false;
} else } else
// Ignore anything else. // Ignore anything else.
symbol_++; ++iterator_;
} }
// An N_SO with an empty name indicates the end of the compilation // An N_SO with an empty name indicates the end of the compilation
// unit. Default to zero. // unit. Default to zero.
uint64_t ending_address = 0; uint64_t ending_address = 0;
if (symbol_ < symbols_end_) { if (!iterator_->at_end) {
assert(symbol_->n_type == N_SO); assert(iterator_->type == N_SO);
const char *name = SymbolString(); const char *name = SymbolString();
if (name[0] == '\0') { if (name[0] == '\0') {
ending_address = SymbolValue(); ending_address = iterator_->value;
symbol_++; ++iterator_;
} }
} }
@ -165,9 +181,9 @@ bool StabsReader::ProcessCompilationUnit() {
} }
bool StabsReader::ProcessFunction() { bool StabsReader::ProcessFunction() {
assert(symbol_ < symbols_end_ && symbol_->n_type == N_FUN); assert(!iterator_->at_end && iterator_->type == N_FUN);
uint64_t function_address = SymbolValue(); uint64_t function_address = iterator_->value;
// The STABS string for an N_FUN entry is the name of the function, // The STABS string for an N_FUN entry is the name of the function,
// followed by a colon, followed by type information for the // followed by a colon, followed by type information for the
// function. We want to pass the name alone to StartFunction. // function. We want to pass the name alone to StartFunction.
@ -178,37 +194,37 @@ bool StabsReader::ProcessFunction() {
std::string name(stab_string, name_end - stab_string); std::string name(stab_string, name_end - stab_string);
if (! handler_->StartFunction(name, function_address)) if (! handler_->StartFunction(name, function_address))
return false; return false;
symbol_++; ++iterator_;
while (symbol_ < symbols_end_) { while (!iterator_->at_end) {
if (symbol_->n_type == N_SO || symbol_->n_type == N_FUN) if (iterator_->type == N_SO || iterator_->type == N_FUN)
break; break;
else if (symbol_->n_type == N_SLINE) { else if (iterator_->type == N_SLINE) {
// The value of an N_SLINE entry is the offset of the line from // The value of an N_SLINE entry is the offset of the line from
// the function's start address. // the function's start address.
uint64_t line_address = function_address + SymbolValue(); uint64_t line_address = function_address + iterator_->value;
// The n_desc of a N_SLINE entry is the line number. It's a // The n_desc of a N_SLINE entry is the line number. It's a
// signed 16-bit field; line numbers from 32768 to 65535 are // signed 16-bit field; line numbers from 32768 to 65535 are
// stored as n-65536. // stored as n-65536.
uint16_t line_number = symbol_->n_desc; uint16_t line_number = iterator_->descriptor;
if (! handler_->Line(line_address, current_source_file_, line_number)) if (! handler_->Line(line_address, current_source_file_, line_number))
return false; return false;
symbol_++; ++iterator_;
} else if (symbol_->n_type == N_SOL) { } else if (iterator_->type == N_SOL) {
current_source_file_ = SymbolString(); current_source_file_ = SymbolString();
symbol_++; ++iterator_;
} else } else
// Ignore anything else. // Ignore anything else.
symbol_++; ++iterator_;
} }
// If there is a subsequent N_SO or N_FUN entry, its address is our // If there is a subsequent N_SO or N_FUN entry, its address is our
// end address. // end address.
uint64_t ending_address = 0; uint64_t ending_address = 0;
if (symbol_ < symbols_end_) { if (!iterator_->at_end) {
assert(symbol_->n_type == N_SO || symbol_->n_type == N_FUN); assert(iterator_->type == N_SO || iterator_->type == N_FUN);
ending_address = SymbolValue(); ending_address = iterator_->value;
// Note: we do not increment symbol_ here, since we haven't consumed it. // Note: we do not advance iterator_ here, since we haven't consumed it.
} }
if (! handler_->EndFunction(ending_address)) if (! handler_->EndFunction(ending_address))

View file

@ -38,36 +38,47 @@
// //
// The comments here assume you understand the format. // The comments here assume you understand the format.
// //
// This parser assumes that the system's <a.out.h> and <stab.h> // This parser can handle big-endian and little-endian data, and the symbol
// headers accurately describe the layout of the STABS data; this code // values may be either 32 or 64 bits long. It handles both STABS in
// will not parse STABS data for a system with a different address // sections (as used on Linux) and STABS appearing directly in an
// size or endianness. // a.out-like symbol table (as used in Darwin OS X Mach-O files).
#ifndef COMMON_LINUX_STABS_READER_H__ #ifndef COMMON_STABS_READER_H__
#define COMMON_LINUX_STABS_READER_H__ #define COMMON_STABS_READER_H__
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <cstddef>
#ifdef HAVE_A_OUT_H
#include <a.out.h> #include <a.out.h>
#endif
#include <string> #include <string>
#include "common/byte_cursor.h"
namespace google_breakpad { namespace google_breakpad {
class StabsHandler; class StabsHandler;
class StabsReader { class StabsReader {
public: public:
// Create a reader for the STABS debug information whose .stab // Create a reader for the STABS debug information whose .stab section is
// section is the STAB_SIZE bytes at STAB, and whose .stabstr // being traversed by ITERATOR, and whose .stabstr section is referred to
// section is the STABSTR_SIZE bytes at STABSTR. The reader will // by STRINGS. The reader will call the member functions of HANDLER to
// call the member functions of HANDLER to report the information it // report the information it finds, when the reader's 'Process' member
// finds, when the reader's 'Process' member function is called. // function is called.
//
// BIG_ENDIAN should be true if the entries in the .stab section are in
// big-endian form, or false if they are in little-endian form.
// VALUE_SIZE should be either 4 or 8, indicating the size of the 'value'
// field in each entry in bytes.
// //
// Note that, in ELF, the .stabstr section should be found using the // Note that, in ELF, the .stabstr section should be found using the
// 'sh_link' field of the .stab section header, not by name. // 'sh_link' field of the .stab section header, not by name.
StabsReader(const uint8_t *stab, size_t stab_size, StabsReader(const uint8_t *stab, size_t stab_size,
const uint8_t *stabstr, size_t stabstr_size, const uint8_t *stabstr, size_t stabstr_size,
bool big_endian, size_t value_size,
StabsHandler *handler); StabsHandler *handler);
// Process the STABS data, calling the handler's member functions to // Process the STABS data, calling the handler's member functions to
@ -75,17 +86,82 @@ class StabsReader {
// continue to process until we reach the end of the section. If we // continue to process until we reach the end of the section. If we
// processed the entire section and all handlers returned true, // processed the entire section and all handlers returned true,
// return true. If any handler returned false, return false. // return true. If any handler returned false, return false.
//
// This is only meant to be called once per StabsReader instance;
// resuming a prior processing pass that stopped abruptly isn't supported.
bool Process(); bool Process();
private: private:
// An class for walking arrays of STABS entries. This isolates the main
// STABS reader from the exact format (size; endianness) of the entries
// themselves.
class EntryIterator {
public:
// The contents of a STABS entry, adjusted for the host's endianness,
// word size, 'struct nlist' layout, and so on.
struct Entry {
// True if this iterator has reached the end of the entry array. When
// this is set, the other members of this structure are not valid.
bool at_end;
// The number of this entry within the list.
size_t index;
// The current entry's name offset. This is the offset within the
// current compilation unit's strings, as establish by the N_UNDF entries.
size_t name_offset;
// The current entry's type, 'other' field, descriptor, and value.
unsigned char type;
unsigned char other;
short descriptor;
uint64_t value;
};
// Create a EntryIterator walking the entries in BUFFER. Treat the
// entries as big-endian if BIG_ENDIAN is true, as little-endian
// otherwise. Assume each entry has a 'value' field whose size is
// VALUE_SIZE.
//
// This would not be terribly clean to extend to other format variations,
// but it's enough to handle Linux and Mac, and we'd like STABS to die
// anyway.
//
// For the record: on Linux, STABS entry values are always 32 bits,
// regardless of the architecture address size (don't ask me why); on
// Mac, they are 32 or 64 bits long. Oddly, the section header's entry
// size for a Linux ELF .stab section varies according to the ELF class
// from 12 to 20 even as the actual entries remain unchanged.
EntryIterator(const ByteBuffer *buffer, bool big_endian, size_t value_size);
// Move to the next entry. This function's behavior is undefined if
// at_end() is true when it is called.
EntryIterator &operator++() { Fetch(); entry_.index++; return *this; }
// Dereferencing this iterator produces a reference to an Entry structure
// that holds the current entry's values. The entry is owned by this
// EntryIterator, and will be invalidated at the next call to operator++.
const Entry &operator*() const { return entry_; }
const Entry *operator->() const { return &entry_; }
private:
// Read the STABS entry at cursor_, and set entry_ appropriately.
void Fetch();
// The size of entries' value field, in bytes.
size_t value_size_;
// A byte cursor traversing buffer_.
ByteCursor cursor_;
// Values for the entry this iterator refers to.
Entry entry_;
};
// Return the name of the current symbol. // Return the name of the current symbol.
const char *SymbolString(); const char *SymbolString();
// Return the value of the current symbol.
const uint64_t SymbolValue() {
return symbol_->n_value;
}
// Process a compilation unit starting at symbol_. Return true // Process a compilation unit starting at symbol_. Return true
// to continue processing, or false to abort. // to continue processing, or false to abort.
bool ProcessCompilationUnit(); bool ProcessCompilationUnit();
@ -94,10 +170,14 @@ class StabsReader {
// Return true to continue processing, or false to abort. // Return true to continue processing, or false to abort.
bool ProcessFunction(); bool ProcessFunction();
// The debugging information we're reading. // The STABS entries we're parsing.
const struct nlist *symbols_, *symbols_end_; ByteBuffer entries_;
const uint8_t *stabstr_;
size_t stabstr_size_; // The string section to which the entries refer.
ByteBuffer strings_;
// The iterator walking the STABS entries.
EntryIterator iterator_;
StabsHandler *handler_; StabsHandler *handler_;
@ -108,9 +188,6 @@ class StabsReader {
// as established by N_UNDF entries. // as established by N_UNDF entries.
size_t next_cu_string_offset_; size_t next_cu_string_offset_;
// The current symbol we're processing.
const struct nlist *symbol_;
// The current source file name. // The current source file name.
const char *current_source_file_; const char *current_source_file_;
}; };
@ -203,4 +280,4 @@ class StabsHandler {
} // namespace google_breakpad } // namespace google_breakpad
#endif // COMMON_LINUX_STABS_READER_H__ #endif // COMMON_STABS_READER_H__

View file

@ -31,7 +31,6 @@
// stabs_reader_unittest.cc: Unit tests for google_breakpad::StabsReader. // stabs_reader_unittest.cc: Unit tests for google_breakpad::StabsReader.
#include <a.out.h>
#include <cassert> #include <cassert>
#include <cerrno> #include <cerrno>
#include <cstdarg> #include <cstdarg>
@ -46,445 +45,172 @@
#include "breakpad_googletest_includes.h" #include "breakpad_googletest_includes.h"
#include "common/stabs_reader.h" #include "common/stabs_reader.h"
#include "common/test_assembler.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::Eq;
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Return; using ::testing::Return;
using ::testing::Sequence;
using ::testing::StrEq; using ::testing::StrEq;
using ::testing::_;
using google_breakpad::StabsHandler; using google_breakpad::StabsHandler;
using google_breakpad::StabsReader; using google_breakpad::StabsReader;
using google_breakpad::TestAssembler::Label;
using google_breakpad::TestAssembler::Section;
using google_breakpad::TestAssembler::kBigEndian;
using google_breakpad::TestAssembler::kLittleEndian;
using std::map;
using std::string;
namespace { namespace {
// Mock stabs file parser // A StringAssembler is a class for generating .stabstr sections to present
// // as input to the STABS parser.
// In order to test StabsReader, we parse a human-readable input file class StringAssembler: public Section {
// 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: public:
MockStabsHandler() { } StringAssembler() : in_cu_(false) { StartCU(); }
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, // Add the string S to this StringAssembler, and return the string's
// the parser continues; if it returns false, the parser stops and // offset within this compilation unit's strings. If S has been added
// its Process member function returns false. // already, this returns the offset of its first instance.
virtual bool Error(const char *format, ...) = 0; size_t Add(const string &s) {
}; map<string, size_t>::iterator it = added_.find(s);
if (it != added_.end())
// A class for parsing mock stabs files. return it->second;
class MockStabsParser { size_t offset = Size() - cu_start_;
public: AppendCString(s);
// Create a parser reading input from STREAM and passing data to HANDLER. added_[s] = offset;
// Use FILENAME when reporting errors. return offset;
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<string, unsigned char> 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() { // Start a fresh compilation unit string collection.
// On GLIBC-based systems, <bits/stab.def> is a file containing a void StartCU() {
// call to an unspecified macro __define_stab for each stab type. // Ignore duplicate calls to StartCU. Our test data don't always call
// <stab.h> uses it to define the __stab_debug_code enum type. We // StartCU at all, meaning that our constructor has to take care of it,
// use it here to initialize our mapping from type names to enum // meaning that tests that *do* call StartCU call it twice at the
// values. // beginning. This is not worth smoothing out.
// if (in_cu_) return;
// 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 <bits/stab.def>
# undef __define_stab
}
bool MockStabsParser::ParseLine(const string &line) { added_.clear();
istringstream linestream(line); cu_start_ = Size();
// Allow "0x" prefix for hex, and so on.
linestream.unsetf(istringstream::basefield); // Each compilation unit's strings start with an empty string.
// Parse and validate the stabs type. AppendCString("");
string typeName; added_[""] = 0;
linestream >> typeName;
if (typeName == "cu-boundary") { in_cu_ = true;
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. // Finish off the current CU's strings.
// size_t EndCU() {
// A .stab section is an array of struct nlist entries. These assert(in_cu_);
// entries' n_un.n_strx fields are indices into an accompanying in_cu_ = false;
// .stabstr section. return Size() - cu_start_;
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: private:
// The array of stabs entries, // The offset of the start of this compilation unit's strings.
struct nlist *entries_; size_t cu_start_;
// The number of elements of entries_ that are used, and the allocated size
// of the array. // True if we're in a CU.
size_t used_, size_; bool in_cu_;
// A map from the strings that have been added to this section to
// their starting indices within their compilation unit.
map<string, size_t> added_;
}; };
struct nlist *StabSection::Append() { // A StabsAssembler is a class for generating .stab sections to present as
if (used_ == size_) { // test input for the STABS parser.
size_ *= 2; class StabsAssembler: public Section {
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<char *>(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: public:
StabstrSection(): next_byte_(1) { string_indices_[""] = 0; } // Create a StabsAssembler that uses StringAssembler for its strings.
// Ensure STR is present in the string section, and return its index. StabsAssembler(StringAssembler *string_assembler)
size_t Insert(const string &str); : Section(string_assembler->endianness()),
// Set SECTION to the contents of a .stabstr section in which the string_assembler_(string_assembler),
// strings passed to Insert appear at the indices we promised. value_size_(0),
void GetSection(string *section); entry_count_(0),
// Clear the contents of this StabstrSection, and prepare it to cu_header_(NULL) { }
// accumulate a new set of strings. ~StabsAssembler() { assert(!cu_header_); }
void Clear();
private:
// Maps from strings to .stabstr indices and back.
typedef map<string, size_t> StringToIndex;
typedef map<size_t, const string *> IndexToString;
// A map from strings to the indices we've assigned them. // Accessor and setter for value_size_.
StringToIndex string_indices_; size_t value_size() const { return value_size_; }
StabsAssembler &set_value_size(size_t value_size) {
// The next unused byte in the section. The next string we add value_size_ = value_size;
// will get this index. return *this;
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) { // Append a STAB entry to the end of this section with the given
// First we have to invert the map. // characteristics. NAME is the offset of this entry's name string within
IndexToString byIndex; // its compilation unit's portion of the .stabstr section; this can be a
for (StringToIndex::const_iterator it = string_indices_.begin(); // value generated by a StringAssembler. Return a reference to this
it != string_indices_.end(); it++) // StabsAssembler.
byIndex[it->second] = &it->first; StabsAssembler &Stab(uint8_t type, uint8_t other, Label descriptor,
// Now we build the .stabstr section. Label value, Label name) {
section->clear(); D32(name);
for (IndexToString::const_iterator it = byIndex.begin(); D8(type);
it != byIndex.end(); it++) { D8(other);
// Make sure we're actually assigning it the index we claim to be. D16(descriptor);
assert(it->first == section->size()); Append(endianness(), value_size_, value);
*section += *(it->second); entry_count_++;
*section += '\0'; return *this;
} }
}
void StabstrSection::Clear() { // As above, but automatically add NAME to our StringAssembler.
string_indices_.clear(); StabsAssembler &Stab(uint8_t type, uint8_t other, Label descriptor,
string_indices_[""] = 0; Label value, const string &name) {
next_byte_ = 1; return Stab(type, other, descriptor, value, string_assembler_->Add(name));
} }
// A mock stabs parser handler class that builds .stab and .stabstr // Start a compilation unit named NAME, with an N_UNDF symbol to start
// sections. // it, and its own portion of the string section. Return a reference to
class StabsSectionsBuilder: public MockStabsHandler { // this StabsAssembler.
public: StabsAssembler &StartCU(const string &name) {
// Construct a handler that will receive data from a MockStabsParser assert(!cu_header_);
// and construct .stab and .stabstr sections. FILENAME should be cu_header_ = new CUHeader;
// the name of the mock stabs input file; we use it in error string_assembler_->StartCU();
// messages. entry_count_ = 0;
StabsSectionsBuilder(const string &filename) return Stab(N_UNDF, 0,
: filename_(filename), error_count_(0), has_header_(false), cu_header_->final_entry_count,
entry_count_(0) { } cu_header_->final_string_size,
string_assembler_->Add(name));
}
// Overridden virtual member functions. // Close off the current compilation unit. Return a reference to this
bool Entry(enum __stab_debug_code type, char other, short desc, // StabsAssembler.
unsigned long value, const string &name); StabsAssembler &EndCU() {
bool CUBoundary(const string &filename); assert(cu_header_);
bool Error(const char *format, ...); cu_header_->final_entry_count = entry_count_;
cu_header_->final_string_size = string_assembler_->EndCU();
// Set SECTION to the contents of a .stab or .stabstr section delete cu_header_;
// reflecting the entries that have been passed to us via Entry. cu_header_ = NULL;
void GetStab(string *section); return *this;
void GetStabstr(string *section); }
private: private:
// Finish a compilation unit. If there are any entries accumulated in // Data used in a compilation unit header STAB that we won't know until
// stab_ and stabstr_, add them as a new compilation unit to // we've finished the compilation unit.
// finished_cu_stabs_ and finished_cu_stabstr_, and then clear stab_ and struct CUHeader {
// stabstr_. // The final number of entries this compilation unit will hold.
void FinishCU(); Label final_entry_count;
const string &filename_; // input filename, for error messages // The final size of this compilation unit's strings.
int error_count_; // number of errors we've seen so far Label final_string_size;
};
// The following members accumulate the contents of a single compilation // The strings for our STABS entries.
// unit, possibly headed by an N_UNDF stab. StringAssembler *string_assembler_;
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. // The size of the 'value' field of stabs entries in this section.
string finished_cu_stab_, finished_cu_stabstr_; 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_;
}; };
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 { class MockStabsReaderHandler: public StabsHandler {
public: public:
MOCK_METHOD3(StartCompilationUnit, MOCK_METHOD3(StartCompilationUnit,
@ -497,109 +223,138 @@ class MockStabsReaderHandler: public StabsHandler {
MOCK_METHOD1(MockWarning, void(const char *)); MOCK_METHOD1(MockWarning, void(const char *));
}; };
// Create a StabsReader to parse the mock stabs data in INPUT_FILE, // Create a StabsReader to parse the mock stabs data in STABS_ASSEMBLER and
// passing the parsed information to HANDLER. If all goes well, return // STRINGS_ASSEMBLER, and pass the parsed information to HANDLER. Use the
// the result of calling the reader's Process member function. // endianness and value size of STABS_ASSEMBLER to parse the data. If all
// Otherwise, return false. INPUT_FILE should be relative to the top // goes well, return the result of calling the reader's Process member
// of the source tree. // function. Otherwise, return false.
static bool ApplyHandlerToMockStabsData(StabsHandler *handler, static bool ApplyHandlerToMockStabsData(StabsAssembler *stabs_assembler,
const string &input_file) { StringAssembler *strings_assembler,
string full_input_file StabsHandler *handler) {
= 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; string stab, stabstr;
builder.GetStab(&stab); if (!stabs_assembler->GetContents(&stab) ||
builder.GetStabstr(&stabstr); !strings_assembler->GetContents(&stabstr))
return false;
// Run the parser on the test input, passing whatever we find to HANDLER. // Run the parser on the test input, passing whatever we find to HANDLER.
StabsReader reader( StabsReader reader(
reinterpret_cast<const uint8_t *>(stab.data()), stab.size(), reinterpret_cast<const uint8_t *>(stab.data()), stab.size(),
reinterpret_cast<const uint8_t *>(stabstr.data()), stabstr.size(), reinterpret_cast<const uint8_t *>(stabstr.data()), stabstr.size(),
stabs_assembler->endianness() == kBigEndian,
stabs_assembler->value_size(),
handler); handler);
return reader.Process(); return reader.Process();
} }
TEST(StabsReader, MockStabsInput) { TEST(StabsReader, MockStabsInput) {
MockStabsReaderHandler mock_handler; StringAssembler strings;
StabsAssembler stabs(&strings);
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"),
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; MockStabsReaderHandler mock_handler;
{ {
InSequence s; InSequence s;
EXPECT_CALL(mock_handler, EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("file2-1.c"), 0x12, NULL)) 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(&stabs, &strings, &mock_handler));
}
TEST(StabsReader, AbruptCU) {
StringAssembler strings;
StabsAssembler stabs(&strings);
stabs.set_endianness(kBigEndian);
stabs.set_value_size(4);
stabs.Stab(N_SO, 177, 23446, 0xbf10d5e4, "file2-1.c");
MockStabsReaderHandler mock_handler;
{
InSequence s;
EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("file2-1.c"), 0xbf10d5e4, NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
} }
ASSERT_TRUE(ApplyHandlerToMockStabsData( ASSERT_TRUE(ApplyHandlerToMockStabsData(&stabs, &strings, &mock_handler));
&mock_handler,
"common/testdata/stabs_reader_unittest.input2"));
} }
TEST(StabsReader, AbruptFunction) { TEST(StabsReader, AbruptFunction) {
MockStabsReaderHandler mock_handler; MockStabsReaderHandler mock_handler;
StringAssembler strings;
StabsAssembler stabs(&strings);
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; InSequence s;
EXPECT_CALL(mock_handler, EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("file3-1.c"), 0x12, NULL)) StartCompilationUnit(StrEq("file3-1.c"), 0xb83ddf10U, NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, StartFunction(StrEq("fun3_1"), 0x22)) EXPECT_CALL(mock_handler, StartFunction(StrEq("fun3_1"), 0xbbd4a145U))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndFunction(NULL)) EXPECT_CALL(mock_handler, EndFunction(NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
@ -607,12 +362,16 @@ TEST(StabsReader, AbruptFunction) {
.WillOnce(Return(true)); .WillOnce(Return(true));
} }
ASSERT_TRUE(ApplyHandlerToMockStabsData( ASSERT_TRUE(ApplyHandlerToMockStabsData(&stabs, &strings, &mock_handler));
&mock_handler,
"common/testdata/stabs_reader_unittest.input3"));
} }
TEST(StabsReader, NoCU) { TEST(StabsReader, NoCU) {
StringAssembler strings;
StabsAssembler stabs(&strings);
stabs.set_endianness(kBigEndian);
stabs.set_value_size(8);
stabs.Stab(N_SO, 161, 25673, 0x8f676e7bU, "build-directory/");
MockStabsReaderHandler mock_handler; MockStabsReaderHandler mock_handler;
EXPECT_CALL(mock_handler, StartCompilationUnit(_, _, _)) EXPECT_CALL(mock_handler, StartCompilationUnit(_, _, _))
@ -620,64 +379,80 @@ TEST(StabsReader, NoCU) {
EXPECT_CALL(mock_handler, StartFunction(_, _)) EXPECT_CALL(mock_handler, StartFunction(_, _))
.Times(0); .Times(0);
ASSERT_TRUE(ApplyHandlerToMockStabsData( ASSERT_TRUE(ApplyHandlerToMockStabsData(&stabs, &strings, &mock_handler));
&mock_handler,
"common/testdata/stabs_reader_unittest.input4"));
} }
TEST(StabsReader, NoCUEnd) { TEST(StabsReader, NoCUEnd) {
StringAssembler strings;
StabsAssembler stabs(&strings);
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");
MockStabsReaderHandler mock_handler; MockStabsReaderHandler mock_handler;
{ {
InSequence s; InSequence s;
EXPECT_CALL(mock_handler, EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("file5-1.c"), 0x12, NULL)) StartCompilationUnit(StrEq("file5-1.c"), 0x2f7493c9U, NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("file5-2.c"), 0x22, NULL)) StartCompilationUnit(StrEq("file5-2.c"), 0xf9f1d50fU, NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndCompilationUnit(NULL)) EXPECT_CALL(mock_handler, EndCompilationUnit(NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
} }
ASSERT_TRUE(ApplyHandlerToMockStabsData( ASSERT_TRUE(ApplyHandlerToMockStabsData(&stabs, &strings, &mock_handler));
&mock_handler,
"common/testdata/stabs_reader_unittest.input5"));
} }
TEST(StabsReader, MultipleCUs) { TEST(StabsReader, MultipleCUs) {
StringAssembler strings;
StabsAssembler stabs(&strings);
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();
MockStabsReaderHandler mock_handler; MockStabsReaderHandler mock_handler;
{ {
InSequence s; InSequence s;
EXPECT_CALL(mock_handler, EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("antimony"), 0x12, NULL)) StartCompilationUnit(StrEq("antimony"), 0x7e259f1aU, NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, StartFunction(Eq("arsenic"), 0x22)) EXPECT_CALL(mock_handler, StartFunction(Eq("arsenic"), 0x7fbcccaeU))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndFunction(0x32)) EXPECT_CALL(mock_handler, EndFunction(0x80b0014cU))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndCompilationUnit(0x32)) EXPECT_CALL(mock_handler, EndCompilationUnit(0x80b0014cU))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EXPECT_CALL(mock_handler,
StartCompilationUnit(StrEq("aluminum"), 0x42, NULL)) StartCompilationUnit(StrEq("aluminum"), 0x86756839U, NULL))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, StartFunction(Eq("selenium"), 0x52)) EXPECT_CALL(mock_handler, StartFunction(Eq("selenium"), 0xa8e120b0U))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndFunction(0x62)) EXPECT_CALL(mock_handler, EndFunction(0xbffff983U))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(mock_handler, EndCompilationUnit(0x62)) EXPECT_CALL(mock_handler, EndCompilationUnit(0xbffff983U))
.WillOnce(Return(true)); .WillOnce(Return(true));
} }
ASSERT_TRUE(ApplyHandlerToMockStabsData( ASSERT_TRUE(ApplyHandlerToMockStabsData(&stabs, &strings, &mock_handler));
&mock_handler,
"common/testdata/stabs_reader_unittest.input6"));
} }
// name duplication // name duplication

View file

@ -203,6 +203,7 @@ void Label::Binding::Get(Binding **base, u_int64_t *addend) {
template<typename Inserter> template<typename Inserter>
static inline void InsertEndian(TestAssembler::Endianness endianness, static inline void InsertEndian(TestAssembler::Endianness endianness,
size_t size, u_int64_t number, Inserter dest) { size_t size, u_int64_t number, Inserter dest) {
assert(size > 0);
if (endianness == kLittleEndian) { if (endianness == kLittleEndian) {
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
*dest++ = (char) (number & 0xff); *dest++ = (char) (number & 0xff);

View file

@ -1,19 +0,0 @@
SO 10 11 0x02 builddir/
FUN 20 21 0x12 not the SO with source file name we expected
SO 30 31 0x22
SO 40 41 0x32 builddir1/
SO 50 51 0x42 file1.c
LSYM 60 61 0x52 not the FUN we're looking for
FUN 70 71 0x62 fun1
BINCL 80 81 0x72 something to ignore in a FUN body
SLINE 90 91 0x82
SOL 100 101 0x92 header.h
SLINE 110 111 0x102
FUN 120 121 0x112 fun2:some stabs type info here, to trim from the name
SLINE 130 131 0x122
SOL 140 141 0x132 file1.c
SLINE 150 151 0x142
SO 160 161 0x152
LSYM 170 171 0x162
SO 180 181 0x182 file3.c
SO 190 191 0x192

View file

@ -1 +0,0 @@
SO 10 11 0x12 file2-1.c

View file

@ -1,2 +0,0 @@
SO 10 11 0x12 file3-1.c
FUN 20 21 0x22 fun3_1

View file

@ -1 +0,0 @@
SO 10 11 0x12 build-directory/

View file

@ -1,2 +0,0 @@
SO 10 11 0x12 file5-1.c
SO 20 21 0x22 file5-2.c

View file

@ -1,8 +0,0 @@
cu-boundary antimony
SO 10 11 0x12 antimony
FUN 20 21 0x22 arsenic
SO 30 31 0x32
cu-boundary aluminum
SO 40 41 0x42 aluminum
FUN 50 51 0x52 selenium
SO 60 61 0x62

View file

@ -154,6 +154,7 @@ stabs_reader_unittest: \
gtest-all.o \ gtest-all.o \
gtest_main.o \ gtest_main.o \
stabs_reader.o \ stabs_reader.o \
test_assembler.o \
$(empty) $(empty)
CPP_EXECUTABLES += stabs_reader_unittest CPP_EXECUTABLES += stabs_reader_unittest
stabs_reader_unittest.o: stabs_reader_unittest.cc stabs_reader_unittest.o: stabs_reader_unittest.cc
@ -360,7 +361,7 @@ $(CPP_EXECUTABLES): %: %.o
# Allow #include directives to refer to files below 'src'; generate # Allow #include directives to refer to files below 'src'; generate
# dependency files automatically; and I doubt _REENTRANT is needed at all. # dependency files automatically; and I doubt _REENTRANT is needed at all.
BREAKPAD_CPPFLAGS = -I$(SRC) -MMD -D_REENTRANT BREAKPAD_CPPFLAGS = -I$(SRC) -MMD -D_REENTRANT -DHAVE_A_OUT_H
# Bring in whatever dependency files we have generated by compiling with -MMD. # Bring in whatever dependency files we have generated by compiling with -MMD.
-include *.d -include *.d
@ -383,7 +384,7 @@ clean::
### appropriate for Google C++ Testing Framework test programs. But ### appropriate for Google C++ Testing Framework test programs. But
### you can provide your own commands. ### you can provide your own commands.
check-%: % check-%: %
srcdir=$(SRC) $(TEST_WRAPPER) ./$< $(TEST_ARGS) $(TEST_WRAPPER) ./$< $(TEST_ARGS)
### Generic coverage reporting rules. ### Generic coverage reporting rules.