aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjimblandy <jimblandy@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-03-16 16:37:50 +0000
committerjimblandy <jimblandy@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-03-16 16:37:50 +0000
commita76aaa1442d765410da36d743ef92748ea1b815e (patch)
tree885e4efe8329d3362869680e0e6156fbb9ff0d65
parentBreakpad DWARF parser: Add support for parsing .eh_frame encoded pointers. (diff)
downloadbreakpad-a76aaa1442d765410da36d743ef92748ea1b815e.tar.xz
Breakpad Linux dumper: Parse the .eh_frame section.
Extend google_breakpad::CFISection with the ability to produce .eh_frame data. Entry headers have a different format, and pointers can be encoded in new and fascinating ways. Extend dwarf2reader::CallFrameInfo to be able to parse either DWARF CFI or .eh_frame data, as determined by an argument to the constructor. Cope with variations in header formats, encoded pointers, and additional data in 'z' augmentation data blocks. Extend the unit tests appropriately. Extend dump_syms to look for a .eh_frame section, and if it is present, find the necessary base addresess and parse its contents. There's no need for DwarfCFIToModule to check the version numbers; if CallFrameInfo can parse it, DwarfCFIToModule should be able to handle it. Adjust tests accordingly. a=jimblandy, r=nealsid git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@552 4c0a9323-5329-0410-9bdc-e9ce6186880e
-rw-r--r--src/common/dwarf/bytereader.cc39
-rw-r--r--src/common/dwarf/bytereader_unittest.cc14
-rw-r--r--src/common/dwarf/cfi_assembler.cc107
-rw-r--r--src/common/dwarf/cfi_assembler.h115
-rw-r--r--src/common/dwarf/dwarf2enums.h14
-rw-r--r--src/common/dwarf/dwarf2reader.cc359
-rw-r--r--src/common/dwarf/dwarf2reader.h242
-rw-r--r--src/common/dwarf/dwarf2reader_cfi_unittest.cc529
-rw-r--r--src/common/linux/dump_symbols.cc36
-rw-r--r--src/common/linux/dwarf_cfi_to_module.cc10
-rw-r--r--src/common/linux/dwarf_cfi_to_module_unittest.cc14
11 files changed, 1336 insertions, 143 deletions
diff --git a/src/common/dwarf/bytereader.cc b/src/common/dwarf/bytereader.cc
index 7d784659..a9b0020f 100644
--- a/src/common/dwarf/bytereader.cc
+++ b/src/common/dwarf/bytereader.cc
@@ -82,13 +82,15 @@ uint64 ByteReader::ReadInitialLength(const char* start, size_t* len) {
bool ByteReader::ValidEncoding(DwarfPointerEncoding encoding) const {
if (encoding == DW_EH_PE_omit) return true;
if (encoding == DW_EH_PE_aligned) return true;
- if (DwarfPointerEncoding(encoding & 0x7) > DW_EH_PE_udata8) return false;
- if (DwarfPointerEncoding(encoding & 0x70) > DW_EH_PE_funcrel) return false;
+ if ((encoding & 0x7) > DW_EH_PE_udata8)
+ return false;
+ if ((encoding & 0x70) > DW_EH_PE_funcrel)
+ return false;
return true;
}
bool ByteReader::UsableEncoding(DwarfPointerEncoding encoding) const {
- switch (DwarfPointerEncoding(encoding & 0x70)) {
+ switch (encoding & 0x70) {
case DW_EH_PE_absptr: return true;
case DW_EH_PE_pcrel: return have_section_base_;
case DW_EH_PE_textrel: return have_text_base_;
@@ -101,13 +103,14 @@ bool ByteReader::UsableEncoding(DwarfPointerEncoding encoding) const {
uint64 ByteReader::ReadEncodedPointer(const char *buffer,
DwarfPointerEncoding encoding,
size_t *len) const {
- // This is what the GCC unwinder does.
- if (encoding == DW_EH_PE_omit) {
- *len = 0;
- return 0;
- }
-
- // Aligned pointers are always absolute machine-sized and -signed pointers.
+ // UsableEncoding doesn't approve of DW_EH_PE_omit, so we shouldn't
+ // see it here.
+ assert(encoding != DW_EH_PE_omit);
+
+ // The Linux Standards Base 4.0 does not make this clear, but the
+ // GNU tools (gcc/unwind-pe.h; readelf/dwarf.c; gdb/dwarf2-frame.c)
+ // agree that aligned pointers are always absolute, machine-sized,
+ // machine-signed pointers.
if (encoding == DW_EH_PE_aligned) {
assert(have_section_base_);
@@ -135,11 +138,17 @@ uint64 ByteReader::ReadEncodedPointer(const char *buffer,
// Extract the value first, ignoring whether it's a pointer or an
// offset relative to some base.
uint64 offset;
- switch (DwarfPointerEncoding(encoding & 0x0f)) {
+ switch (encoding & 0x0f) {
case DW_EH_PE_absptr:
- // As the low nybble value, DW_EH_PE_absptr simply means a
- // machine-sized and -signed address; it doesn't mean it's absolute.
- // So it is correct for us to relocate after this.
+ // DW_EH_PE_absptr is weird, as it is used as a meaningful value for
+ // both the high and low nybble of encoding bytes. When it appears in
+ // the high nybble, it means that the pointer is absolute, not an
+ // offset from some base address. When it appears in the low nybble,
+ // as here, it means that the pointer is stored as a normal
+ // machine-sized and machine-signed address. A low nybble of
+ // DW_EH_PE_absptr does not imply that the pointer is absolute; it is
+ // correct for us to treat the value as an offset from a base address
+ // if the upper nybble is not DW_EH_PE_absptr.
offset = ReadAddress(buffer);
*len = AddressSize();
break;
@@ -193,7 +202,7 @@ uint64 ByteReader::ReadEncodedPointer(const char *buffer,
// Find the appropriate base address.
uint64 base;
- switch (DwarfPointerEncoding(encoding & 0x70)) {
+ switch (encoding & 0x70) {
case DW_EH_PE_absptr:
base = 0;
break;
diff --git a/src/common/dwarf/bytereader_unittest.cc b/src/common/dwarf/bytereader_unittest.cc
index 5e854c6e..729c54a8 100644
--- a/src/common/dwarf/bytereader_unittest.cc
+++ b/src/common/dwarf/bytereader_unittest.cc
@@ -56,6 +56,7 @@ struct ReaderFixture {
};
class Reader: public ReaderFixture, public Test { };
+class ReaderDeathTest: public ReaderFixture, public Test { };
TEST_F(Reader, SimpleConstructor) {
ByteReader reader(ENDIANNESS_BIG);
@@ -373,13 +374,13 @@ TEST_F(Reader, ValidEncodings) {
EXPECT_FALSE(reader.ValidEncoding(DwarfPointerEncoding(0xd0)));
}
-TEST_F(Reader, DW_EH_PE_omit) {
+TEST_F(ReaderDeathTest, DW_EH_PE_omit) {
static const char data[1] = { 42 };
ByteReader reader(ENDIANNESS_BIG);
reader.SetAddressSize(4);
- EXPECT_EQ(0U, reader.ReadEncodedPointer(data, dwarf2reader::DW_EH_PE_omit,
- &pointer_size));
- EXPECT_EQ(0U, pointer_size);
+ EXPECT_DEATH(reader.ReadEncodedPointer(data, dwarf2reader::DW_EH_PE_omit,
+ &pointer_size),
+ "encoding != DW_EH_PE_omit");
}
TEST_F(Reader, DW_EH_PE_absptr4) {
@@ -561,6 +562,7 @@ TEST(UsableBase, CFI) {
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_textrel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_datarel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_funcrel));
+ EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_omit));
EXPECT_FALSE(reader.UsableEncoding(DwarfPointerEncoding(0x60)));
}
@@ -572,6 +574,7 @@ TEST(UsableBase, Text) {
EXPECT_TRUE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_textrel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_datarel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_funcrel));
+ EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_omit));
EXPECT_FALSE(reader.UsableEncoding(DwarfPointerEncoding(0x60)));
}
@@ -583,6 +586,7 @@ TEST(UsableBase, Data) {
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_textrel));
EXPECT_TRUE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_datarel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_funcrel));
+ EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_omit));
EXPECT_FALSE(reader.UsableEncoding(DwarfPointerEncoding(0x60)));
}
@@ -594,6 +598,7 @@ TEST(UsableBase, Function) {
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_textrel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_datarel));
EXPECT_TRUE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_funcrel));
+ EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_omit));
EXPECT_FALSE(reader.UsableEncoding(DwarfPointerEncoding(0x60)));
}
@@ -606,6 +611,7 @@ TEST(UsableBase, ClearFunction) {
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_textrel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_datarel));
EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_funcrel));
+ EXPECT_FALSE(reader.UsableEncoding(dwarf2reader::DW_EH_PE_omit));
EXPECT_FALSE(reader.UsableEncoding(DwarfPointerEncoding(0x60)));
}
diff --git a/src/common/dwarf/cfi_assembler.cc b/src/common/dwarf/cfi_assembler.cc
index 07995458..110aa2e7 100644
--- a/src/common/dwarf/cfi_assembler.cc
+++ b/src/common/dwarf/cfi_assembler.cc
@@ -33,11 +33,13 @@
// See cfi_assembler.h for details.
#include <cassert>
+#include <stdlib.h>
#include "common/dwarf/cfi_assembler.h"
-#include "common/dwarf/dwarf2enums.h"
namespace google_breakpad {
+
+using dwarf2reader::DwarfPointerEncoding;
CFISection &CFISection::CIEHeader(u_int64_t code_alignment_factor,
int data_alignment_factor,
@@ -47,16 +49,21 @@ CFISection &CFISection::CIEHeader(u_int64_t code_alignment_factor,
bool dwarf64) {
assert(!entry_length_);
entry_length_ = new PendingLength();
+ in_fde_ = false;
if (dwarf64) {
D32(0xffffffff);
D64(entry_length_->length);
entry_length_->start = Here();
- D64(0xffffffffffffffffULL); // CIE distinguished value
+ // Write the CIE distinguished value. In .debug_frame sections, it's
+ // ~0; in .eh_frame sections, it's zero.
+ D64(eh_frame_ ? 0 : ~(u_int64_t)0);
} else {
D32(entry_length_->length);
entry_length_->start = Here();
- D32(0xffffffff); // CIE distinguished value
+ // Write the CIE distinguished value. In .debug_frame sections, it's
+ // ~0; in .eh_frame sections, it's zero.
+ D32(eh_frame_ ? 0 : ~(u_int32_t)0);
}
D8(version);
AppendCString(augmentation);
@@ -75,19 +82,32 @@ CFISection &CFISection::FDEHeader(Label cie_pointer,
bool dwarf64) {
assert(!entry_length_);
entry_length_ = new PendingLength();
+ in_fde_ = true;
+ fde_start_address_ = initial_location;
if (dwarf64) {
D32(0xffffffff);
D64(entry_length_->length);
entry_length_->start = Here();
- D64(cie_pointer);
+ if (eh_frame_)
+ D64(Here() - cie_pointer);
+ else
+ D64(cie_pointer);
} else {
D32(entry_length_->length);
entry_length_->start = Here();
- D32(cie_pointer);
+ if (eh_frame_)
+ D32(Here() - cie_pointer);
+ else
+ D32(cie_pointer);
}
- Append(endianness(), address_size_, initial_location);
- Append(endianness(), address_size_, address_range);
+ EncodedPointer(initial_location);
+ // The FDE length in an .eh_frame section uses the same encoding as the
+ // initial location, but ignores the base address (selected by the upper
+ // nybble of the encoding), as it's a length, not an address that can be
+ // made relative.
+ EncodedPointer(address_range,
+ DwarfPointerEncoding(pointer_encoding_ & 0x0f));
return *this;
}
@@ -97,7 +117,80 @@ CFISection &CFISection::FinishEntry() {
entry_length_->length = Here() - entry_length_->start;
delete entry_length_;
entry_length_ = NULL;
+ in_fde_ = false;
return *this;
}
+CFISection &CFISection::EncodedPointer(u_int64_t address,
+ DwarfPointerEncoding encoding,
+ const EncodedPointerBases &bases) {
+ // Omitted data is extremely easy to emit.
+ if (encoding == dwarf2reader::DW_EH_PE_omit)
+ return *this;
+
+ // If (encoding & dwarf2reader::DW_EH_PE_indirect) != 0, then we assume
+ // that ADDRESS is the address at which the pointer is stored --- in
+ // other words, that bit has no effect on how we write the pointer.
+ encoding = DwarfPointerEncoding(encoding & ~dwarf2reader::DW_EH_PE_indirect);
+
+ // Find the base address to which this pointer is relative. The upper
+ // nybble of the encoding specifies this.
+ u_int64_t base;
+ switch (encoding & 0xf0) {
+ case dwarf2reader::DW_EH_PE_absptr: base = 0; break;
+ case dwarf2reader::DW_EH_PE_pcrel: base = bases.cfi + Size(); break;
+ case dwarf2reader::DW_EH_PE_textrel: base = bases.text; break;
+ case dwarf2reader::DW_EH_PE_datarel: base = bases.data; break;
+ case dwarf2reader::DW_EH_PE_funcrel: base = fde_start_address_; break;
+ case dwarf2reader::DW_EH_PE_aligned: base = 0; break;
+ default: abort();
+ };
+
+ // Make ADDRESS relative. Yes, this is appropriate even for "absptr"
+ // values; see gcc/unwind-pe.h.
+ address -= base;
+
+ // Align the pointer, if required.
+ if ((encoding & 0xf0) == dwarf2reader::DW_EH_PE_aligned)
+ Align(AddressSize());
+
+ // Append ADDRESS to this section in the appropriate form. For the
+ // fixed-width forms, we don't need to differentiate between signed and
+ // unsigned encodings, because ADDRESS has already been extended to 64
+ // bits before it was passed to us.
+ switch (encoding & 0x0f) {
+ case dwarf2reader::DW_EH_PE_absptr:
+ Address(address);
+ break;
+
+ case dwarf2reader::DW_EH_PE_uleb128:
+ ULEB128(address);
+ break;
+
+ case dwarf2reader::DW_EH_PE_sleb128:
+ LEB128(address);
+ break;
+
+ case dwarf2reader::DW_EH_PE_udata2:
+ case dwarf2reader::DW_EH_PE_sdata2:
+ D16(address);
+ break;
+
+ case dwarf2reader::DW_EH_PE_udata4:
+ case dwarf2reader::DW_EH_PE_sdata4:
+ D32(address);
+ break;
+
+ case dwarf2reader::DW_EH_PE_udata8:
+ case dwarf2reader::DW_EH_PE_sdata8:
+ D64(address);
+ break;
+
+ default:
+ abort();
+ }
+
+ return *this;
};
+
+} // namespace google_breakpad
diff --git a/src/common/dwarf/cfi_assembler.h b/src/common/dwarf/cfi_assembler.h
index 449a8078..f5bf9710 100644
--- a/src/common/dwarf/cfi_assembler.h
+++ b/src/common/dwarf/cfi_assembler.h
@@ -39,11 +39,13 @@
#include <string>
+#include "common/dwarf/dwarf2enums.h"
#include "google_breakpad/common/breakpad_types.h"
#include "processor/test_assembler.h"
namespace google_breakpad {
+using dwarf2reader::DwarfPointerEncoding;
using google_breakpad::TestAssembler::Endianness;
using google_breakpad::TestAssembler::Label;
using google_breakpad::TestAssembler::Section;
@@ -51,11 +53,53 @@ using std::string;
class CFISection: public Section {
public:
+
+ // CFI augmentation strings beginning with 'z', defined by the
+ // Linux/IA-64 C++ ABI, can specify interesting encodings for
+ // addresses appearing in FDE headers and call frame instructions (and
+ // for additional fields whose presence the augmentation string
+ // specifies). In particular, pointers can be specified to be relative
+ // to various base address: the start of the .text section, the
+ // location holding the address itself, and so on. These allow the
+ // frame data to be position-independent even when they live in
+ // write-protected pages. These variants are specified at the
+ // following two URLs:
+ //
+ // http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html
+ // http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
+ //
+ // CFISection leaves the production of well-formed 'z'-augmented CIEs and
+ // FDEs to the user, but does provide EncodedPointer, to emit
+ // properly-encoded addresses for a given pointer encoding.
+ // EncodedPointer uses an instance of this structure to find the base
+ // addresses it should use; you can establish a default for all encoded
+ // pointers appended to this section with SetEncodedPointerBases.
+ struct EncodedPointerBases {
+ EncodedPointerBases() : cfi(), text(), data() { }
+
+ // The starting address of this CFI section in memory, for
+ // DW_EH_PE_pcrel. DW_EH_PE_pcrel pointers may only be used in data
+ // that has is loaded into the program's address space.
+ u_int64_t cfi;
+
+ // The starting address of this file's .text section, for DW_EH_PE_textrel.
+ u_int64_t text;
+
+ // The starting address of this file's .got or .eh_frame_hdr section,
+ // for DW_EH_PE_datarel.
+ u_int64_t data;
+ };
+
// Create a CFISection whose endianness is ENDIANNESS, and where
- // machine addresses are ADDRESS_SIZE bytes long.
- CFISection(Endianness endianness, size_t address_size)
- : Section(endianness), address_size_(address_size),
- entry_length_(NULL) {
+ // machine addresses are ADDRESS_SIZE bytes long. If EH_FRAME is
+ // true, use the .eh_frame format, as described by the Linux
+ // Standards Base Core Specification, instead of the DWARF CFI
+ // format.
+ CFISection(Endianness endianness, size_t address_size,
+ bool eh_frame = false)
+ : Section(endianness), address_size_(address_size), eh_frame_(eh_frame),
+ pointer_encoding_(dwarf2reader::DW_EH_PE_absptr),
+ encoded_pointer_bases_(), entry_length_(NULL), in_fde_(false) {
// The 'start', 'Here', and 'Mark' members of a CFISection all refer
// to section offsets.
start() = 0;
@@ -64,6 +108,22 @@ class CFISection: public Section {
// Return this CFISection's address size.
size_t AddressSize() const { return address_size_; }
+ // Return true if this CFISection uses the .eh_frame format, or
+ // false if it contains ordinary DWARF CFI data.
+ bool ContainsEHFrame() const { return eh_frame_; }
+
+ // Use ENCODING for pointers in calls to FDEHeader and EncodedPointer.
+ void SetPointerEncoding(DwarfPointerEncoding encoding) {
+ pointer_encoding_ = encoding;
+ }
+
+ // Use the addresses in BASES as the base addresses for encoded
+ // pointers in subsequent calls to FDEHeader or EncodedPointer.
+ // This function makes a copy of BASES.
+ void SetEncodedPointerBases(const EncodedPointerBases &bases) {
+ encoded_pointer_bases_ = bases;
+ }
+
// Append a Common Information Entry header to this section with the
// given values. If dwarf64 is true, use the 64-bit DWARF initial
// length format for the CIE's initial length. Return a reference to
@@ -109,6 +169,35 @@ class CFISection: public Section {
return *this;
}
+ // Append ADDRESS to this section, in the appropriate size and
+ // endianness. Return a reference to this section.
+ CFISection &Address(u_int64_t address) {
+ Section::Append(endianness(), address_size_, address);
+ return *this;
+ }
+ CFISection &Address(Label address) {
+ Section::Append(endianness(), address_size_, address);
+ return *this;
+ }
+
+ // Append ADDRESS to this section, using ENCODING and BASES. ENCODING
+ // defaults to this section's default encoding, established by
+ // SetPointerEncoding. BASES defaults to this section's bases, set by
+ // SetEncodedPointerBases. If the DW_EH_PE_indirect bit is set in the
+ // encoding, assume that ADDRESS is where the true address is stored.
+ // Return a reference to this section.
+ //
+ // (C++ doesn't let me use default arguments here, because I want to
+ // refer to members of *this in the default argument expression.)
+ CFISection &EncodedPointer(u_int64_t address) {
+ return EncodedPointer(address, pointer_encoding_, encoded_pointer_bases_);
+ }
+ CFISection &EncodedPointer(u_int64_t address, DwarfPointerEncoding encoding) {
+ return EncodedPointer(address, encoding, encoded_pointer_bases_);
+ }
+ CFISection &EncodedPointer(u_int64_t address, DwarfPointerEncoding encoding,
+ const EncodedPointerBases &bases);
+
// Restate some member functions, to keep chaining working nicely.
CFISection &Mark(Label *label) { Section::Mark(label); return *this; }
CFISection &D8(u_int8_t v) { Section::D8(v); return *this; }
@@ -133,6 +222,16 @@ class CFISection: public Section {
// The size of a machine address for the data in this section.
size_t address_size_;
+ // If true, we are generating a Linux .eh_frame section, instead of
+ // a standard DWARF .debug_frame section.
+ bool eh_frame_;
+
+ // The encoding to use for FDE pointers.
+ DwarfPointerEncoding pointer_encoding_;
+
+ // The base addresses to use when emitting encoded pointers.
+ EncodedPointerBases encoded_pointer_bases_;
+
// The length value for the current entry.
//
// Oddly, this must be dynamically allocated. Labels never get new
@@ -142,6 +241,14 @@ class CFISection: public Section {
// headers and track their positions. The alternative is explicit
// destructor invocation and a placement new. Ick.
PendingLength *entry_length_;
+
+ // True if we are currently emitting an FDE --- that is, we have
+ // called FDEHeader but have not yet called FinishEntry.
+ bool in_fde_;
+
+ // If in_fde_ is true, this is its starting address. We use this for
+ // emitting DW_EH_PE_funcrel pointers.
+ u_int64_t fde_start_address_;
};
} // namespace google_breakpad
diff --git a/src/common/dwarf/dwarf2enums.h b/src/common/dwarf/dwarf2enums.h
index 8444821a..8ac28eb2 100644
--- a/src/common/dwarf/dwarf2enums.h
+++ b/src/common/dwarf/dwarf2enums.h
@@ -1,3 +1,5 @@
+// -*- mode: c++ -*-
+
// Copyright (c) 2010 Google Inc. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -588,12 +590,22 @@ enum DwarfPointerEncoding
DW_EH_PE_sdata2 = 0x0A,
DW_EH_PE_sdata4 = 0x0B,
DW_EH_PE_sdata8 = 0x0C,
- DW_EH_PE_signed = 0x08,
DW_EH_PE_pcrel = 0x10,
DW_EH_PE_textrel = 0x20,
DW_EH_PE_datarel = 0x30,
DW_EH_PE_funcrel = 0x40,
DW_EH_PE_aligned = 0x50,
+
+ // The GNU toolchain sources define this enum value as well,
+ // simply to help classify the lower nybble values into signed and
+ // unsigned groups.
+ DW_EH_PE_signed = 0x08,
+
+ // This is not documented in LSB 4.0, but it is used in both the
+ // Linux and OS X toolchains. It can be added to any other
+ // encoding (except DW_EH_PE_aligned), and indicates that the
+ // encoded value represents the address at which the true address
+ // is stored, not the true address itself.
DW_EH_PE_indirect = 0x80
};
diff --git a/src/common/dwarf/dwarf2reader.cc b/src/common/dwarf/dwarf2reader.cc
index fb6d7c4d..92ceb4a1 100644
--- a/src/common/dwarf/dwarf2reader.cc
+++ b/src/common/dwarf/dwarf2reader.cc
@@ -1245,6 +1245,8 @@ class CallFrameInfo::State {
// 'o' unsigned LEB128 offset (OPERANDS->offset)
// 's' signed LEB128 offset (OPERANDS->signed_offset)
// 'a' machine-size address (OPERANDS->offset)
+ // (If the CIE has a 'z' augmentation string, 'a' uses the
+ // encoding specified by the 'R' argument.)
// '1' a one-byte offset (OPERANDS->offset)
// '2' a two-byte offset (OPERANDS->offset)
// '4' a four-byte offset (OPERANDS->offset)
@@ -1381,9 +1383,11 @@ bool CallFrameInfo::State::ParseOperands(const char *format,
break;
case 'a':
- if (reader_->AddressSize() > bytes_left) return ReportIncomplete();
- operands->offset = reader_->ReadAddress(cursor_);
- cursor_ += reader_->AddressSize();
+ operands->offset =
+ reader_->ReadEncodedPointer(cursor_, entry_->cie->pointer_encoding,
+ &len);
+ if (len > bytes_left) return ReportIncomplete();
+ cursor_ += len;
break;
case '1':
@@ -1773,15 +1777,24 @@ bool CallFrameInfo::ReadEntryPrologue(const char *cursor, Entry *entry) {
entry->kind = kUnknown;
entry->end = NULL;
- // Read the initial length. This sets reader_'s offset size. The length
- // could be something like (uint64)-1, so we have to do two comparisons
- // here.
+ // Read the initial length. This sets reader_'s offset size.
size_t length_size;
uint64 length = reader_->ReadInitialLength(cursor, &length_size);
- if (length_size > size_t(buffer_end - cursor) ||
- length > size_t(buffer_end - (cursor + length_size)))
+ if (length_size > size_t(buffer_end - cursor))
return ReportIncomplete(entry);
cursor += length_size;
+
+ // In a .eh_frame section, a length of zero marks the end of the series
+ // of entries.
+ if (length == 0 && eh_frame_) {
+ entry->kind = kTerminator;
+ entry->end = cursor;
+ return true;
+ }
+
+ // Validate the length.
+ if (length > size_t(buffer_end - cursor))
+ return ReportIncomplete(entry);
// The length is the number of bytes after the initial length field;
// we have that position handy at this point, so compute the end
@@ -1794,16 +1807,37 @@ bool CallFrameInfo::ReadEntryPrologue(const char *cursor, Entry *entry) {
size_t offset_size = reader_->OffsetSize();
if (offset_size > size_t(entry->end - cursor)) return ReportIncomplete(entry);
entry->id = reader_->ReadOffset(cursor);
- cursor += offset_size;
+
+ // Don't advance cursor past id field yet; in .eh_frame data we need
+ // the id's position to compute the section offset of an FDE's CIE.
// Now we can decide what kind of entry this is.
- if (offset_size == 4)
- entry->kind = (entry->id == 0xffffffff) ? kCIE : kFDE;
- else {
- assert(offset_size == 8);
- entry->kind = (entry->id == 0xffffffffffffffffULL) ? kCIE : kFDE;
+ if (eh_frame_) {
+ // In .eh_frame data, an ID of zero marks the entry as a CIE, and
+ // anything else is an offset from the id field of the FDE to the start
+ // of the CIE.
+ if (entry->id == 0) {
+ entry->kind = kCIE;
+ } else {
+ entry->kind = kFDE;
+ // Turn the offset from the id into an offset from the buffer's start.
+ entry->id = (cursor - buffer_) - entry->id;
+ }
+ } else {
+ // In DWARF CFI data, an ID of ~0 (of the appropriate width, given the
+ // offset size for the entry) marks the entry as a CIE, and anything
+ // else is the offset of the CIE from the beginning of the section.
+ if (offset_size == 4)
+ entry->kind = (entry->id == 0xffffffff) ? kCIE : kFDE;
+ else {
+ assert(offset_size == 8);
+ entry->kind = (entry->id == 0xffffffffffffffffULL) ? kCIE : kFDE;
+ }
}
+ // Now advance cursor past the id.
+ cursor += offset_size;
+
// The fields specific to this kind of entry start here.
entry->fields = cursor;
@@ -1824,6 +1858,8 @@ bool CallFrameInfo::ReadCIEFields(CIE *cie) {
cie->code_alignment_factor = 0;
cie->data_alignment_factor = 0;
cie->return_address_register = 0;
+ cie->has_z_augmentation = false;
+ cie->pointer_encoding = DW_EH_PE_absptr;
cie->instructions = 0;
// Parse the version number.
@@ -1833,10 +1869,19 @@ bool CallFrameInfo::ReadCIEFields(CIE *cie) {
cursor++;
// If we don't recognize the version, we can't parse any more fields
- // of the CIE.
- if (cie->version < 1 || 3 < cie->version) {
- reporter_->UnrecognizedVersion(cie->offset, cie->version);
- return false;
+ // of the CIE. For DWARF CFI, we handle versions 1 through 3 (there
+ // was never a version 2 fo CFI data). For .eh_frame, we handle only
+ // version 1.
+ if (eh_frame_) {
+ if (cie->version != 1) {
+ reporter_->UnrecognizedVersion(cie->offset, cie->version);
+ return false;
+ }
+ } else {
+ if (cie->version < 1 || 3 < cie->version) {
+ reporter_->UnrecognizedVersion(cie->offset, cie->version);
+ return false;
+ }
}
const char *augmentation_start = cursor;
@@ -1848,11 +1893,16 @@ bool CallFrameInfo::ReadCIEFields(CIE *cie) {
// Skip the terminating '\0'.
cursor++;
- // If we don't recognize this augmentation, we can't parse any more
- // fields of the CIE.
- if (!cie->augmentation.empty()) {
- // Augmentations can have arbitrary effects on the form of rest of
- // the content, so we have to give up.
+ // Is this an augmentation we recognize?
+ if (cie->augmentation.empty()) {
+ ; // Stock DWARF CFI.
+ } else if (cie->augmentation[0] == 'z') {
+ // Linux C++ ABI 'z' augmentation, used for exception handling data.
+ cie->has_z_augmentation = true;
+ } else {
+ // Not an augmentation we recognize. Augmentations can have
+ // arbitrary effects on the form of rest of the content, so we
+ // have to give up.
reporter_->UnrecognizedAugmentation(cie->offset, cie->augmentation);
return false;
}
@@ -1878,6 +1928,100 @@ bool CallFrameInfo::ReadCIEFields(CIE *cie) {
cursor += len;
}
+ // If we have a 'z' augmentation string, find the augmentation data and
+ // use the augmentation string to parse it.
+ if (cie->has_z_augmentation) {
+ size_t data_size = reader_->ReadUnsignedLEB128(cursor, &len);
+ if (size_t(cie->end - cursor) < len + data_size)
+ return ReportIncomplete(cie);
+ cursor += len;
+ const char *data = cursor;
+ cursor += data_size;
+ const char *data_end = cursor;
+
+ cie->has_z_lsda = false;
+ cie->has_z_personality = false;
+ cie->has_z_signal_frame = false;
+
+ // Walk the augmentation string, and extract values from the
+ // augmentation data as the string directs.
+ for (size_t i = 1; i < cie->augmentation.size(); i++) {
+ switch (cie->augmentation[i]) {
+ case 'L':
+ // The CIE's augmentation data holds the language-specific data
+ // area pointer's encoding, and the FDE's augmentation data holds
+ // the pointer itself.
+ cie->has_z_lsda = true;
+ // Fetch the LSDA encoding from the augmentation data.
+ if (data >= data_end) return ReportIncomplete(cie);
+ cie->lsda_encoding = DwarfPointerEncoding(*data++);
+ if (!reader_->ValidEncoding(cie->lsda_encoding)) {
+ reporter_->InvalidPointerEncoding(cie->offset, cie->lsda_encoding);
+ return false;
+ }
+ // Don't check if the encoding is usable here --- we haven't
+ // read the FDE's fields yet, so we're not prepared for
+ // DW_EH_PE_funcrel, although that's a fine encoding for the
+ // LSDA to use, since it appears in the FDE.
+ break;
+
+ case 'P':
+ // The CIE's augmentation data holds the personality routine
+ // pointer's encoding, followed by the pointer itself.
+ cie->has_z_personality = true;
+ // Fetch the personality routine pointer's encoding from the
+ // augmentation data.
+ if (data >= data_end) return ReportIncomplete(cie);
+ cie->personality_encoding = DwarfPointerEncoding(*data++);
+ if (!reader_->ValidEncoding(cie->personality_encoding)) {
+ reporter_->InvalidPointerEncoding(cie->offset,
+ cie->personality_encoding);
+ return false;
+ }
+ if (!reader_->UsableEncoding(cie->personality_encoding)) {
+ reporter_->UnusablePointerEncoding(cie->offset,
+ cie->personality_encoding);
+ return false;
+ }
+ // Fetch the personality routine's pointer itself from the data.
+ cie->personality_address =
+ reader_->ReadEncodedPointer(data, cie->personality_encoding,
+ &len);
+ if (len > size_t(data_end - data))
+ return ReportIncomplete(cie);
+ data += len;
+ break;
+
+ case 'R':
+ // The CIE's augmentation data holds the pointer encoding to use
+ // for addresses in the FDE.
+ if (data >= data_end) return ReportIncomplete(cie);
+ cie->pointer_encoding = DwarfPointerEncoding(*data++);
+ if (!reader_->ValidEncoding(cie->pointer_encoding)) {
+ reporter_->InvalidPointerEncoding(cie->offset,
+ cie->pointer_encoding);
+ return false;
+ }
+ if (!reader_->UsableEncoding(cie->pointer_encoding)) {
+ reporter_->UnusablePointerEncoding(cie->offset,
+ cie->pointer_encoding);
+ return false;
+ }
+ break;
+
+ case 'S':
+ // Frames using this CIE are signal delivery frames.
+ cie->has_z_signal_frame = true;
+ break;
+
+ default:
+ // An augmentation we don't recognize.
+ reporter_->UnrecognizedAugmentation(cie->offset, cie->augmentation);
+ return false;
+ }
+ }
+ }
+
// The CIE's instructions start here.
cie->instructions = cursor;
@@ -1886,19 +2030,66 @@ bool CallFrameInfo::ReadCIEFields(CIE *cie) {
bool CallFrameInfo::ReadFDEFields(FDE *fde) {
const char *cursor = fde->fields;
- size_t address_size = reader_->AddressSize();
+ size_t size;
- // Since both fields are of known size, we can do all bounds
- // checking here.
- if (size_t(fde->end - cursor) < 2 * address_size)
+ fde->address = reader_->ReadEncodedPointer(cursor, fde->cie->pointer_encoding,
+ &size);
+ if (size > size_t(fde->end - cursor))
+ return ReportIncomplete(fde);
+ cursor += size;
+ reader_->SetFunctionBase(fde->address);
+
+ // For the length, we strip off the upper nybble of the encoding used for
+ // the starting address.
+ DwarfPointerEncoding length_encoding =
+ DwarfPointerEncoding(fde->cie->pointer_encoding & 0x0f);
+ fde->size = reader_->ReadEncodedPointer(cursor, length_encoding, &size);
+ if (size > size_t(fde->end - cursor))
return ReportIncomplete(fde);
+ cursor += size;
+
+ // If the CIE has a 'z' augmentation string, then augmentation data
+ // appears here.
+ if (fde->cie->has_z_augmentation) {
+ size_t data_size = reader_->ReadUnsignedLEB128(cursor, &size);
+ if (size_t(fde->end - cursor) < size + data_size)
+ return ReportIncomplete(fde);
+ cursor += size;
+
+ // In the abstract, we should walk the augmentation string, and extract
+ // items from the FDE's augmentation data as we encounter augmentation
+ // string characters that specify their presence: the ordering of items
+ // in the augmentation string determines the arrangement of values in
+ // the augmentation data.
+ //
+ // In practice, there's only ever one value in FDE augmentation data
+ // that we support --- the LSDA pointer --- and we have to bail if we
+ // see any unrecognized augmentation string characters. So if there is
+ // anything here at all, we know what it is, and where it starts.
+ if (fde->cie->has_z_lsda) {
+ // Check whether the LSDA's pointer encoding is usable now: only once
+ // we've parsed the FDE's starting address do we call reader_->
+ // SetFunctionBase, so that the DW_EH_PE_funcrel encoding becomes
+ // usable.
+ if (!reader_->UsableEncoding(fde->cie->lsda_encoding)) {
+ reporter_->UnusablePointerEncoding(fde->cie->offset,
+ fde->cie->lsda_encoding);
+ return false;
+ }
- // Parse the start address and size.
- fde->address = reader_->ReadAddress(cursor);
- fde->size = reader_->ReadAddress(cursor + address_size);
+ fde->lsda_address =
+ reader_->ReadEncodedPointer(cursor, fde->cie->lsda_encoding, &size);
+ if (size > data_size)
+ return ReportIncomplete(fde);
+ // Ideally, we would also complain here if there were unconsumed
+ // augmentation data.
+ }
+
+ cursor += data_size;
+ }
// The FDE's instructions start after those.
- fde->instructions = cursor + 2 * address_size;
+ fde->instructions = cursor;
return true;
}
@@ -1916,18 +2107,35 @@ bool CallFrameInfo::Start() {
cursor = entry_end, all_ok = all_ok && ok) {
FDE fde;
- // Read the entry's prologue.
- if (!ReadEntryPrologue(cursor, &fde))
- // We can't continue processing the section, because we may not
- // have gotten the length.
- return false;
-
// Make it easy to skip this entry with 'continue': assume that
// things are not okay until we've checked all the data, and
// prepare the address of the next entry.
ok = false;
+
+ // Read the entry's prologue.
+ if (!ReadEntryPrologue(cursor, &fde)) {
+ if (!fde.end) {
+ // If we couldn't even figure out this entry's extent, then we
+ // must stop processing entries altogether.
+ all_ok = false;
+ break;
+ }
+ entry_end = fde.end;
+ continue;
+ }
+
+ // The next iteration picks up after this entry.
entry_end = fde.end;
+ // Did we see an .eh_frame terminating mark?
+ if (fde.kind == kTerminator) {
+ // If there appears to be more data left in the section after the
+ // terminating mark, warn the user. But this is just a warning;
+ // we leave all_ok true.
+ if (fde.end < buffer_end) reporter_->EarlyEHTerminator(fde.offset);
+ break;
+ }
+
// In this loop, we skip CIEs. We only parse them fully when we
// parse an FDE that refers to them. This limits our memory
// consumption (beyond the buffer itself) to that needed to
@@ -1973,10 +2181,38 @@ bool CallFrameInfo::Start() {
continue;
}
+ if (cie.has_z_augmentation) {
+ // Report the personality routine address, if we have one.
+ if (cie.has_z_personality) {
+ if (!handler_
+ ->PersonalityRoutine(cie.personality_address,
+ IsIndirectEncoding(cie.personality_encoding)))
+ continue;
+ }
+
+ // Report the language-specific data area address, if we have one.
+ if (cie.has_z_lsda) {
+ if (!handler_
+ ->LanguageSpecificDataArea(fde.lsda_address,
+ IsIndirectEncoding(cie.lsda_encoding)))
+ continue;
+ }
+
+ // If this is a signal-handling frame, report that.
+ if (cie.has_z_signal_frame) {
+ if (!handler_->SignalHandler())
+ continue;
+ }
+ }
+
// Interpret the CIE's instructions, and then the FDE's instructions.
State state(reader_, handler_, reporter_, fde.address);
ok = state.InterpretCIE(cie) && state.InterpretFDE(fde);
+ // Tell the ByteReader that the function start address from the
+ // FDE header is no longer valid.
+ reader_->ClearFunctionBase();
+
// Report the end of the entry.
handler_->End();
}
@@ -1989,9 +2225,11 @@ const char *CallFrameInfo::KindName(EntryKind kind) {
return "entry";
else if (kind == CallFrameInfo::kCIE)
return "common information entry";
- else {
- assert(kind == CallFrameInfo::kFDE);
+ else if (kind == CallFrameInfo::kFDE)
return "frame description entry";
+ else {
+ assert (kind == CallFrameInfo::kTerminator);
+ return ".eh_frame sequence terminator";
}
}
@@ -2001,15 +2239,22 @@ bool CallFrameInfo::ReportIncomplete(Entry *entry) {
}
void CallFrameInfo::Reporter::Incomplete(uint64 offset,
- CallFrameInfo::EntryKind kind) {
+ CallFrameInfo::EntryKind kind) {
fprintf(stderr,
"%s: CFI %s at offset 0x%llx in '%s': entry ends early\n",
filename_.c_str(), CallFrameInfo::KindName(kind), offset,
section_.c_str());
}
+void CallFrameInfo::Reporter::EarlyEHTerminator(uint64 offset) {
+ fprintf(stderr,
+ "%s: CFI at offset 0x%llx in '%s': saw end-of-data marker"
+ " before end of section contents\n",
+ filename_.c_str(), offset, section_.c_str());
+}
+
void CallFrameInfo::Reporter::CIEPointerOutOfRange(uint64 offset,
- uint64 cie_offset) {
+ uint64 cie_offset) {
fprintf(stderr,
"%s: CFI frame description entry at offset 0x%llx in '%s':"
" CIE pointer is out of range: 0x%llx\n",
@@ -2038,6 +2283,22 @@ void CallFrameInfo::Reporter::UnrecognizedAugmentation(uint64 offset,
filename_.c_str(), offset, section_.c_str(), aug.c_str());
}
+void CallFrameInfo::Reporter::InvalidPointerEncoding(uint64 offset,
+ uint8 encoding) {
+ fprintf(stderr,
+ "%s: CFI common information entry at offset 0x%llx in '%s':"
+ " 'z' augmentation specifies invalid pointer encoding: 0x%02x\n",
+ filename_.c_str(), offset, section_.c_str(), encoding);
+}
+
+void CallFrameInfo::Reporter::UnusablePointerEncoding(uint64 offset,
+ uint8 encoding) {
+ fprintf(stderr,
+ "%s: CFI common information entry at offset 0x%llx in '%s':"
+ " 'z' augmentation specifies a pointer encoding for which we have no base address: 0x%02x\n",
+ filename_.c_str(), offset, section_.c_str(), encoding);
+}
+
void CallFrameInfo::Reporter::RestoreInCIE(uint64 offset, uint64 insn_offset) {
fprintf(stderr,
"%s: CFI common information entry at offset 0x%llx in '%s':"
@@ -2047,8 +2308,8 @@ void CallFrameInfo::Reporter::RestoreInCIE(uint64 offset, uint64 insn_offset) {
}
void CallFrameInfo::Reporter::BadInstruction(uint64 offset,
- CallFrameInfo::EntryKind kind,
- uint64 insn_offset) {
+ CallFrameInfo::EntryKind kind,
+ uint64 insn_offset) {
fprintf(stderr,
"%s: CFI %s at offset 0x%llx in section '%s':"
" the instruction at offset 0x%llx is unrecognized\n",
@@ -2057,8 +2318,8 @@ void CallFrameInfo::Reporter::BadInstruction(uint64 offset,
}
void CallFrameInfo::Reporter::NoCFARule(uint64 offset,
- CallFrameInfo::EntryKind kind,
- uint64 insn_offset) {
+ CallFrameInfo::EntryKind kind,
+ uint64 insn_offset) {
fprintf(stderr,
"%s: CFI %s at offset 0x%llx in section '%s':"
" the instruction at offset 0x%llx assumes that a CFA rule has"
@@ -2068,8 +2329,8 @@ void CallFrameInfo::Reporter::NoCFARule(uint64 offset,
}
void CallFrameInfo::Reporter::EmptyStateStack(uint64 offset,
- CallFrameInfo::EntryKind kind,
- uint64 insn_offset) {
+ CallFrameInfo::EntryKind kind,
+ uint64 insn_offset) {
fprintf(stderr,
"%s: CFI %s at offset 0x%llx in section '%s':"
" the DW_CFA_restore_state instruction at offset 0x%llx"
@@ -2079,8 +2340,8 @@ void CallFrameInfo::Reporter::EmptyStateStack(uint64 offset,
}
void CallFrameInfo::Reporter::ClearingCFARule(uint64 offset,
- CallFrameInfo::EntryKind kind,
- uint64 insn_offset) {
+ CallFrameInfo::EntryKind kind,
+ uint64 insn_offset) {
fprintf(stderr,
"%s: CFI %s at offset 0x%llx in section '%s':"
" the DW_CFA_restore_state instruction at offset 0x%llx"
diff --git a/src/common/dwarf/dwarf2reader.h b/src/common/dwarf/dwarf2reader.h
index 136b8932..a7a13afb 100644
--- a/src/common/dwarf/dwarf2reader.h
+++ b/src/common/dwarf/dwarf2reader.h
@@ -46,6 +46,7 @@
#include <utility>
#include <vector>
+#include "common/dwarf/bytereader.h"
#include "common/dwarf/dwarf2enums.h"
#include "common/dwarf/types.h"
@@ -53,7 +54,6 @@ using namespace std;
namespace dwarf2reader {
struct LineStateMachine;
-class ByteReader;
class Dwarf2Handler;
class LineInfoHandler;
@@ -556,7 +556,7 @@ class CallFrameInfo {
public:
// The different kinds of entries one finds in CFI. Used internally,
// and for error reporting.
- enum EntryKind { kUnknown, kCIE, kFDE };
+ enum EntryKind { kUnknown, kCIE, kFDE, kTerminator };
// The handler class to which the parser hands the parsed call frame
// information. Defined below.
@@ -567,19 +567,75 @@ class CallFrameInfo {
class Reporter;
// Create a DWARF CFI parser. BUFFER points to the contents of the
- // .debug_frame section to parse; BUFFER_LENGTH is its length in
- // bytes. REPORTER is an error reporter the parser should use to
- // report problems. READER is a ByteReader instance that has the
- // endianness and address size set properly. Report the data we find
- // to HANDLER.
+ // .debug_frame section to parse; BUFFER_LENGTH is its length in bytes.
+ // REPORTER is an error reporter the parser should use to report
+ // problems. READER is a ByteReader instance that has the endianness and
+ // address size set properly. Report the data we find to HANDLER.
+ //
+ // This class can also parse Linux C++ exception handling data, as found
+ // in '.eh_frame' sections. This data is a variant of DWARF CFI that is
+ // placed in loadable segments so that it is present in the program's
+ // address space, and is interpreted by the C++ runtime to search the
+ // call stack for a handler interested in the exception being thrown,
+ // actually pop the frames, and find cleanup code to run.
+ //
+ // There are two differences between the call frame information described
+ // in the DWARF standard and the exception handling data Linux places in
+ // the .eh_frame section:
+ //
+ // - Exception handling data uses uses a different format for call frame
+ // information entry headers. The distinguished CIE id, the way FDEs
+ // refer to their CIEs, and the way the end of the series of entries is
+ // determined are all slightly different.
+ //
+ // If the constructor's EH_FRAME argument is true, then the
+ // CallFrameInfo parses the entry headers as Linux C++ exception
+ // handling data. If EH_FRAME is false or omitted, the CallFrameInfo
+ // parses standard DWARF call frame information.
+ //
+ // - Linux C++ exception handling data uses CIE augmentation strings
+ // beginning with 'z' to specify the presence of additional data after
+ // the CIE and FDE headers and special encodings used for addresses in
+ // frame description entries.
+ //
+ // CallFrameInfo can handle 'z' augmentations in either DWARF CFI or
+ // exception handling data if you have supplied READER with the base
+ // addresses needed to interpret the pointer encodings that 'z'
+ // augmentations can specify. See the ByteReader interface for details
+ // about the base addresses. See the CallFrameInfo::Handler interface
+ // for details about the additional information one might find in
+ // 'z'-augmented data.
+ //
+ // Thus:
+ //
+ // - If you are parsing standard DWARF CFI, as found in a .debug_frame
+ // section, you should pass false for the EH_FRAME argument, or omit
+ // it, and you need not worry about providing READER with the
+ // additional base addresses.
+ //
+ // - If you want to parse Linux C++ exception handling data from a
+ // .eh_frame section, you should pass EH_FRAME as true, and call
+ // READER's Set*Base member functions before calling our Start method.
+ //
+ // - If you want to parse DWARF CFI that uses the 'z' augmentations
+ // (although I don't think any toolchain ever emits such data), you
+ // could pass false for EH_FRAME, but call READER's Set*Base members.
+ //
+ // The extensions the Linux C++ ABI makes to DWARF for exception
+ // handling are described here, rather poorly:
+ // http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html
+ // http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
+ //
+ // The mechanics of C++ exception handling, personality routines,
+ // and language-specific data areas are described here, rather nicely:
+ // http://www.codesourcery.com/public/cxx-abi/abi-eh.html
CallFrameInfo(const char *buffer, size_t buffer_length,
- ByteReader *reader, Handler *handler,
- Reporter *reporter)
- : buffer_(buffer),
- buffer_length_(buffer_length),
- reader_(reader),
- handler_(handler),
- reporter_(reporter) { }
+ ByteReader *reader, Handler *handler, Reporter *reporter,
+ bool eh_frame = false)
+ : buffer_(buffer), buffer_length_(buffer_length),
+ reader_(reader), handler_(handler), reporter_(reporter),
+ eh_frame_(eh_frame) { }
+
~CallFrameInfo() { }
// Parse the entries in BUFFER, reporting what we find to HANDLER.
@@ -603,6 +659,13 @@ class CallFrameInfo {
// The start of this entry in the buffer.
const char *start;
+ // Which kind of entry this is.
+ //
+ // We want to be able to use this for error reporting even while we're
+ // in the midst of parsing. Error reporting code may assume that kind,
+ // offset, and start fields are valid, although kind may be kUnknown.
+ EntryKind kind;
+
// The end of this entry's common prologue (initial length and id), and
// the start of this entry's kind-specific fields.
const char *fields;
@@ -616,16 +679,10 @@ class CallFrameInfo {
// simply buffer_ + offset + length.)
const char *end;
- // The CIE pointer or CIE id field.
+ // For both DWARF CFI and .eh_frame sections, this is the CIE id in a
+ // CIE, and the offset of the associated CIE in an FDE.
uint64 id;
- // The kind of entry we're parsing.
- //
- // This may be kUnknown at times, since we want to be able to
- // count on it for error reporting even before we've finished
- // parsing enough to tell what kind of entry we're looking at.
- EntryKind kind;
-
// The CIE that applies to this entry, if we've parsed it. If this is a
// CIE, then this field points to this structure.
CIE *cie;
@@ -638,12 +695,45 @@ class CallFrameInfo {
uint64 code_alignment_factor; // scale for code address adjustments
int data_alignment_factor; // scale for stack pointer adjustments
unsigned return_address_register; // which register holds the return addr
+
+ // True if this CIE includes Linux C++ ABI 'z' augmentation data.
+ bool has_z_augmentation;
+
+ // Parsed 'z' augmentation data. These are meaningful only if
+ // has_z_augmentation is true.
+ bool has_z_lsda; // The 'z' augmentation included 'L'.
+ bool has_z_personality; // The 'z' augmentation included 'P'.
+ bool has_z_signal_frame; // The 'z' augmentation included 'S'.
+
+ // If has_z_lsda is true, this is the encoding to be used for language-
+ // specific data area pointers in FDEs.
+ DwarfPointerEncoding lsda_encoding;
+
+ // If has_z_personality is true, this is the encoding used for the
+ // personality routine pointer in the augmentation data.
+ DwarfPointerEncoding personality_encoding;
+
+ // If has_z_personality is true, this is the address of the personality
+ // routine --- or, if personality_encoding & DW_EH_PE_indirect, the
+ // address where the personality routine's address is stored.
+ uint64 personality_address;
+
+ // This is the encoding used for addresses in the FDE header and
+ // in DW_CFA_set_loc instructions. This is always valid, whether
+ // or not we saw a 'z' augmentation string; its default value is
+ // DW_EH_PE_absptr, which is what normal DWARF CFI uses.
+ DwarfPointerEncoding pointer_encoding;
};
// A frame description entry (FDE).
struct FDE: public Entry {
uint64 address; // start address of described code
uint64 size; // size of described code, in bytes
+
+ // If cie->has_z_lsda is true, then this is the language-specific data
+ // area's address --- or its address's address, if cie->lsda_encoding
+ // has the DW_EH_PE_indirect bit set.
+ uint64 lsda_address;
};
// Internal use.
@@ -658,24 +748,27 @@ class CallFrameInfo {
class RuleMap;
class State;
- // Parse the initial length and id of a CFI entry, either a CIE or an
- // FDE. CURSOR points to the beginning of the data to parse.
- // On success, populate ENTRY as appropriate, and return true.
- // On failure, report the problem, and return false.
+ // Parse the initial length and id of a CFI entry, either a CIE, an FDE,
+ // or a .eh_frame end-of-data mark. CURSOR points to the beginning of the
+ // data to parse. On success, populate ENTRY as appropriate, and return
+ // true. On failure, report the problem, and return false. Even if we
+ // return false, set ENTRY->end to the first byte after the entry if we
+ // were able to figure that out, or NULL if we weren't.
bool ReadEntryPrologue(const char *cursor, Entry *entry);
- // Parse the fields of a CIE after the entry prologue. Assume that the
- // 'Entry' fields of CIE are populated; use CIE->fields and CIE->end as
- // the start and limit for parsing. On success, populate the rest of
- // *CIE, and return true; on failure, report the problem and return
- // false.
+ // Parse the fields of a CIE after the entry prologue, including any 'z'
+ // augmentation data. Assume that the 'Entry' fields of CIE are
+ // populated; use CIE->fields and CIE->end as the start and limit for
+ // parsing. On success, populate the rest of *CIE, and return true; on
+ // failure, report the problem and return false.
bool ReadCIEFields(CIE *cie);
- // Parse the fields of an FDE after the entry prologue. Assume that the
- // 'Entry' fields of *FDE are initialized; use FDE->fields and FDE->end
- // as the start and limit for parsing. Assume that FDE->cie is fully
- // initialized. On success, populate the rest of *FDE, and return true;
- // on failure, report the problem and return false.
+ // Parse the fields of an FDE after the entry prologue, including any 'z'
+ // augmentation data. Assume that the 'Entry' fields of *FDE are
+ // initialized; use FDE->fields and FDE->end as the start and limit for
+ // parsing. Assume that FDE->cie is fully initialized. On success,
+ // populate the rest of *FDE, and return true; on failure, report the
+ // problem and return false.
bool ReadFDEFields(FDE *fde);
// Report that ENTRY is incomplete, and return false. This is just a
@@ -683,6 +776,11 @@ class CallFrameInfo {
// little brevity.
bool ReportIncomplete(Entry *entry);
+ // Return true if ENCODING has the DW_EH_PE_indirect bit set.
+ static bool IsIndirectEncoding(DwarfPointerEncoding encoding) {
+ return encoding & DW_EH_PE_indirect;
+ }
+
// The contents of the DWARF .debug_info section we're parsing.
const char *buffer_;
size_t buffer_length_;
@@ -695,6 +793,9 @@ class CallFrameInfo {
// For reporting problems in the info we're parsing.
Reporter *reporter_;
+
+ // True if we are processing .eh_frame-format data.
+ bool eh_frame_;
};
// The handler class for CallFrameInfo. The a CFI parser calls the
@@ -786,6 +887,61 @@ class CallFrameInfo::Handler {
// everything is okay, or false if an error has occurred and parsing
// should stop.
virtual bool End() = 0;
+
+ // Handler functions for Linux C++ exception handling data. These are
+ // only called if the data includes 'z' augmentation strings.
+
+ // The Linux C++ ABI uses an extension of the DWARF CFI format to
+ // walk the stack to propagate exceptions from the throw to the
+ // appropriate catch, and do the appropriate cleanups along the way.
+ // CFI entries used for exception handling have two additional data
+ // associated with them:
+ //
+ // - The "language-specific data area" describes which exception
+ // types the function has 'catch' clauses for, and indicates how
+ // to go about re-entering the function at the appropriate catch
+ // clause. If the exception is not caught, it describes the
+ // destructors that must run before the frame is popped.
+ //
+ // - The "personality routine" is responsible for interpreting the
+ // language-specific data area's contents, and deciding whether
+ // the exception should continue to propagate down the stack,
+ // perhaps after doing some cleanup for this frame, or whether the
+ // exception will be caught here.
+ //
+ // In principle, the language-specific data area is opaque to
+ // everybody but the personality routine. In practice, these values
+ // may be useful or interesting to readers with extra context, and
+ // we have to at least skip them anyway, so we might as well report
+ // them to the handler.
+
+ // This entry's exception handling personality routine's address is
+ // ADDRESS. If INDIRECT is true, then ADDRESS is the address at
+ // which the routine's address is stored. The default definition for
+ // this handler function simply returns true, allowing parsing of
+ // the entry to continue.
+ virtual bool PersonalityRoutine(uint64 address, bool indirect) {
+ return true;
+ }
+
+ // This entry's language-specific data area (LSDA) is located at
+ // ADDRESS. If INDIRECT is true, then ADDRESS is the address at
+ // which the area's address is stored. The default definition for
+ // this handler function simply returns true, allowing parsing of
+ // the entry to continue.
+ virtual bool LanguageSpecificDataArea(uint64 address, bool indirect) {
+ return true;
+ }
+
+ // This entry describes a signal trampoline --- this frame is the
+ // caller of a signal handler. The default definition for this
+ // handler function simply returns true, allowing parsing of the
+ // entry to continue.
+ //
+ // The best description of the rationale for and meaning of signal
+ // trampoline CFI entries seems to be in the GCC bug database:
+ // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=26208
+ virtual bool SignalHandler() { return true; }
};
// The CallFrameInfo class makes calls on an instance of this class to
@@ -811,6 +967,12 @@ class CallFrameInfo::Reporter {
// haven't parsed enough of the entry to tell yet.
virtual void Incomplete(uint64 offset, CallFrameInfo::EntryKind kind);
+ // The .eh_frame data has a four-byte zero at OFFSET where the next
+ // entry's length would be; this is a terminator. However, the buffer
+ // length as given to the CallFrameInfo constructor says there should be
+ // more data.
+ virtual void EarlyEHTerminator(uint64 offset);
+
// The FDE at OFFSET refers to the CIE at CIE_OFFSET, but the
// section is not that large.
virtual void CIEPointerOutOfRange(uint64 offset, uint64 cie_offset);
@@ -830,6 +992,14 @@ class CallFrameInfo::Reporter {
virtual void UnrecognizedAugmentation(uint64 offset,
const string &augmentation);
+ // The pointer encoding ENCODING, specified by the CIE at OFFSET, is not
+ // a valid encoding.
+ virtual void InvalidPointerEncoding(uint64 offset, uint8 encoding);
+
+ // The pointer encoding ENCODING, specified by the CIE at OFFSET, depends
+ // on a base address which has not been supplied.
+ virtual void UnusablePointerEncoding(uint64 offset, uint8 encoding);
+
// The CIE at OFFSET contains a DW_CFA_restore instruction at
// INSN_OFFSET, which may not appear in a CIE.
virtual void RestoreInCIE(uint64 offset, uint64 insn_offset);
diff --git a/src/common/dwarf/dwarf2reader_cfi_unittest.cc b/src/common/dwarf/dwarf2reader_cfi_unittest.cc
index 6083df71..4762cfbf 100644
--- a/src/common/dwarf/dwarf2reader_cfi_unittest.cc
+++ b/src/common/dwarf/dwarf2reader_cfi_unittest.cc
@@ -31,10 +31,33 @@
// dwarf2reader_cfi_unittest.cc: Unit tests for dwarf2reader::CallFrameInfo
+#include <cstdlib>
#include <vector>
+// The '.eh_frame' format, used by the Linux C++ ABI for exception
+// handling, is poorly specified. To help test our support for .eh_frame,
+// if you #define WRITE_ELF while compiling this file, and add the
+// 'include' directory from the binutils, gcc, or gdb source tree to the
+// #include path, then each test that calls the
+// PERHAPS_WRITE_DEBUG_FRAME_FILE or PERHAPS_WRITE_EH_FRAME_FILE will an
+// ELF file containing a .debug_frame or .eh_frame section; you can then
+// use tools like readelf to examine the test data, and check the tools'
+// interpretation against the test's intentions. Each ELF file is named
+// "cfitest-TEST", where TEST identifies the particular test.
+#ifdef WRITE_ELF
+#include <cstdio>
+#include <cerrno>
+#include <cstring>
+extern "C" {
+// To compile with WRITE_ELF, you should add the 'include' directory
+// of the binutils, gcc, or gdb source tree to your #include path;
+// that directory contains this header.
+#include "elf/common.h"
+}
+#endif
+
#include "breakpad_googletest_includes.h"
-#include "common/dwarf/bytereader.h"
+#include "common/dwarf/bytereader-inl.h"
#include "common/dwarf/cfi_assembler.h"
#include "common/dwarf/dwarf2reader.h"
#include "google_breakpad/common/breakpad_types.h"
@@ -45,6 +68,7 @@ using google_breakpad::TestAssembler::kBigEndian;
using google_breakpad::TestAssembler::kLittleEndian;
using google_breakpad::TestAssembler::Section;
+using dwarf2reader::DwarfPointerEncoding;
using dwarf2reader::ENDIANNESS_BIG;
using dwarf2reader::ENDIANNESS_LITTLE;
using dwarf2reader::ByteReader;
@@ -57,6 +81,18 @@ using testing::Sequence;
using testing::Test;
using testing::_;
+#ifdef WRITE_ELF
+void WriteELFFrameSection(const char *filename, const char *section_name,
+ const CFISection &section);
+#define PERHAPS_WRITE_DEBUG_FRAME_FILE(name, section) \
+ WriteELFFrameSection("cfitest-" name, ".debug_frame", section);
+#define PERHAPS_WRITE_EH_FRAME_FILE(name, section) \
+ WriteELFFrameSection("cfitest-" name, ".eh_frame", section);
+#else
+#define PERHAPS_WRITE_DEBUG_FRAME_FILE(name, section)
+#define PERHAPS_WRITE_EH_FRAME_FILE(name, section)
+#endif
+
class MockCallFrameInfoHandler: public CallFrameInfo::Handler {
public:
MOCK_METHOD6(Entry, bool(size_t offset, uint64 address, uint64 length,
@@ -74,16 +110,22 @@ class MockCallFrameInfoHandler: public CallFrameInfo::Handler {
MOCK_METHOD3(ValExpressionRule, bool(uint64 address, int reg,
const string &expression));
MOCK_METHOD0(End, bool());
+ MOCK_METHOD2(PersonalityRoutine, bool(uint64 address, bool indirect));
+ MOCK_METHOD2(LanguageSpecificDataArea, bool(uint64 address, bool indirect));
+ MOCK_METHOD0(SignalHandler, bool());
};
class MockCallFrameErrorReporter: public CallFrameInfo::Reporter {
public:
MockCallFrameErrorReporter() : Reporter("mock filename", "mock section") { }
MOCK_METHOD2(Incomplete, void(uint64, CallFrameInfo::EntryKind));
+ MOCK_METHOD1(EarlyEHTerminator, void(uint64));
MOCK_METHOD2(CIEPointerOutOfRange, void(uint64, uint64));
MOCK_METHOD2(BadCIEId, void(uint64, uint64));
MOCK_METHOD2(UnrecognizedVersion, void(uint64, int version));
MOCK_METHOD2(UnrecognizedAugmentation, void(uint64, const string &));
+ MOCK_METHOD2(InvalidPointerEncoding, void(uint64, uint8));
+ MOCK_METHOD2(UnusablePointerEncoding, void(uint64, uint8));
MOCK_METHOD2(RestoreInCIE, void(uint64, uint64));
MOCK_METHOD3(BadInstruction, void(uint64, CallFrameInfo::EntryKind, uint64));
MOCK_METHOD3(NoCFARule, void(uint64, CallFrameInfo::EntryKind, uint64));
@@ -113,13 +155,19 @@ struct CFIFixture {
EXPECT_CALL(handler, RegisterRule(_, _, _)).Times(0);
EXPECT_CALL(handler, ExpressionRule(_, _, _)).Times(0);
EXPECT_CALL(handler, ValExpressionRule(_, _, _)).Times(0);
+ EXPECT_CALL(handler, PersonalityRoutine(_, _)).Times(0);
+ EXPECT_CALL(handler, LanguageSpecificDataArea(_, _)).Times(0);
+ EXPECT_CALL(handler, SignalHandler()).Times(0);
// Default expectations for the error/warning reporer.
EXPECT_CALL(reporter, Incomplete(_, _)).Times(0);
+ EXPECT_CALL(reporter, EarlyEHTerminator(_)).Times(0);
EXPECT_CALL(reporter, CIEPointerOutOfRange(_, _)).Times(0);
EXPECT_CALL(reporter, BadCIEId(_, _)).Times(0);
EXPECT_CALL(reporter, UnrecognizedVersion(_, _)).Times(0);
EXPECT_CALL(reporter, UnrecognizedAugmentation(_, _)).Times(0);
+ EXPECT_CALL(reporter, InvalidPointerEncoding(_, _)).Times(0);
+ EXPECT_CALL(reporter, UnusablePointerEncoding(_, _)).Times(0);
EXPECT_CALL(reporter, RestoreInCIE(_, _)).Times(0);
EXPECT_CALL(reporter, BadInstruction(_, _, _)).Times(0);
EXPECT_CALL(reporter, NoCFARule(_, _, _)).Times(0);
@@ -249,6 +297,8 @@ TEST_F(CFI, SingleCIE) {
section.Append(10, dwarf2reader::DW_CFA_nop);
section.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("SingleCIE", section);
+
EXPECT_CALL(handler, Entry(_, _, _, _, _, _)).Times(0);
EXPECT_CALL(handler, End()).Times(0);
@@ -272,6 +322,8 @@ TEST_F(CFI, OneFDE) {
.FDEHeader(cie, 0x7714740d, 0x3d5a10cd)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("OneFDE", section);
+
{
InSequence s;
EXPECT_CALL(handler,
@@ -294,7 +346,8 @@ TEST_F(CFI, TwoFDEsOneCIE) {
CFISection section(kBigEndian, 4);
Label cie;
section
- // First FDE.
+ // First FDE. readelf complains about this one because it makes
+ // a forward reference to its CIE.
.FDEHeader(cie, 0xa42744df, 0xa3b42121)
.FinishEntry()
// CIE.
@@ -305,6 +358,8 @@ TEST_F(CFI, TwoFDEsOneCIE) {
.FDEHeader(cie, 0x6057d391, 0x700f608d)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("TwoFDEsOneCIE", section);
+
{
InSequence s;
EXPECT_CALL(handler,
@@ -338,7 +393,8 @@ TEST_F(CFI, TwoFDEsTwoCIEs) {
.Mark(&cie1)
.CIEHeader(0x694d5d45, 0x4233221b, 0xbf45e65a, 3, "")
.FinishEntry()
- // First FDE which cites second CIE.
+ // First FDE which cites second CIE. readelf complains about
+ // this one because it makes a forward reference to its CIE.
.FDEHeader(cie2, 0x778b27dfe5871f05ULL, 0x324ace3448070926ULL)
.FinishEntry()
// Second FDE, which cites first CIE.
@@ -349,6 +405,8 @@ TEST_F(CFI, TwoFDEsTwoCIEs) {
.CIEHeader(0xfba3fad7, 0x6287e1fd, 0x61d2c581, 2, "")
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("TwoFDEsTwoCIEs", section);
+
{
InSequence s;
EXPECT_CALL(handler,
@@ -394,6 +452,8 @@ TEST_F(CFI, BadVersion) {
.FDEHeader(cie2, 0x2094735a, 0x6e875501)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("BadVersion", section);
+
EXPECT_CALL(reporter, UnrecognizedVersion(_, 0x52))
.WillOnce(Return());
@@ -436,6 +496,8 @@ TEST_F(CFI, BadAugmentation) {
.FDEHeader(cie2, 0x7bf0fda0, 0xcbcd28d8)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("BadAugmentation", section);
+
EXPECT_CALL(reporter, UnrecognizedAugmentation(_, "spaniels!"))
.WillOnce(Return());
@@ -475,6 +537,8 @@ TEST_F(CFI, CIEVersion1ReturnColumn) {
.FDEHeader(cie, 0xb8d347b5, 0x825e55dc)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("CIEVersion1ReturnColumn", section);
+
{
InSequence s;
EXPECT_CALL(handler, Entry(_, 0xb8d347b5, 0x825e55dc, 1, "", 0x9f))
@@ -507,6 +571,8 @@ TEST_F(CFI, CIEVersion3ReturnColumn) {
.FDEHeader(cie, 0x86763f2b, 0x2a66dc23)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("CIEVersion3ReturnColumn", section);
+
{
InSequence s;
EXPECT_CALL(handler, Entry(_, 0x86763f2b, 0x2a66dc23, 3, "", 0x89))
@@ -630,6 +696,8 @@ TEST_F(CFIInsn, DW_CFA_set_loc) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0x4defb431).ULEB128(0x6d17b0ee)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_set_loc", section);
+
EXPECT_CALL(handler,
ValOffsetRule(0xb1ee3e7a, kCFARegister, 0x4defb431, 0x6d17b0ee))
.InSequence(s)
@@ -649,6 +717,8 @@ TEST_F(CFIInsn, DW_CFA_advance_loc) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0x5bbb3715).ULEB128(0x0186c7bf)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_advance_loc", section);
+
EXPECT_CALL(handler,
ValOffsetRule(fde_start + 0x2a * code_factor,
kCFARegister, 0x5bbb3715, 0x0186c7bf))
@@ -667,6 +737,8 @@ TEST_F(CFIInsn, DW_CFA_advance_loc1) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0x69d5696a).ULEB128(0x1eb7fc93)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_advance_loc1", section);
+
EXPECT_CALL(handler,
ValOffsetRule((fde_start + 0xd8 * code_factor),
kCFARegister, 0x69d5696a, 0x1eb7fc93))
@@ -685,6 +757,8 @@ TEST_F(CFIInsn, DW_CFA_advance_loc2) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0x3a368bed).ULEB128(0x3194ee37)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_advance_loc2", section);
+
EXPECT_CALL(handler,
ValOffsetRule((fde_start + 0x3adb * code_factor),
kCFARegister, 0x3a368bed, 0x3194ee37))
@@ -703,6 +777,8 @@ TEST_F(CFIInsn, DW_CFA_advance_loc4) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0x135270c5).ULEB128(0x24bad7cb)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_advance_loc4", section);
+
EXPECT_CALL(handler,
ValOffsetRule((fde_start + 0x15813c88ULL * code_factor),
kCFARegister, 0x135270c5, 0x24bad7cb))
@@ -722,6 +798,8 @@ TEST_F(CFIInsn, DW_CFA_MIPS_advance_loc8) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0xe17ed602).ULEB128(0x3d162e7f)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_advance_loc8", section);
+
EXPECT_CALL(handler,
ValOffsetRule((fde_start + 0x3c4f3945b92c14ULL * code_factor),
kCFARegister, 0xe17ed602, 0x3d162e7f))
@@ -739,6 +817,8 @@ TEST_F(CFIInsn, DW_CFA_def_cfa) {
.D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(0x4e363a85).ULEB128(0x815f9aa7)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("DW_CFA_def_cfa", section);
+
EXPECT_CALL(handler,
ValOffsetRule(fde_start, kCFARegister, 0x4e363a85, 0x815f9aa7))
.InSequence(s).WillOnce(Return(true));
@@ -1835,6 +1915,8 @@ TEST_F(CFIRestore, RestoreValExpressionRuleChanged) {
.D8(dwarf2reader::DW_CFA_restore_state)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("RestoreValExpressionRuleChanged", section);
+
EXPECT_CALL(handler, ValExpressionRule(fde_start, 0xb5ca5c46, "revolting"))
.InSequence(s).WillOnce(Return(true));
EXPECT_CALL(handler, UndefinedRule(fde_start + code_factor, 0xb5ca5c46))
@@ -1861,6 +1943,9 @@ TEST_F(CFIRestore, RestoreValExpressionRuleChangedValExpression) {
.D8(dwarf2reader::DW_CFA_restore_state)
.FinishEntry();
+ PERHAPS_WRITE_DEBUG_FRAME_FILE("RestoreValExpressionRuleChangedValExpression",
+ section);
+
EXPECT_CALL(handler, ValExpressionRule(fde_start, 0x500f5739, "repulsive"))
.InSequence(s).WillOnce(Return(true));
EXPECT_CALL(handler, ValExpressionRule(fde_start + code_factor, 0x500f5739,
@@ -1875,6 +1960,294 @@ TEST_F(CFIRestore, RestoreValExpressionRuleChangedValExpression) {
ParseSection(&section);
}
+struct EHFrameFixture: public CFIInsnFixture {
+ EHFrameFixture()
+ : CFIInsnFixture(), section(kBigEndian, 4, true) {
+ encoded_pointer_bases.cfi = 0x7f496cb2;
+ encoded_pointer_bases.text = 0x540f67b6;
+ encoded_pointer_bases.data = 0xe3eab768;
+ section.SetEncodedPointerBases(encoded_pointer_bases);
+ }
+ CFISection section;
+ CFISection::EncodedPointerBases encoded_pointer_bases;
+
+ // Parse CFIInsnFixture::ParseSection, but parse the section as
+ // .eh_frame data, supplying stock base addresses.
+ void ParseEHFrameSection(CFISection *section, bool succeeds = true) {
+ EXPECT_TRUE(section->ContainsEHFrame());
+ string contents;
+ EXPECT_TRUE(section->GetContents(&contents));
+ dwarf2reader::Endianness endianness;
+ if (section->endianness() == kBigEndian)
+ endianness = ENDIANNESS_BIG;
+ else {
+ assert(section->endianness() == kLittleEndian);
+ endianness = ENDIANNESS_LITTLE;
+ }
+ ByteReader byte_reader(endianness);
+ byte_reader.SetAddressSize(section->AddressSize());
+ byte_reader.SetCFIDataBase(encoded_pointer_bases.cfi, contents.data());
+ byte_reader.SetTextBase(encoded_pointer_bases.text);
+ byte_reader.SetDataBase(encoded_pointer_bases.data);
+ CallFrameInfo parser(contents.data(), contents.size(),
+ &byte_reader, &handler, &reporter, true);
+ if (succeeds)
+ EXPECT_TRUE(parser.Start());
+ else
+ EXPECT_FALSE(parser.Start());
+ }
+
+};
+
+class EHFrame: public EHFrameFixture, public Test { };
+
+// A simple CIE, an FDE, and a terminator.
+TEST_F(EHFrame, Terminator) {
+ Label cie;
+ section
+ .Mark(&cie)
+ .CIEHeader(9968, 2466, 67, 1, "")
+ .D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(3772).ULEB128(1372)
+ .FinishEntry()
+ .FDEHeader(cie, 0x848037a1, 0x7b30475e)
+ .D8(dwarf2reader::DW_CFA_set_loc).D32(0x17713850)
+ .D8(dwarf2reader::DW_CFA_undefined).ULEB128(5721)
+ .FinishEntry()
+ .D32(0) // Terminate the sequence.
+ // This FDE should be ignored.
+ .FDEHeader(cie, 0xf19629fe, 0x439fb09b)
+ .FinishEntry();
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.Terminator", section);
+
+ EXPECT_CALL(handler, Entry(_, 0x848037a1, 0x7b30475e, 1, "", 67))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, ValOffsetRule(0x848037a1, kCFARegister, 3772, 1372))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, UndefinedRule(0x17713850, 5721))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(reporter, EarlyEHTerminator(_))
+ .InSequence(s).WillOnce(Return());
+
+ ParseEHFrameSection(&section);
+}
+
+// The parser should recognize the Linux Standards Base 'z' augmentations.
+TEST_F(EHFrame, SimpleFDE) {
+ DwarfPointerEncoding lsda_encoding =
+ DwarfPointerEncoding(dwarf2reader::DW_EH_PE_indirect
+ | dwarf2reader::DW_EH_PE_datarel
+ | dwarf2reader::DW_EH_PE_sdata2);
+ DwarfPointerEncoding fde_encoding =
+ DwarfPointerEncoding(dwarf2reader::DW_EH_PE_textrel
+ | dwarf2reader::DW_EH_PE_udata2);
+
+ section.SetPointerEncoding(fde_encoding);
+ section.SetEncodedPointerBases(encoded_pointer_bases);
+ Label cie;
+ section
+ .Mark(&cie)
+ .CIEHeader(4873, 7012, 100, 1, "zSLPR")
+ .ULEB128(7) // Augmentation data length
+ .D8(lsda_encoding) // LSDA pointer format
+ .D8(dwarf2reader::DW_EH_PE_pcrel) // personality pointer format
+ .EncodedPointer(0x97baa00, dwarf2reader::DW_EH_PE_pcrel) // and value
+ .D8(fde_encoding) // FDE pointer format
+ .D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(6706).ULEB128(31)
+ .FinishEntry()
+ .FDEHeader(cie, 0x540f6b56, 0xf686)
+ .ULEB128(2) // Augmentation data length
+ .EncodedPointer(0xe3eab475, lsda_encoding) // LSDA pointer, signed
+ .D8(dwarf2reader::DW_CFA_set_loc)
+ .EncodedPointer(0x540fa4ce, fde_encoding)
+ .D8(dwarf2reader::DW_CFA_undefined).ULEB128(0x675e)
+ .FinishEntry()
+ .D32(0); // terminator
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.SimpleFDE", section);
+
+ EXPECT_CALL(handler, Entry(_, 0x540f6b56, 0xf686, 1, "zSLPR", 100))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, PersonalityRoutine(0x97baa00, false))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, LanguageSpecificDataArea(0xe3eab475, true))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, SignalHandler())
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, ValOffsetRule(0x540f6b56, kCFARegister, 6706, 31))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, UndefinedRule(0x540fa4ce, 0x675e))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+
+ ParseEHFrameSection(&section);
+}
+
+// Check that we can handle an empty 'z' augmentation.
+TEST_F(EHFrame, EmptyZ) {
+ Label cie;
+ section
+ .Mark(&cie)
+ .CIEHeader(5955, 5805, 228, 1, "z")
+ .ULEB128(0) // Augmentation data length
+ .D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(3629).ULEB128(247)
+ .FinishEntry()
+ .FDEHeader(cie, 0xda007738, 0xfb55c641)
+ .ULEB128(0) // Augmentation data length
+ .D8(dwarf2reader::DW_CFA_advance_loc1).D8(11)
+ .D8(dwarf2reader::DW_CFA_undefined).ULEB128(3769)
+ .FinishEntry();
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.EmptyZ", section);
+
+ EXPECT_CALL(handler, Entry(_, 0xda007738, 0xfb55c641, 1, "z", 228))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, ValOffsetRule(0xda007738, kCFARegister, 3629, 247))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, UndefinedRule(0xda007738 + 11 * 5955, 3769))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+
+ ParseEHFrameSection(&section);
+}
+
+// Check that we recognize bad 'z' augmentation characters.
+TEST_F(EHFrame, BadZ) {
+ Label cie;
+ section
+ .Mark(&cie)
+ .CIEHeader(6937, 1045, 142, 1, "zQ")
+ .ULEB128(0) // Augmentation data length
+ .D8(dwarf2reader::DW_CFA_def_cfa).ULEB128(9006).ULEB128(7725)
+ .FinishEntry()
+ .FDEHeader(cie, 0x1293efa8, 0x236f53f2)
+ .ULEB128(0) // Augmentation data length
+ .D8(dwarf2reader::DW_CFA_advance_loc | 12)
+ .D8(dwarf2reader::DW_CFA_register).ULEB128(5667).ULEB128(3462)
+ .FinishEntry();
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.BadZ", section);
+
+ EXPECT_CALL(reporter, UnrecognizedAugmentation(_, "zQ"))
+ .WillOnce(Return());
+
+ ParseEHFrameSection(&section, false);
+}
+
+TEST_F(EHFrame, zL) {
+ Label cie;
+ DwarfPointerEncoding lsda_encoding =
+ DwarfPointerEncoding(dwarf2reader::DW_EH_PE_funcrel
+ | dwarf2reader::DW_EH_PE_udata2);
+ section
+ .Mark(&cie)
+ .CIEHeader(9285, 9959, 54, 1, "zL")
+ .ULEB128(1) // Augmentation data length
+ .D8(lsda_encoding) // encoding for LSDA pointer in FDE
+
+ .FinishEntry()
+ .FDEHeader(cie, 0xd40091aa, 0x9aa6e746)
+ .ULEB128(2) // Augmentation data length
+ .EncodedPointer(0xd40099cd, lsda_encoding) // LSDA pointer
+ .FinishEntry()
+ .D32(0); // terminator
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.zL", section);
+
+ EXPECT_CALL(handler, Entry(_, 0xd40091aa, 0x9aa6e746, 1, "zL", 54))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, LanguageSpecificDataArea(0xd40099cd, false))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+
+ ParseEHFrameSection(&section);
+}
+
+TEST_F(EHFrame, zP) {
+ Label cie;
+ DwarfPointerEncoding personality_encoding =
+ DwarfPointerEncoding(dwarf2reader::DW_EH_PE_datarel
+ | dwarf2reader::DW_EH_PE_udata2);
+ section
+ .Mark(&cie)
+ .CIEHeader(1097, 6313, 17, 1, "zP")
+ .ULEB128(3) // Augmentation data length
+ .D8(personality_encoding) // encoding for personality routine
+ .EncodedPointer(0xe3eaccac, personality_encoding) // value
+ .FinishEntry()
+ .FDEHeader(cie, 0x0c8350c9, 0xbef11087)
+ .ULEB128(0) // Augmentation data length
+ .FinishEntry()
+ .D32(0); // terminator
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.zP", section);
+
+ EXPECT_CALL(handler, Entry(_, 0x0c8350c9, 0xbef11087, 1, "zP", 17))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, PersonalityRoutine(0xe3eaccac, false))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+
+ ParseEHFrameSection(&section);
+}
+
+TEST_F(EHFrame, zR) {
+ Label cie;
+ DwarfPointerEncoding pointer_encoding =
+ DwarfPointerEncoding(dwarf2reader::DW_EH_PE_textrel
+ | dwarf2reader::DW_EH_PE_sdata2);
+ section.SetPointerEncoding(pointer_encoding);
+ section
+ .Mark(&cie)
+ .CIEHeader(8011, 5496, 75, 1, "zR")
+ .ULEB128(1) // Augmentation data length
+ .D8(pointer_encoding) // encoding for FDE addresses
+ .FinishEntry()
+ .FDEHeader(cie, 0x540f9431, 0xbd0)
+ .ULEB128(0) // Augmentation data length
+ .FinishEntry()
+ .D32(0); // terminator
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.zR", section);
+
+ EXPECT_CALL(handler, Entry(_, 0x540f9431, 0xbd0, 1, "zR", 75))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+
+ ParseEHFrameSection(&section);
+}
+
+TEST_F(EHFrame, zS) {
+ Label cie;
+ section
+ .Mark(&cie)
+ .CIEHeader(9217, 7694, 57, 1, "zS")
+ .ULEB128(0) // Augmentation data length
+ .FinishEntry()
+ .FDEHeader(cie, 0xd40091aa, 0x9aa6e746)
+ .ULEB128(0) // Augmentation data length
+ .FinishEntry()
+ .D32(0); // terminator
+
+ PERHAPS_WRITE_EH_FRAME_FILE("EHFrame.zS", section);
+
+ EXPECT_CALL(handler, Entry(_, 0xd40091aa, 0x9aa6e746, 1, "zS", 57))
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, SignalHandler())
+ .InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(handler, End())
+ .InSequence(s).WillOnce(Return(true));
+
+ ParseEHFrameSection(&section);
+}
+
// These tests require manual inspection of the test output.
struct CFIReporterFixture {
CFIReporterFixture() : reporter("test file name", "test section name") { }
@@ -1887,6 +2260,10 @@ TEST_F(CFIReporter, Incomplete) {
reporter.Incomplete(0x0102030405060708ULL, CallFrameInfo::kUnknown);
}
+TEST_F(CFIReporter, EarlyEHTerminator) {
+ reporter.EarlyEHTerminator(0x0102030405060708ULL);
+}
+
TEST_F(CFIReporter, CIEPointerOutOfRange) {
reporter.CIEPointerOutOfRange(0x0123456789abcdefULL, 0xfedcba9876543210ULL);
}
@@ -1903,6 +2280,14 @@ TEST_F(CFIReporter, UnrecognizedAugmentation) {
reporter.UnrecognizedAugmentation(0x0123456789abcdefULL, "poodles");
}
+TEST_F(CFIReporter, InvalidPointerEncoding) {
+ reporter.InvalidPointerEncoding(0x0123456789abcdefULL, 0x42);
+}
+
+TEST_F(CFIReporter, UnusablePointerEncoding) {
+ reporter.UnusablePointerEncoding(0x0123456789abcdefULL, 0x42);
+}
+
TEST_F(CFIReporter, RestoreInCIE) {
reporter.RestoreInCIE(0x0123456789abcdefULL, 0xfedcba9876543210ULL);
}
@@ -1918,7 +2303,7 @@ TEST_F(CFIReporter, NoCFARule) {
}
TEST_F(CFIReporter, EmptyStateStack) {
- reporter.EmptyStateStack(0x0123456789abcdefULL, CallFrameInfo::kFDE,
+ reporter.EmptyStateStack(0x0123456789abcdefULL, CallFrameInfo::kTerminator,
0xfedcba9876543210ULL);
}
@@ -1926,3 +2311,139 @@ TEST_F(CFIReporter, ClearingCFARule) {
reporter.ClearingCFARule(0x0123456789abcdefULL, CallFrameInfo::kFDE,
0xfedcba9876543210ULL);
}
+
+#ifdef WRITE_ELF
+// See comments at the top of the file mentioning WRITE_ELF for details.
+
+using google_breakpad::TestAssembler::Section;
+
+struct ELFSectionHeader {
+ ELFSectionHeader(unsigned int set_type)
+ : type(set_type), flags(0), address(0), link(0), info(0),
+ alignment(1), entry_size(0) { }
+ Label name;
+ unsigned int type;
+ u_int64_t flags;
+ u_int64_t address;
+ Label file_offset;
+ Label file_size;
+ unsigned int link;
+ unsigned int info;
+ u_int64_t alignment;
+ u_int64_t entry_size;
+};
+
+void AppendSectionHeader(CFISection *table, const ELFSectionHeader &header) {
+ (*table)
+ .D32(header.name) // name, index in string tbl
+ .D32(header.type) // type
+ .Address(header.flags) // flags
+ .Address(header.address) // address in memory
+ .Address(header.file_offset) // offset in ELF file
+ .Address(header.file_size) // length in bytes
+ .D32(header.link) // link to related section
+ .D32(header.info) // miscellaneous
+ .Address(header.alignment) // alignment
+ .Address(header.entry_size); // entry size
+}
+
+void WriteELFFrameSection(const char *filename, const char *cfi_name,
+ const CFISection &cfi) {
+ int elf_class = cfi.AddressSize() == 4 ? ELFCLASS32 : ELFCLASS64;
+ int elf_data = (cfi.endianness() == kBigEndian
+ ? ELFDATA2MSB : ELFDATA2LSB);
+ CFISection elf(cfi.endianness(), cfi.AddressSize());
+ Label elf_header_size, section_table_offset;
+ elf
+ .Append("\x7f" "ELF")
+ .D8(elf_class) // 32-bit or 64-bit ELF
+ .D8(elf_data) // endianness
+ .D8(1) // ELF version
+ .D8(ELFOSABI_LINUX) // Operating System/ABI indication
+ .D8(0) // ABI version
+ .Append(7, 0xda) // padding
+ .D16(ET_EXEC) // file type: executable file
+ .D16(EM_386) // architecture: Intel IA-32
+ .D32(EV_CURRENT); // ELF version
+ elf
+ .Address(0x0123456789abcdefULL) // program entry point
+ .Address(0) // program header offset
+ .Address(section_table_offset) // section header offset
+ .D32(0) // processor-specific flags
+ .D16(elf_header_size) // ELF header size in bytes */
+ .D16(elf_class == ELFCLASS32 ? 32 : 56) // program header entry size
+ .D16(0) // program header table entry count
+ .D16(elf_class == ELFCLASS32 ? 40 : 64) // section header entry size
+ .D16(3) // section count
+ .D16(1) // section name string table
+ .Mark(&elf_header_size);
+
+ // The null section. Every ELF file has one, as the first entry in
+ // the section header table.
+ ELFSectionHeader null_header(SHT_NULL);
+ null_header.file_offset = 0;
+ null_header.file_size = 0;
+
+ // The CFI section. The whole reason for writing out this ELF file
+ // is to put this in it so that we can run other dumping programs on
+ // it to check its contents.
+ ELFSectionHeader cfi_header(SHT_PROGBITS);
+ cfi_header.file_size = cfi.Size();
+
+ // The section holding the names of the sections. This is the
+ // section whose index appears in the e_shstrndx member of the ELF
+ // header.
+ ELFSectionHeader section_names_header(SHT_STRTAB);
+ CFISection section_names(cfi.endianness(), cfi.AddressSize());
+ section_names
+ .Mark(&null_header.name)
+ .AppendCString("")
+ .Mark(&section_names_header.name)
+ .AppendCString(".shstrtab")
+ .Mark(&cfi_header.name)
+ .AppendCString(cfi_name)
+ .Mark(&section_names_header.file_size);
+
+ // Create the section table. The ELF header's e_shoff member refers
+ // to this, and the e_shnum member gives the number of entries it
+ // contains.
+ CFISection section_table(cfi.endianness(), cfi.AddressSize());
+ AppendSectionHeader(&section_table, null_header);
+ AppendSectionHeader(&section_table, section_names_header);
+ AppendSectionHeader(&section_table, cfi_header);
+
+ // Append the section table and the section contents to the ELF file.
+ elf
+ .Mark(&section_table_offset)
+ .Append(section_table)
+ .Mark(&section_names_header.file_offset)
+ .Append(section_names)
+ .Mark(&cfi_header.file_offset)
+ .Append(cfi);
+
+ string contents;
+ if (!elf.GetContents(&contents)) {
+ fprintf(stderr, "failed to get ELF file contents\n");
+ exit(1);
+ }
+
+ FILE *out = fopen(filename, "w");
+ if (!out) {
+ fprintf(stderr, "error opening ELF file '%s': %s\n",
+ filename, strerror(errno));
+ exit(1);
+ }
+
+ if (fwrite(contents.data(), 1, contents.size(), out) != contents.size()) {
+ fprintf(stderr, "error writing ELF data to '%s': %s\n",
+ filename, strerror(errno));
+ exit(1);
+ }
+
+ if (fclose(out) == EOF) {
+ fprintf(stderr, "error closing ELF file '%s': %s\n",
+ filename, strerror(errno));
+ exit(1);
+ }
+}
+#endif
diff --git a/src/common/linux/dump_symbols.cc b/src/common/linux/dump_symbols.cc
index 79d7ad36..3df31372 100644
--- a/src/common/linux/dump_symbols.cc
+++ b/src/common/linux/dump_symbols.cc
@@ -46,6 +46,7 @@
#include <cstring>
#include <string>
+#include "common/dwarf/bytereader-inl.h"
#include "common/dwarf/dwarf2diehandler.h"
#include "common/linux/dump_stabs.h"
#include "common/linux/dump_symbols.h"
@@ -278,6 +279,9 @@ static bool LoadDwarfCFI(const string &dwarf_filename,
const ElfW(Ehdr) *elf_header,
const char *section_name,
const ElfW(Shdr) *section,
+ bool eh_frame,
+ const ElfW(Shdr) *got_section,
+ const ElfW(Shdr) *text_section,
Module *module) {
// Find the appropriate set of register names for this file's
// architecture.
@@ -321,11 +325,19 @@ static bool LoadDwarfCFI(const string &dwarf_filename,
dwarf_filename.c_str(), elf_header->e_ident[EI_CLASS]);
return false;
}
+ // Provide the base addresses for .eh_frame encoded pointers, if
+ // possible.
+ byte_reader.SetCFIDataBase(section->sh_addr, cfi);
+ if (got_section)
+ byte_reader.SetDataBase(got_section->sh_addr);
+ if (text_section)
+ byte_reader.SetTextBase(got_section->sh_addr);
dwarf2reader::CallFrameInfo::Reporter dwarf_reporter(dwarf_filename,
section_name);
- dwarf2reader::CallFrameInfo parser(cfi, cfi_size, &byte_reader,
- &handler, &dwarf_reporter);
+ dwarf2reader::CallFrameInfo parser(cfi, cfi_size,
+ &byte_reader, &handler, &dwarf_reporter,
+ eh_frame);
parser.Start();
return true;
}
@@ -379,7 +391,25 @@ static bool LoadSymbols(const std::string &obj_file, ElfW(Ehdr) *elf_header,
// information, the other debugging information could be perfectly
// useful.
LoadDwarfCFI(obj_file, elf_header, ".debug_frame",
- dwarf_cfi_section, module);
+ dwarf_cfi_section, false, 0, 0, module);
+ }
+
+ // Linux C++ exception handling information can also provide
+ // unwinding data.
+ const ElfW(Shdr) *eh_frame_section =
+ FindSectionByName(".eh_frame", sections, section_names,
+ elf_header->e_shnum);
+ if (eh_frame_section) {
+ // Pointers in .eh_frame data may be relative to the base addresses of
+ // certain sections. Provide those sections if present.
+ const ElfW(Shdr) *got_section =
+ FindSectionByName(".got", sections, section_names, elf_header->e_shnum);
+ const ElfW(Shdr) *text_section =
+ FindSectionByName(".text", sections, section_names,
+ elf_header->e_shnum);
+ // As above, ignore the return value of this function.
+ LoadDwarfCFI(obj_file, elf_header, ".eh_frame",
+ eh_frame_section, true, got_section, text_section, module);
}
if (!found_debug_info_section) {
diff --git a/src/common/linux/dwarf_cfi_to_module.cc b/src/common/linux/dwarf_cfi_to_module.cc
index d7946a0e..603acc0e 100644
--- a/src/common/linux/dwarf_cfi_to_module.cc
+++ b/src/common/linux/dwarf_cfi_to_module.cc
@@ -46,12 +46,10 @@ 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;
+
+ // If dwarf2reader::CallFrameInfo can handle this version and
+ // augmentation, then we should be okay with that, so there's no
+ // need to check them here.
// Get ready to collect entries.
entry_ = new Module::StackFrameEntry;
diff --git a/src/common/linux/dwarf_cfi_to_module_unittest.cc b/src/common/linux/dwarf_cfi_to_module_unittest.cc
index de769393..d7b08aef 100644
--- a/src/common/linux/dwarf_cfi_to_module_unittest.cc
+++ b/src/common/linux/dwarf_cfi_to_module_unittest.cc
@@ -78,20 +78,6 @@ struct DwarfCFIToModuleFixture {
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));