aboutsummaryrefslogtreecommitdiff
path: root/src/common/linux
diff options
context:
space:
mode:
authorjimblandy <jimblandy@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-03-16 16:31:49 +0000
committerjimblandy <jimblandy@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-03-16 16:31:49 +0000
commit3e768ed9c01a244cdb1bc0d6aec34fb25821fbcc (patch)
treebc77f789150d26cb57c7743d15b9e0f7b052bc49 /src/common/linux
parentBreakpad: Add minidump processor support for DWARF Call Frame Information. (diff)
downloadbreakpad-3e768ed9c01a244cdb1bc0d6aec34fb25821fbcc.tar.xz
Breakpad Linux dumper: Add support for dumping DWARF CFI as STACK CFI records.
Define a new DWARF parser class, dwarf2reader::CallFrameInfo. Extend google_breakpad::Module to store and write out 'STACK CFI' records. Define a new google_breakpad::DwarfCFIToModule class, to accept DWARF CFI data from the parser and populate a Module with the equivalent STACK CFI records. Extend the Linux symbol dumping tool, dump_syms, to use dwarf2reader::CallFrameInfo, google_breakpad::DwarfCFIToModule, and google_breakpad::Module to extract DWARF CFI from the executable or shared library files and write it to the Breakpad symbol file. Define CFISection, a new class derived from TestAssembler::Section, for use in creating DWARF CFI data for test cases. a=jimblandy, r=nealsid git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@550 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/common/linux')
-rw-r--r--src/common/linux/dump_symbols.cc133
-rw-r--r--src/common/linux/dwarf_cfi_to_module.cc187
-rw-r--r--src/common/linux/dwarf_cfi_to_module.h154
-rw-r--r--src/common/linux/dwarf_cfi_to_module_unittest.cc274
-rw-r--r--src/common/linux/module.cc46
-rw-r--r--src/common/linux/module.h65
-rw-r--r--src/common/linux/module_unittest.cc112
7 files changed, 963 insertions, 8 deletions
diff --git a/src/common/linux/dump_symbols.cc b/src/common/linux/dump_symbols.cc
index 993bdbd5..79d7ad36 100644
--- a/src/common/linux/dump_symbols.cc
+++ b/src/common/linux/dump_symbols.cc
@@ -49,6 +49,7 @@
#include "common/dwarf/dwarf2diehandler.h"
#include "common/linux/dump_stabs.h"
#include "common/linux/dump_symbols.h"
+#include "common/linux/dwarf_cfi_to_module.h"
#include "common/linux/dwarf_cu_to_module.h"
#include "common/linux/dwarf_line_to_module.h"
#include "common/linux/file_id.h"
@@ -59,6 +60,7 @@
namespace {
using google_breakpad::DumpStabsHandler;
+using google_breakpad::DwarfCFIToModule;
using google_breakpad::DwarfCUToModule;
using google_breakpad::DwarfLineToModule;
using google_breakpad::Module;
@@ -215,6 +217,119 @@ static bool LoadDwarf(const string &dwarf_filename,
return true;
}
+// Fill REGISTER_NAMES with the register names appropriate to the
+// machine architecture given in HEADER, indexed by the register
+// numbers used in DWARF call frame information. Return true on
+// success, or false if we don't recognize HEADER's machine
+// architecture.
+static bool DwarfCFIRegisterNames(const ElfW(Ehdr) *elf_header,
+ vector<string> *register_names)
+{
+ static const char *const i386_names[] = {
+ "$eax", "$ecx", "$edx", "$ebx", "$esp", "$ebp", "$esi", "$edi",
+ "$eip", "$eflags", "$unused1",
+ "$st0", "$st1", "$st2", "$st3", "$st4", "$st5", "$st6", "$st7",
+ "$unused2", "$unused3",
+ "$xmm0", "$xmm1", "$xmm2", "$xmm3", "$xmm4", "$xmm5", "$xmm6", "$xmm7",
+ "$mm0", "$mm1", "$mm2", "$mm3", "$mm4", "$mm5", "$mm6", "$mm7",
+ "$fcw", "$fsw", "$mxcsr",
+ "$es", "$cs", "$ss", "$ds", "$fs", "$gs", "$unused4", "$unused5",
+ "$tr", "$ldtr",
+ NULL
+ };
+
+ static const char *const x86_64_names[] = {
+ "$rax", "$rdx", "$rcx", "$rbx", "$rsi", "$rdi", "$rbp", "$rsp",
+ "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15",
+ "$rip",
+ "$xmm0","$xmm1","$xmm2", "$xmm3", "$xmm4", "$xmm5", "$xmm6", "$xmm7",
+ "$xmm8","$xmm9","$xmm10","$xmm11","$xmm12","$xmm13","$xmm14","$xmm15",
+ "$st0", "$st1", "$st2", "$st3", "$st4", "$st5", "$st6", "$st7",
+ "$mm0", "$mm1", "$mm2", "$mm3", "$mm4", "$mm5", "$mm6", "$mm7",
+ "$rflags",
+ "$es", "$cs", "$ss", "$ds", "$fs", "$gs", "$unused1", "$unused2",
+ "$fs.base", "$gs.base", "$unused3", "$unused4",
+ "$tr", "$ldtr",
+ "$mxcsr", "$fcw", "$fsw",
+ NULL
+ };
+
+ const char * const *name_table;
+ switch (elf_header->e_machine) {
+ case EM_386:
+ name_table = i386_names;
+ break;
+
+ case EM_X86_64:
+ name_table = x86_64_names;
+ break;
+
+ default:
+ return false;
+ }
+
+ register_names->clear();
+ for (int i = 0; name_table[i]; i++)
+ register_names->push_back(name_table[i]);
+ return true;
+}
+
+static bool LoadDwarfCFI(const string &dwarf_filename,
+ const ElfW(Ehdr) *elf_header,
+ const char *section_name,
+ const ElfW(Shdr) *section,
+ Module *module) {
+ // Find the appropriate set of register names for this file's
+ // architecture.
+ vector<string> register_names;
+ if (!DwarfCFIRegisterNames(elf_header, &register_names)) {
+ fprintf(stderr, "%s: unrecognized ELF machine architecture '%d';"
+ " cannot convert DWARF call frame information\n",
+ dwarf_filename.c_str(), elf_header->e_machine);
+ return false;
+ }
+
+ // Figure out what endianness this file is.
+ dwarf2reader::Endianness endianness;
+ if (elf_header->e_ident[EI_DATA] == ELFDATA2LSB)
+ endianness = dwarf2reader::ENDIANNESS_LITTLE;
+ else if (elf_header->e_ident[EI_DATA] == ELFDATA2MSB)
+ endianness = dwarf2reader::ENDIANNESS_BIG;
+ else {
+ fprintf(stderr, "%s: bad data encoding in ELF header: %d\n",
+ dwarf_filename.c_str(), elf_header->e_ident[EI_DATA]);
+ return false;
+ }
+
+ // Find the call frame information and its size.
+ const char *cfi = reinterpret_cast<const char *>(section->sh_offset);
+ size_t cfi_size = section->sh_size;
+
+ // Plug together the parser, handler, and their entourages.
+ DwarfCFIToModule::Reporter module_reporter(dwarf_filename, section_name);
+ DwarfCFIToModule handler(module, register_names, &module_reporter);
+ dwarf2reader::ByteReader byte_reader(endianness);
+ // Since we're using the ElfW macro, we're not actually capable of
+ // processing both ELF32 and ELF64 files with the same program; that
+ // would take a bit more work. But this will work out well enough.
+ if (elf_header->e_ident[EI_CLASS] == ELFCLASS32)
+ byte_reader.SetAddressSize(4);
+ else if (elf_header->e_ident[EI_CLASS] == ELFCLASS64)
+ byte_reader.SetAddressSize(8);
+ else {
+ fprintf(stderr, "%s: bad file class in ELF header: %d\n",
+ dwarf_filename.c_str(), elf_header->e_ident[EI_CLASS]);
+ return false;
+ }
+
+ dwarf2reader::CallFrameInfo::Reporter dwarf_reporter(dwarf_filename,
+ section_name);
+ dwarf2reader::CallFrameInfo parser(cfi, cfi_size, &byte_reader,
+ &handler, &dwarf_reporter);
+ parser.Start();
+ return true;
+}
+
static bool LoadSymbols(const std::string &obj_file, ElfW(Ehdr) *elf_header,
Module *module) {
// Translate all offsets in section headers into address.
@@ -228,6 +343,8 @@ static bool LoadSymbols(const std::string &obj_file, ElfW(Ehdr) *elf_header,
reinterpret_cast<ElfW(Shdr) *>(elf_header->e_shoff);
const ElfW(Shdr) *section_names = sections + elf_header->e_shstrndx;
bool found_debug_info_section = false;
+
+ // Look for STABS debugging information, and load it if present.
const ElfW(Shdr) *stab_section
= FindSectionByName(".stab", sections, section_names,
elf_header->e_shnum);
@@ -240,6 +357,8 @@ static bool LoadSymbols(const std::string &obj_file, ElfW(Ehdr) *elf_header,
" debugging information\n");
}
}
+
+ // Look for DWARF debugging information, and load it if present.
const ElfW(Shdr) *dwarf_section
= FindSectionByName(".debug_info", sections, section_names,
elf_header->e_shnum);
@@ -249,6 +368,20 @@ static bool LoadSymbols(const std::string &obj_file, ElfW(Ehdr) *elf_header,
fprintf(stderr, "\".debug_info\" section found, but failed to load "
"DWARF debugging information\n");
}
+
+ // Dwarf Call Frame Information (CFI) is actually independent from
+ // the other DWARF debugging information, and can be used alone.
+ const ElfW(Shdr) *dwarf_cfi_section =
+ FindSectionByName(".debug_frame", sections, section_names,
+ elf_header->e_shnum);
+ if (dwarf_cfi_section) {
+ // Ignore the return value of this function; even without call frame
+ // information, the other debugging information could be perfectly
+ // useful.
+ LoadDwarfCFI(obj_file, elf_header, ".debug_frame",
+ dwarf_cfi_section, module);
+ }
+
if (!found_debug_info_section) {
fprintf(stderr, "file contains no debugging information"
" (no \".stab\" or \".debug_info\" sections)\n");
diff --git a/src/common/linux/dwarf_cfi_to_module.cc b/src/common/linux/dwarf_cfi_to_module.cc
new file mode 100644
index 00000000..d7946a0e
--- /dev/null
+++ b/src/common/linux/dwarf_cfi_to_module.cc
@@ -0,0 +1,187 @@
+// -*- mode: c++ -*-
+
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// Implementation of google_breakpad::DwarfCFIToModule.
+// See dwarf_cfi_to_module.h for details.
+
+#include <sstream>
+
+#include "common/linux/dwarf_cfi_to_module.h"
+
+namespace google_breakpad {
+
+using std::ostringstream;
+
+bool DwarfCFIToModule::Entry(size_t offset, uint64 address, uint64 length,
+ uint8 version, const string &augmentation,
+ unsigned return_address) {
+ assert(!entry_);
+ // The latest CFI format version we understand is version 3.
+ if (version > 3)
+ return false;
+ // We only handle non-augmented DWARF unwinding data at the moment.
+ if (!augmentation.empty())
+ return false;
+
+ // Get ready to collect entries.
+ entry_ = new Module::StackFrameEntry;
+ entry_->address = address;
+ entry_->size = length;
+ entry_offset_ = offset;
+ return_address_ = return_address;
+
+ // Breakpad STACK CFI records must provide a .ra rule, but DWARF CFI
+ // may not establish any rule for .ra if the return address column
+ // is an ordinary register, and that register holds the return
+ // address on entry to the function. So establish an initial .ra
+ // rule citing the return address register.
+ if (return_address_ < register_names_.size())
+ entry_->initial_rules[".ra"] = register_names_[return_address_];
+
+ return true;
+}
+
+string DwarfCFIToModule::RegisterName(int i) {
+ assert(entry_);
+ if (i < 0) {
+ assert(i == kCFARegister);
+ return ".cfa";
+ }
+ unsigned reg = i;
+ if (reg == return_address_)
+ return ".ra";
+
+ if (0 <= reg && reg < register_names_.size())
+ return register_names_[reg];
+
+ reporter_->UnnamedRegister(entry_offset_, reg);
+ char buf[30];
+ sprintf(buf, "unnamed_register%u", reg);
+ return buf;
+}
+
+void DwarfCFIToModule::Record(Module::Address address, int reg,
+ const string &rule) {
+ assert(entry_);
+ // Is this one of this entry's initial rules?
+ if (address == entry_->address)
+ entry_->initial_rules[RegisterName(reg)] = rule;
+ // File it under the appropriate address.
+ else
+ entry_->rule_changes[address][RegisterName(reg)] = rule;
+}
+
+bool DwarfCFIToModule::UndefinedRule(uint64 address, int reg) {
+ reporter_->UndefinedNotSupported(entry_offset_, RegisterName(reg));
+ // Treat this as a non-fatal error.
+ return true;
+}
+
+bool DwarfCFIToModule::SameValueRule(uint64 address, int reg) {
+ ostringstream s;
+ s << RegisterName(reg);
+ Record(address, reg, s.str());
+ return true;
+}
+
+bool DwarfCFIToModule::OffsetRule(uint64 address, int reg,
+ int base_register, long offset) {
+ ostringstream s;
+ s << RegisterName(base_register) << " " << offset << " + ^";
+ Record(address, reg, s.str());
+ return true;
+}
+
+bool DwarfCFIToModule::ValOffsetRule(uint64 address, int reg,
+ int base_register, long offset) {
+ ostringstream s;
+ s << RegisterName(base_register) << " " << offset << " +";
+ Record(address, reg, s.str());
+ return true;
+}
+
+bool DwarfCFIToModule::RegisterRule(uint64 address, int reg,
+ int base_register) {
+ ostringstream s;
+ s << RegisterName(base_register);
+ Record(address, reg, s.str());
+ return true;
+}
+
+bool DwarfCFIToModule::ExpressionRule(uint64 address, int reg,
+ const string &expression) {
+ reporter_->ExpressionsNotSupported(entry_offset_, RegisterName(reg));
+ // Treat this as a non-fatal error.
+ return true;
+}
+
+bool DwarfCFIToModule::ValExpressionRule(uint64 address, int reg,
+ const string &expression) {
+ reporter_->ExpressionsNotSupported(entry_offset_, RegisterName(reg));
+ // Treat this as a non-fatal error.
+ return true;
+}
+
+bool DwarfCFIToModule::End() {
+ module_->AddStackFrameEntry(entry_);
+ entry_ = NULL;
+ return true;
+}
+
+void DwarfCFIToModule::Reporter::UnnamedRegister(size_t offset, int reg) {
+ fprintf(stderr, "%s, section '%s': "
+ "the call frame entry at offset 0x%zx refers to register %d,"
+ " whose name we don't know\n",
+ file_.c_str(), section_.c_str(), offset, reg);
+}
+
+void DwarfCFIToModule::Reporter::UndefinedNotSupported(size_t offset,
+ const string &reg) {
+ fprintf(stderr, "%s, section '%s': "
+ "the call frame entry at offset 0x%zx sets the rule for "
+ "register '%s' to 'undefined', but the Breakpad symbol file format"
+ " cannot express this\n",
+ file_.c_str(), section_.c_str(), offset, reg.c_str());
+}
+
+void DwarfCFIToModule::Reporter::ExpressionsNotSupported(size_t offset,
+ const string &reg) {
+ fprintf(stderr, "%s, section '%s': "
+ "the call frame entry at offset 0x%zx uses a DWARF expression to"
+ " describe how to recover register '%s', "
+ " but this translator cannot yet translate DWARF expressions to"
+ " Breakpad postfix expressions\n",
+ file_.c_str(), section_.c_str(), offset, reg.c_str());
+}
+
+} // namespace google_breakpad
diff --git a/src/common/linux/dwarf_cfi_to_module.h b/src/common/linux/dwarf_cfi_to_module.h
new file mode 100644
index 00000000..9df796f5
--- /dev/null
+++ b/src/common/linux/dwarf_cfi_to_module.h
@@ -0,0 +1,154 @@
+// -*- mode: c++ -*-
+
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// dwarf_cfi_to_module.h: Define the DwarfCFIToModule class, which
+// accepts parsed DWARF call frame info and adds it to a
+// google_breakpad::Module object, which can write that information to
+// a Breakpad symbol file.
+
+#ifndef COMMON_LINUX_DWARF_CFI_TO_MODULE_H
+#define COMMON_LINUX_DWARF_CFI_TO_MODULE_H
+
+#include <cassert>
+#include <string>
+#include <vector>
+
+#include "common/linux/module.h"
+#include "common/dwarf/dwarf2reader.h"
+
+namespace google_breakpad {
+
+using dwarf2reader::CallFrameInfo;
+using google_breakpad::Module;
+using std::string;
+using std::vector;
+
+// A class that accepts parsed call frame information from the DWARF
+// CFI parser and populates a google_breakpad::Module object with the
+// contents.
+class DwarfCFIToModule: public CallFrameInfo::Handler {
+ public:
+
+ // DwarfCFIToModule uses an instance of this class to report errors
+ // detected while converting DWARF CFI to Breakpad STACK CFI records.
+ class Reporter {
+ public:
+ // Create a reporter that writes messages to the standard error
+ // stream. FILE is the name of the file we're processing, and
+ // SECTION is the name of the section within that file that we're
+ // looking at (.debug_frame, .eh_frame, etc.).
+ Reporter(const string &file, const string &section)
+ : file_(file), section_(section) { }
+ virtual ~Reporter() { }
+
+ // The DWARF CFI entry at OFFSET cites register REG, but REG is not
+ // covered by the vector of register names passed to the
+ // DwarfCFIToModule constructor, nor does it match the return
+ // address column number for this entry.
+ virtual void UnnamedRegister(size_t offset, int reg);
+
+ // The DWARF CFI entry at OFFSET says that REG is undefined, but the
+ // Breakpad symbol file format cannot express this.
+ virtual void UndefinedNotSupported(size_t offset, const string &reg);
+
+ // The DWARF CFI entry at OFFSET says that REG uses a DWARF
+ // expression to find its value, but DwarfCFIToModule is not
+ // capable of translating DWARF expressions to Breakpad postfix
+ // expressions.
+ virtual void ExpressionsNotSupported(size_t offset, const string &reg);
+
+ protected:
+ string file_, section_;
+ };
+
+ // Create a handler for the dwarf2reader::CallFrameInfo parser that
+ // records the stack unwinding information it receives in MODULE.
+ //
+ // Use REGISTER_NAMES[I] as the name of register number I; *this
+ // keeps a reference to the vector, so the vector should remain
+ // alive for as long as the DwarfCFIToModule does.
+ //
+ // Use REPORTER for reporting problems encountered in the conversion
+ // process.
+ DwarfCFIToModule(Module *module, const vector<string> &register_names,
+ Reporter *reporter)
+ : module_(module), register_names_(register_names), reporter_(reporter),
+ entry_(NULL), return_address_(-1) { }
+ virtual ~DwarfCFIToModule() { delete entry_; }
+
+ virtual bool Entry(size_t offset, uint64 address, uint64 length,
+ uint8 version, const string &augmentation,
+ unsigned return_address);
+ virtual bool UndefinedRule(uint64 address, int reg);
+ virtual bool SameValueRule(uint64 address, int reg);
+ virtual bool OffsetRule(uint64 address, int reg,
+ int base_register, long offset);
+ virtual bool ValOffsetRule(uint64 address, int reg,
+ int base_register, long offset);
+ virtual bool RegisterRule(uint64 address, int reg, int base_register);
+ virtual bool ExpressionRule(uint64 address, int reg,
+ const string &expression);
+ virtual bool ValExpressionRule(uint64 address, int reg,
+ const string &expression);
+ virtual bool End();
+
+ private:
+ // Return the name to use for register REG.
+ string RegisterName(int i);
+
+ // Record RULE for register REG at ADDRESS.
+ void Record(Module::Address address, int reg, const string &rule);
+
+ // The module to which we should add entries.
+ Module *module_;
+
+ // Map from register numbers to register names.
+ const vector<string> &register_names_;
+
+ // The reporter to use to report problems.
+ Reporter *reporter_;
+
+ // The current entry we're constructing.
+ Module::StackFrameEntry *entry_;
+
+ // The section offset of the current frame description entry, for
+ // use in error messages.
+ size_t entry_offset_;
+
+ // The return address column for that entry.
+ unsigned return_address_;
+};
+
+} // namespace google_breakpad
+
+#endif // COMMON_LINUX_DWARF_CFI_TO_MODULE_H
diff --git a/src/common/linux/dwarf_cfi_to_module_unittest.cc b/src/common/linux/dwarf_cfi_to_module_unittest.cc
new file mode 100644
index 00000000..de769393
--- /dev/null
+++ b/src/common/linux/dwarf_cfi_to_module_unittest.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// dwarf_cfi_to_module_unittest.cc: Tests for google_breakpad::DwarfCFIToModule.
+
+#include "breakpad_googletest_includes.h"
+#include "common/linux/dwarf_cfi_to_module.h"
+
+using google_breakpad::Module;
+using google_breakpad::DwarfCFIToModule;
+using testing::ContainerEq;
+using testing::Test;
+using testing::_;
+
+struct MockCFIReporter: public DwarfCFIToModule::Reporter {
+ MockCFIReporter(const string &file, const string &section)
+ : Reporter(file, section) { }
+ MOCK_METHOD2(UnnamedRegister, void(size_t offset, int reg));
+ MOCK_METHOD2(UndefinedNotSupported, void(size_t offset, const string &reg));
+ MOCK_METHOD2(ExpressionsNotSupported, void(size_t offset, const string &reg));
+};
+
+struct DwarfCFIToModuleFixture {
+ DwarfCFIToModuleFixture()
+ : module("module name", "module os", "module arch", "module id"),
+ reporter("reporter file", "reporter section"),
+ handler(&module, register_names, &reporter) {
+ register_names.push_back("reg0");
+ register_names.push_back("reg1");
+ register_names.push_back("reg2");
+ register_names.push_back("reg3");
+ register_names.push_back("reg4");
+ register_names.push_back("reg5");
+ register_names.push_back("reg6");
+ register_names.push_back("reg7");
+ register_names.push_back("sp");
+ register_names.push_back("pc");
+
+ EXPECT_CALL(reporter, UnnamedRegister(_, _)).Times(0);
+ EXPECT_CALL(reporter, UndefinedNotSupported(_, _)).Times(0);
+ EXPECT_CALL(reporter, ExpressionsNotSupported(_, _)).Times(0);
+ }
+
+ Module module;
+ vector<string> register_names;
+ MockCFIReporter reporter;
+ DwarfCFIToModule handler;
+ vector<Module::StackFrameEntry *> entries;
+};
+
+class Entry: public DwarfCFIToModuleFixture, public Test { };
+
+TEST_F(Entry, IgnoreVersion) {
+ ASSERT_FALSE(handler.Entry(0xf120e638, 0x2851bc1f7a181d6dULL,
+ 0x40589a48d66e5a88ULL, 4, "", 0x1ad80491));
+ module.GetStackFrameEntries(&entries);
+ EXPECT_EQ(0U, entries.size());
+}
+
+TEST_F(Entry, IgnoreAugmentation) {
+ ASSERT_FALSE(handler.Entry(0x3f9d228a, 0xcf9a94bb805cf5a4ULL,
+ 0xe6c41bf958d4c171ULL, 3, "snazzy", 0x444a14f3));
+ module.GetStackFrameEntries(&entries);
+ EXPECT_EQ(0U, entries.size());
+}
+
+TEST_F(Entry, Accept) {
+ ASSERT_TRUE(handler.Entry(0x3b8961b8, 0xa21069698096fc98ULL,
+ 0xb440ce248169c8d6ULL, 3, "", 0xea93c106));
+ ASSERT_TRUE(handler.End());
+ module.GetStackFrameEntries(&entries);
+ EXPECT_EQ(1U, entries.size());
+ EXPECT_EQ(0xa21069698096fc98ULL, entries[0]->address);
+ EXPECT_EQ(0xb440ce248169c8d6ULL, entries[0]->size);
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Entry, AcceptOldVersion) {
+ ASSERT_TRUE(handler.Entry(0xeb60e0fc, 0x75b8806bb09eab78ULL,
+ 0xc771f44958d40bbcULL, 1, "", 0x093c945e));
+ ASSERT_TRUE(handler.End());
+ module.GetStackFrameEntries(&entries);
+ EXPECT_EQ(1U, entries.size());
+ EXPECT_EQ(0x75b8806bb09eab78ULL, entries[0]->address);
+ EXPECT_EQ(0xc771f44958d40bbcULL, entries[0]->size);
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+struct RuleFixture: public DwarfCFIToModuleFixture {
+ RuleFixture() : DwarfCFIToModuleFixture() {
+ entry_address = 0x89327ebf86b47492ULL;
+ entry_size = 0x2f8cd573072fe02aULL;
+ return_reg = 0x7886a346;
+ }
+ void StartEntry() {
+ ASSERT_TRUE(handler.Entry(0x4445c05c, entry_address, entry_size,
+ 3, "", return_reg));
+ }
+ void CheckEntry() {
+ module.GetStackFrameEntries(&entries);
+ EXPECT_EQ(1U, entries.size());
+ EXPECT_EQ(entry_address, entries[0]->address);
+ EXPECT_EQ(entry_size, entries[0]->size);
+ }
+ uint64 entry_address, entry_size;
+ unsigned return_reg;
+};
+
+class Rule: public RuleFixture, public Test { };
+
+TEST_F(Rule, UndefinedRule) {
+ EXPECT_CALL(reporter, UndefinedNotSupported(_, "reg7"));
+ StartEntry();
+ ASSERT_TRUE(handler.UndefinedRule(entry_address, 7));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, SameValueRule) {
+ StartEntry();
+ ASSERT_TRUE(handler.SameValueRule(entry_address, 6));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ Module::RuleMap expected_initial;
+ expected_initial["reg6"] = "reg6";
+ EXPECT_THAT(entries[0]->initial_rules, ContainerEq(expected_initial));
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, OffsetRule) {
+ StartEntry();
+ ASSERT_TRUE(handler.OffsetRule(entry_address + 1, return_reg,
+ DwarfCFIToModule::kCFARegister,
+ 16927065));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ Module::RuleChangeMap expected_changes;
+ expected_changes[entry_address + 1][".ra"] = ".cfa 16927065 + ^";
+ EXPECT_THAT(entries[0]->rule_changes, ContainerEq(expected_changes));
+}
+
+TEST_F(Rule, OffsetRuleNegative) {
+ StartEntry();
+ ASSERT_TRUE(handler.OffsetRule(entry_address + 1,
+ DwarfCFIToModule::kCFARegister, 4, -34530721));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ Module::RuleChangeMap expected_changes;
+ expected_changes[entry_address + 1][".cfa"] = "reg4 -34530721 + ^";
+ EXPECT_THAT(entries[0]->rule_changes, ContainerEq(expected_changes));
+}
+
+TEST_F(Rule, ValOffsetRule) {
+ // Use an unnamed register number, to exercise that branch of RegisterName.
+ EXPECT_CALL(reporter, UnnamedRegister(_, 10));
+ StartEntry();
+ ASSERT_TRUE(handler.ValOffsetRule(entry_address + 0x5ab7,
+ DwarfCFIToModule::kCFARegister,
+ 10, 61812979));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ Module::RuleChangeMap expected_changes;
+ expected_changes[entry_address + 0x5ab7][".cfa"] =
+ "unnamed_register10 61812979 +";
+ EXPECT_THAT(entries[0]->rule_changes, ContainerEq(expected_changes));
+}
+
+TEST_F(Rule, RegisterRule) {
+ StartEntry();
+ ASSERT_TRUE(handler.RegisterRule(entry_address, return_reg, 3));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ Module::RuleMap expected_initial;
+ expected_initial[".ra"] = "reg3";
+ EXPECT_THAT(entries[0]->initial_rules, ContainerEq(expected_initial));
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, ExpressionRule) {
+ EXPECT_CALL(reporter, ExpressionsNotSupported(_, "reg2"));
+ StartEntry();
+ ASSERT_TRUE(handler.ExpressionRule(entry_address + 0xf326, 2,
+ "it takes two to tango"));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, ValExpressionRule) {
+ EXPECT_CALL(reporter, ExpressionsNotSupported(_, "reg0"));
+ StartEntry();
+ ASSERT_TRUE(handler.ValExpressionRule(entry_address + 0x6367, 0,
+ "bit off more than he could chew"));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ EXPECT_EQ(0U, entries[0]->initial_rules.size());
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, DefaultReturnAddressRule) {
+ return_reg = 2;
+ StartEntry();
+ ASSERT_TRUE(handler.RegisterRule(entry_address, 0, 1));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ Module::RuleMap expected_initial;
+ expected_initial[".ra"] = "reg2";
+ expected_initial["reg0"] = "reg1";
+ EXPECT_THAT(entries[0]->initial_rules, ContainerEq(expected_initial));
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, DefaultReturnAddressRuleOverride) {
+ return_reg = 2;
+ StartEntry();
+ ASSERT_TRUE(handler.RegisterRule(entry_address, return_reg, 1));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ Module::RuleMap expected_initial;
+ expected_initial[".ra"] = "reg1";
+ EXPECT_THAT(entries[0]->initial_rules, ContainerEq(expected_initial));
+ EXPECT_EQ(0U, entries[0]->rule_changes.size());
+}
+
+TEST_F(Rule, DefaultReturnAddressRuleLater) {
+ return_reg = 2;
+ StartEntry();
+ ASSERT_TRUE(handler.RegisterRule(entry_address + 1, return_reg, 1));
+ ASSERT_TRUE(handler.End());
+ CheckEntry();
+ Module::RuleMap expected_initial;
+ expected_initial[".ra"] = "reg2";
+ EXPECT_THAT(entries[0]->initial_rules, ContainerEq(expected_initial));
+ Module::RuleChangeMap expected_changes;
+ expected_changes[entry_address + 1][".ra"] = "reg1";
+ EXPECT_THAT(entries[0]->rule_changes, ContainerEq(expected_changes));
+}
+
diff --git a/src/common/linux/module.cc b/src/common/linux/module.cc
index bd0ae9f9..f6373895 100644
--- a/src/common/linux/module.cc
+++ b/src/common/linux/module.cc
@@ -52,6 +52,9 @@ Module::~Module() {
for (vector<Function *>::iterator it = functions_.begin();
it != functions_.end(); it++)
delete *it;
+ for (vector<StackFrameEntry *>::iterator it = stack_frame_entries_.begin();
+ it != stack_frame_entries_.end(); it++)
+ delete *it;
}
void Module::SetLoadAddress(Address address) {
@@ -67,6 +70,10 @@ void Module::AddFunctions(vector<Function *>::iterator begin,
functions_.insert(functions_.end(), begin, end);
}
+void Module::AddStackFrameEntry(StackFrameEntry *stack_frame_entry) {
+ stack_frame_entries_.push_back(stack_frame_entry);
+}
+
void Module::GetFunctions(vector<Function *> *vec,
vector<Function *>::iterator i) {
vec->insert(i, functions_.begin(), functions_.end());
@@ -111,6 +118,10 @@ void Module::GetFiles(vector<File *> *vec) {
vec->push_back(it->second);
}
+void Module::GetStackFrameEntries(vector<StackFrameEntry *> *vec) {
+ *vec = stack_frame_entries_;
+}
+
void Module::AssignSourceIds() {
// First, give every source file an id of -1.
for (FileByNameMap::iterator file_it = files_.begin();
@@ -144,6 +155,18 @@ bool Module::ReportError() {
return false;
}
+bool Module::WriteRuleMap(const RuleMap &rule_map, FILE *stream) {
+ for (RuleMap::const_iterator it = rule_map.begin();
+ it != rule_map.end(); it++) {
+ if (it != rule_map.begin() &&
+ 0 > putc(' ', stream))
+ return false;
+ if (0 > fprintf(stream, "%s: %s", it->first.c_str(), it->second.c_str()))
+ return false;
+ }
+ return true;
+}
+
bool Module::Write(FILE *stream) {
if (0 > fprintf(stream, "MODULE %s %s %s %s\n",
os_.c_str(), architecture_.c_str(), id_.c_str(),
@@ -183,6 +206,29 @@ bool Module::Write(FILE *stream) {
return ReportError();
}
+ // Write out 'STACK CFI INIT' and 'STACK CFI' records.
+ vector<StackFrameEntry *>::const_iterator frame_it;
+ for (frame_it = stack_frame_entries_.begin();
+ frame_it != stack_frame_entries_.end(); frame_it++) {
+ StackFrameEntry *entry = *frame_it;
+ if (0 > fprintf(stream, "STACK CFI INIT %llx %llx ",
+ (unsigned long long) entry->address - load_address_,
+ (unsigned long long) entry->size)
+ || !WriteRuleMap(entry->initial_rules, stream)
+ || 0 > putc('\n', stream))
+ return ReportError();
+
+ // Write out this entry's delta rules as 'STACK CFI' records.
+ for (RuleChangeMap::const_iterator delta_it = entry->rule_changes.begin();
+ delta_it != entry->rule_changes.end(); delta_it++) {
+ if (0 > fprintf(stream, "STACK CFI %llx ",
+ (unsigned long long) delta_it->first - load_address_)
+ || !WriteRuleMap(delta_it->second, stream)
+ || 0 > putc('\n', stream))
+ return ReportError();
+ }
+ }
+
return true;
}
diff --git a/src/common/linux/module.h b/src/common/linux/module.h
index eeda305e..7a44d4ab 100644
--- a/src/common/linux/module.h
+++ b/src/common/linux/module.h
@@ -113,6 +113,35 @@ class Module {
File *file; // The source file.
int number; // The source line number.
};
+
+ // A map from register names to postfix expressions that recover
+ // their their values. This can represent a complete set of rules to
+ // follow at some address, or a set of changes to be applied to an
+ // extant set of rules.
+ typedef map<string, string> RuleMap;
+
+ // A map from addresses to RuleMaps, representing changes that take
+ // effect at given addresses.
+ typedef map<Address, RuleMap> RuleChangeMap;
+
+ // A range of 'STACK CFI' stack walking information. An instance of
+ // this structure corresponds to a 'STACK CFI INIT' record and the
+ // subsequent 'STACK CFI' records that fall within its range.
+ struct StackFrameEntry {
+ // The starting address and number of bytes of machine code this
+ // entry covers.
+ Address address, size;
+
+ // The initial register recovery rules, in force at the starting
+ // address.
+ RuleMap initial_rules;
+
+ // A map from addresses to rule changes. To find the rules in
+ // force at a given address, start with initial_rules, and then
+ // apply the changes given in this map for all addresses up to and
+ // including the address you're interested in.
+ RuleChangeMap rule_changes;
+ };
// Create a new module with the given name, operating system,
// architecture, and ID string.
@@ -139,6 +168,12 @@ class Module {
void AddFunctions(vector<Function *>::iterator begin,
vector<Function *>::iterator end);
+ // Add STACK_FRAME_ENTRY to the module.
+ //
+ // This module owns all StackFrameEntry objects added with this
+ // function: destroying the module destroys them as well.
+ void AddStackFrameEntry(StackFrameEntry *stack_frame_entry);
+
// If this module has a file named NAME, return a pointer to it. If
// it has none, then create one and return a pointer to the new
// file. This module owns all File objects created using these
@@ -151,17 +186,26 @@ class Module {
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.)
+ // VEC. The pointed-to Functions are still owned by this module.
+ // (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.)
+ // module, sorted by name. The pointed-to Files are still owned by
+ // this module. (Since this is effectively a copy of the file list,
+ // this is mostly useful for testing; other uses should probably get
+ // a more appropriate interface.)
void GetFiles(vector<File *> *vec);
+ // Clear VEC and fill it with pointers to the StackFrameEntry
+ // objects that have been added to this module. (Since this is
+ // effectively a copy of the stack frame entry list, this is mostly
+ // useful for testing; other uses should probably get
+ // a more appropriate interface.)
+ void GetStackFrameEntries(vector<StackFrameEntry *> *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
@@ -185,6 +229,11 @@ private:
// errno to find the appropriate cause. Return false.
static bool ReportError();
+ // Write RULE_MAP to STREAM, in the form appropriate for 'STACK CFI'
+ // records, without a final newline. Return true if all goes well;
+ // if an error occurs, return false, and leave errno set.
+ static bool WriteRuleMap(const RuleMap &rule_map, FILE *stream);
+
// Module header entries.
string name_, os_, architecture_, id_;
@@ -208,6 +257,10 @@ private:
// point to.
FileByNameMap files_; // This module's source files.
vector<Function *> functions_; // This module's functions.
+
+ // The module owns all the call frame info entries that have been
+ // added to it.
+ vector<StackFrameEntry *> stack_frame_entries_;
};
} // namespace google_breakpad
diff --git a/src/common/linux/module_unittest.cc b/src/common/linux/module_unittest.cc
index d289f132..771d91e5 100644
--- a/src/common/linux/module_unittest.cc
+++ b/src/common/linux/module_unittest.cc
@@ -42,9 +42,10 @@
#include "breakpad_googletest_includes.h"
#include "common/linux/module.h"
+using google_breakpad::Module;
using std::string;
using std::vector;
-using google_breakpad::Module;
+using testing::ContainerEq;
// Return a FILE * referring to a temporary file that will be deleted
// automatically when the stream is closed or the program exits.
@@ -162,6 +163,17 @@ TEST(Write, RelativeLoadAddress) {
m.AddFunction(function);
+ // Some stack information.
+ Module::StackFrameEntry *entry = new Module::StackFrameEntry();
+ entry->address = 0x30f9e5c83323973dULL;
+ entry->size = 0x49fc9ca7c7c13dc2ULL;
+ entry->initial_rules[".cfa"] = "he was a handsome man";
+ entry->initial_rules["and"] = "what i want to know is";
+ entry->rule_changes[0x30f9e5c83323973eULL]["how"] =
+ "do you like your blueeyed boy";
+ entry->rule_changes[0x30f9e5c83323973eULL]["Mister"] = "Death";
+ m.AddStackFrameEntry(entry);
+
m.Write(f);
checked_fflush(f);
rewind(f);
@@ -173,7 +185,13 @@ TEST(Write, RelativeLoadAddress) {
"FUNC 9410dc39a798c580 2922088f98d3f6fc e5e9aa008bd5f0d0"
" A_FLIBBERTIJIBBET::a_will_o_the_wisp(a clown)\n"
"b03cc3106d47eb91 cf621b8d324d0eb 67519080 0\n"
- "9410dc39a798c580 1c2be6d6c5af2611 41676901 1\n",
+ "9410dc39a798c580 1c2be6d6c5af2611 41676901 1\n"
+ "STACK CFI INIT 6434d177ce326ca 49fc9ca7c7c13dc2"
+ " .cfa: he was a handsome man"
+ " and: what i want to know is\n"
+ "STACK CFI 6434d177ce326cb"
+ " Mister: Death"
+ " how: do you like your blueeyed boy\n",
contents.c_str());
}
@@ -274,6 +292,96 @@ TEST(Construct, AddFunctions) {
EXPECT_EQ((size_t) 2, vec.size());
}
+TEST(Construct, AddFrames) {
+ FILE *f = checked_tmpfile();
+ Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
+
+ // First STACK CFI entry, with no initial rules or deltas.
+ Module::StackFrameEntry *entry1 = new Module::StackFrameEntry();
+ entry1->address = 0xddb5f41285aa7757ULL;
+ entry1->size = 0x1486493370dc5073ULL;
+ m.AddStackFrameEntry(entry1);
+
+ // Second STACK CFI entry, with initial rules but no deltas.
+ Module::StackFrameEntry *entry2 = new Module::StackFrameEntry();
+ entry2->address = 0x8064f3af5e067e38ULL;
+ entry2->size = 0x0de2a5ee55509407ULL;
+ entry2->initial_rules[".cfa"] = "I think that I shall never see";
+ entry2->initial_rules["stromboli"] = "a poem lovely as a tree";
+ entry2->initial_rules["cannoli"] = "a tree whose hungry mouth is prest";
+ m.AddStackFrameEntry(entry2);
+
+ // Third STACK CFI entry, with initial rules and deltas.
+ Module::StackFrameEntry *entry3 = new Module::StackFrameEntry();
+ entry3->address = 0x5e8d0db0a7075c6cULL;
+ entry3->size = 0x1c7edb12a7aea229ULL;
+ entry3->initial_rules[".cfa"] = "Whose woods are these";
+ entry3->rule_changes[0x47ceb0f63c269d7fULL]["calzone"] =
+ "the village though";
+ entry3->rule_changes[0x47ceb0f63c269d7fULL]["cannoli"] =
+ "he will not see me stopping here";
+ entry3->rule_changes[0x36682fad3763ffffULL]["stromboli"] =
+ "his house is in";
+ entry3->rule_changes[0x36682fad3763ffffULL][".cfa"] =
+ "I think I know";
+ m.AddStackFrameEntry(entry3);
+
+ // Check that Write writes STACK CFI records properly.
+ 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"
+ "STACK CFI INIT ddb5f41285aa7757 1486493370dc5073 \n"
+ "STACK CFI INIT 8064f3af5e067e38 de2a5ee55509407"
+ " .cfa: I think that I shall never see"
+ " cannoli: a tree whose hungry mouth is prest"
+ " stromboli: a poem lovely as a tree\n"
+ "STACK CFI INIT 5e8d0db0a7075c6c 1c7edb12a7aea229"
+ " .cfa: Whose woods are these\n"
+ "STACK CFI 36682fad3763ffff"
+ " .cfa: I think I know"
+ " stromboli: his house is in\n"
+ "STACK CFI 47ceb0f63c269d7f"
+ " calzone: the village though"
+ " cannoli: he will not see me stopping here\n",
+ contents.c_str());
+
+ // Check that GetStackFrameEntries works.
+ vector<Module::StackFrameEntry *> entries;
+ m.GetStackFrameEntries(&entries);
+ ASSERT_EQ(3U, entries.size());
+ // Check first entry.
+ EXPECT_EQ(0xddb5f41285aa7757ULL, entries[0]->address);
+ EXPECT_EQ(0x1486493370dc5073ULL, entries[0]->size);
+ ASSERT_EQ(0U, entries[0]->initial_rules.size());
+ ASSERT_EQ(0U, entries[0]->rule_changes.size());
+ // Check second entry.
+ EXPECT_EQ(0x8064f3af5e067e38ULL, entries[1]->address);
+ EXPECT_EQ(0x0de2a5ee55509407ULL, entries[1]->size);
+ ASSERT_EQ(3U, entries[1]->initial_rules.size());
+ Module::RuleMap entry2_initial;
+ entry2_initial[".cfa"] = "I think that I shall never see";
+ entry2_initial["stromboli"] = "a poem lovely as a tree";
+ entry2_initial["cannoli"] = "a tree whose hungry mouth is prest";
+ EXPECT_THAT(entries[1]->initial_rules, ContainerEq(entry2_initial));
+ ASSERT_EQ(0U, entries[1]->rule_changes.size());
+ // Check third entry.
+ EXPECT_EQ(0x5e8d0db0a7075c6cULL, entries[2]->address);
+ EXPECT_EQ(0x1c7edb12a7aea229ULL, entries[2]->size);
+ Module::RuleMap entry3_initial;
+ entry3_initial[".cfa"] = "Whose woods are these";
+ EXPECT_THAT(entries[2]->initial_rules, ContainerEq(entry3_initial));
+ Module::RuleChangeMap entry3_changes;
+ entry3_changes[0x36682fad3763ffffULL][".cfa"] = "I think I know";
+ entry3_changes[0x36682fad3763ffffULL]["stromboli"] = "his house is in";
+ entry3_changes[0x47ceb0f63c269d7fULL]["calzone"] = "the village though";
+ entry3_changes[0x47ceb0f63c269d7fULL]["cannoli"] =
+ "he will not see me stopping here";
+ EXPECT_THAT(entries[2]->rule_changes, ContainerEq(entry3_changes));
+}
+
TEST(Construct, UniqueFiles) {
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
Module::File *file1 = m.FindFile("foo");