blob: 02464a30c03a655677076278e21c707aa2b8d460 [file] [edit]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "Dwarf.h"
#include <string.h>
#include "log.h"
using namespace std;
namespace rr {
struct Dwarf32 {
typedef uint32_t Offset;
static const uint8_t EntrySize = 4;
struct CompilationUnitPreamble {
uint32_t unit_length;
};
};
struct Dwarf64 {
typedef uint64_t Offset;
static const uint8_t EntrySize = 8;
struct __attribute__((packed)) CompilationUnitPreamble {
uint32_t magic; /* 0xffffffff */
uint64_t unit_length;
};
};
template <typename D> struct __attribute__((packed)) Dwarf4CompilationUnitHeader {
typedef D Size;
typename D::CompilationUnitPreamble preamble;
uint16_t version;
typename D::Offset debug_abbrev_offset;
uint8_t address_size;
void install_dwo_id(DwarfCompilationUnit* unit) const {
unit->set_dwo_id(0);
}
};
template <typename D> struct __attribute__((packed)) Dwarf5CompilationUnitHeader {
typedef D Size;
typename D::CompilationUnitPreamble preamble;
uint16_t version;
uint8_t unit_type;
uint8_t address_size;
typename D::Offset debug_abbrev_offset;
void install_dwo_id(DwarfCompilationUnit* unit) const {
unit->set_dwo_id(0);
}
};
template <typename D> struct __attribute__((packed)) Dwarf5SkeletonSplitCompilationUnitHeader {
typedef D Size;
typename D::CompilationUnitPreamble preamble;
uint16_t version;
uint8_t unit_type;
uint8_t address_size;
typename D::Offset debug_abbrev_offset;
uint64_t dwo_id;
void install_dwo_id(DwarfCompilationUnit* unit) const {
unit->set_dwo_id(dwo_id);
}
};
template <typename D> struct __attribute__((packed)) Dwarf2LineNumberTableHeader {
typedef D Size;
typename D::CompilationUnitPreamble preamble;
uint16_t version;
typename D::Offset header_length;
uint8_t minimum_instruction_length;
uint8_t default_is_stmt;
int8_t line_base;
uint8_t line_range;
uint8_t opcode_base;
bool read_directories(const DwarfCompilationUnit& cu,
DwarfSpan span,
const DebugStrSpans& debug_str,
std::vector<const char*>& directories,
std::vector<DwarfSourceFile>& files) const;
};
template <typename D> struct __attribute__((packed)) Dwarf4LineNumberTableHeader {
typedef D Size;
typename D::CompilationUnitPreamble preamble;
uint16_t version;
typename D::Offset header_length;
uint8_t minimum_instruction_length;
uint8_t maximum_operations_per_instruction;
uint8_t default_is_stmt;
int8_t line_base;
uint8_t line_range;
uint8_t opcode_base;
bool read_directories(const DwarfCompilationUnit& cu,
DwarfSpan span,
const DebugStrSpans& debug_str,
std::vector<const char*>& directories,
std::vector<DwarfSourceFile>& files) const;
};
template <typename D> struct __attribute__((packed)) Dwarf5LineNumberTableHeader {
typedef D Size;
typename D::CompilationUnitPreamble preamble;
uint16_t version;
uint8_t address_size;
uint8_t segment_selector_size;
typename D::Offset header_length;
uint8_t minimum_instruction_length;
uint8_t maximum_operations_per_instruction;
uint8_t default_is_stmt;
int8_t line_base;
uint8_t line_range;
uint8_t opcode_base;
bool read_directories(const DwarfCompilationUnit& cu,
DwarfSpan span,
const DebugStrSpans& debug_str,
std::vector<const char*>& directories,
std::vector<DwarfSourceFile>& files) const;
};
uint64_t DwarfSpan::read_uleb(bool* ok) {
uint64_t ret = 0;
int shift = 0;
while (start < end) {
uint8_t b = *start;
++start;
ret |= (b & 0x7f) << shift;
if (!(b & 0x80)) {
return ret;
}
shift += 7;
if (shift >= 64) {
*ok = false;
return 0;
}
}
*ok = false;
return 0;
}
DwarfSpan DwarfSpan::read_leb_ref(bool* ok) {
DwarfSpan ret(*this);
while (start < end) {
if (!(*start & 0x80)) {
++start;
ret.end = start;
return ret;
}
++start;
}
*ok = false;
return ret;
}
const char* DwarfSpan::read_null_terminated_string(bool* ok) {
const void* p = memchr(start, 0, size());
if (!p) {
LOG(warn) << "String was not null-terminated";
*ok = false;
return nullptr;
}
const char* ret = reinterpret_cast<const char*>(start);
start = static_cast<const uint8_t*>(p) + 1;
return ret;
}
DwarfAbbrev* DwarfAbbrevSet::lookup(uint64_t code) {
auto it = abbrevs.find(code);
if (it != abbrevs.end()) {
return it->second.get();
}
while (!remaining_span.empty()) {
bool ok = true;
uint64_t abbrev_code = remaining_span.read_uleb(&ok);
unique_ptr<DwarfAbbrev> abbrev(new DwarfAbbrev);
abbrev->tag = (DWTag)remaining_span.read_uleb(&ok);
abbrev->children = (DWChildren)remaining_span.read_value<uint8_t>(&ok);
auto abbrev_raw = abbrev.get();
while (true) {
uint64_t name = remaining_span.read_uleb(&ok);
DWForm form = (DWForm)remaining_span.read_uleb(&ok);
if (!name && !form) {
break;
}
DwarfSpan constant;
if (form == DW_FORM_implicit_const) {
constant = remaining_span.read_leb_ref(&ok);
}
abbrev->attributes.push_back({ name, form, constant });
}
if (!ok) {
LOG(warn) << "Invalid DWARF abbrev table!";
return nullptr;
}
abbrevs.insert(make_pair(abbrev_code, std::move(abbrev)));
if (code == abbrev_code) {
return abbrev_raw;
}
}
return nullptr;
}
DwarfAbbrevSet& DwarfAbbrevs::lookup(uint64_t offset) {
auto it = abbrevs.find(offset);
if (it != abbrevs.end()) {
return *it->second;
}
unique_ptr<DwarfAbbrevSet> set(new DwarfAbbrevSet(debug_abbrev.subspan(offset)));
auto set_raw = set.get();
abbrevs.insert(make_pair(offset, std::move(set)));
return *set_raw;
}
static DwarfAbbrev null_abbrev;
DwarfDIE::DwarfDIE(DwarfSpan span, DwarfAbbrevSet& abbrevs, uint8_t dwarf_size, uint8_t address_size, bool* ok)
: address_size(address_size), dwarf_size(dwarf_size) {
uint64_t code = span.read_uleb(ok);
if (!*ok) {
return;
}
if (code == 0) {
abbrev = &null_abbrev;
return;
}
abbrev = abbrevs.lookup(code);
if (!abbrev) {
LOG(warn) << "No abbrev found for DIE";
*ok = false;
return;
}
attr_span = span;
}
static size_t form_size(DWForm form, size_t address_size, size_t dwarf_size, DwarfSpan* span, bool* ok) {
if (form == DW_FORM_indirect) {
form = (DWForm)span->read_uleb(ok);
if (!*ok) {
return 0;
}
}
if (form == DW_FORM_udata) {
auto before = span->size();
DwarfSpan a_span(*span);
a_span.read_uleb(ok);
if (!*ok) {
return 0;
}
return before - a_span.size();
}
switch (form) {
case DW_FORM_addr: return address_size;
case DW_FORM_addrx: return dwarf_size;
case DW_FORM_data1: return 1;
case DW_FORM_data2: return 2;
case DW_FORM_data4: return 4;
case DW_FORM_data8: return 8;
case DW_FORM_data16: return 16;
case DW_FORM_flag: return 1;
case DW_FORM_strp: return dwarf_size;
case DW_FORM_line_strp: return dwarf_size;
case DW_FORM_strx: return dwarf_size;
case DW_FORM_strx1: return 1;
case DW_FORM_strx2: return 2;
case DW_FORM_strx3: return 3;
case DW_FORM_strx4: return 4;
case DW_FORM_string: {
auto before = span->size();
DwarfSpan a_span(*span);
a_span.read_null_terminated_string(ok);
if (!*ok) {
return 0;
}
return before - a_span.size();
}
case DW_FORM_sec_offset: return dwarf_size;
case DW_FORM_flag_present: return 0;
case DW_FORM_implicit_const: return 0;
case DW_FORM_rnglistx: return dwarf_size;
case DW_FORM_strp_sup: return dwarf_size;
case DW_FORM_GNU_strp_alt: return dwarf_size;
default:
LOG(warn) << "form " << form << " not supported!";
*ok = false;
return 0;
}
}
DwarfSpan DwarfDIE::find_attribute(DWAttr attr, DWForm* form, bool* ok) const {
DwarfSpan span = attr_span;
for (auto& a : abbrev->attributes) {
size_t size = form_size(a.form, address_size, dwarf_size, &span, ok);
DwarfSpan a_span = span.consume(size);
if (a.name == attr) {
*form = a.form;
if (a.form == DW_FORM_implicit_const) {
a_span = a.constant;
}
return a_span;
}
}
return DwarfSpan();
}
static uint64_t decode_unsigned_literal(DwarfSpan span, bool* ok) {
int shift = 0;
uint64_t ret = 0;
while (!span.empty()) {
if (shift >= 64) {
LOG(warn) << "Literal too large";
*ok = false;
return 0;
}
ret |= (uint64_t)span.read_value<uint8_t>(ok) << shift;
shift += 8;
}
return ret;
}
static int64_t decode_section_ptr(DwarfSpan span, DWForm form, bool* ok) {
switch (form) {
case DW_FORM_data1:
case DW_FORM_data2:
case DW_FORM_data4:
case DW_FORM_data8:
case DW_FORM_sec_offset: {
uint64_t ret = decode_unsigned_literal(span, ok);
if (ret > INT64_MAX) {
LOG(warn) << "section ptr out of range";
*ok = false;
return 0;
}
return ret;
}
default:
LOG(warn) << "Unknown section ptr form " << form;
*ok = false;
return 0;
}
}
static uint64_t decode_unsigned(DwarfSpan span, DWForm form, bool* ok) {
switch (form) {
case DW_FORM_data1:
case DW_FORM_data2:
case DW_FORM_data4:
case DW_FORM_data8: {
return decode_unsigned_literal(span, ok);
}
case DW_FORM_udata: {
return span.read_uleb(ok);
}
default:
LOG(warn) << "Unknown unsigned form " << form;
*ok = false;
return 0;
}
}
static const char* decode_string(const DwarfCompilationUnit& cu, DwarfSpan span, DWForm form, const DebugStrSpans& debug_strs, bool* ok) {
switch (form) {
case DW_FORM_strp: {
uint64_t offset = decode_unsigned_literal(span, ok);
if (!*ok) {
return nullptr;
}
return debug_strs.debug_str.subspan(offset).read_null_terminated_string(ok);
}
case DW_FORM_strp_sup:
case DW_FORM_GNU_strp_alt: {
uint64_t offset = decode_unsigned_literal(span, ok);
if (!*ok) {
return nullptr;
}
return debug_strs.debug_str_sup.subspan(offset).read_null_terminated_string(ok);
}
case DW_FORM_line_strp: {
uint64_t offset = decode_unsigned_literal(span, ok);
if (!*ok) {
return nullptr;
}
return debug_strs.debug_line_str.subspan(offset).read_null_terminated_string(ok);
}
case DW_FORM_strx:
case DW_FORM_strx1:
case DW_FORM_strx2:
case DW_FORM_strx3:
case DW_FORM_strx4: {
uint64_t index = decode_unsigned_literal(span, ok) * cu.entry_size() + cu.str_offsets_base();
if (!*ok) {
return nullptr;
}
uint64_t offset = cu.read_entry_sized_value(debug_strs.debug_str_offsets.subspan(index), ok);
if (!*ok) {
return nullptr;
}
return debug_strs.debug_str.subspan(offset).read_null_terminated_string(ok);
}
case DW_FORM_string:
return span.read_null_terminated_string(ok);
default:
LOG(warn) << "Unknown string form " << form;
*ok = false;
return 0;
}
}
int64_t DwarfDIE::section_ptr_attr(DWAttr attr, bool* ok) const {
DWForm form;
auto span = find_attribute(attr, &form, ok);
if (span.empty() || !*ok) {
return -1;
}
return decode_section_ptr(span, form, ok);
}
uint64_t DwarfDIE::unsigned_attr(DWAttr attr, bool* found, bool* ok) const {
DWForm form;
auto span = find_attribute(attr, &form, ok);
if (span.empty() || !*ok) {
*found = false;
return 0;
}
*found = true;
return decode_unsigned(span, form, ok);
}
const char* DwarfDIE::string_attr(const DwarfCompilationUnit& cu, DWAttr attr, const DebugStrSpans& debug_strs, bool* ok) const {
DWForm form;
auto span = find_attribute(attr, &form, ok);
if (span.empty() || !*ok) {
return nullptr;
}
return decode_string(cu, span, form, debug_strs, ok);
}
DwarfCompilationUnit DwarfCompilationUnit::next(DwarfSpan* debug_info, DwarfAbbrevs& abbrevs, bool* ok) {
DwarfCompilationUnit ret;
uint32_t word = DwarfSpan(*debug_info).read_value<uint32_t>(ok);
if (!*ok) {
return ret;
}
if (word == 0xFFFFFFFF) {
ret.init_size<Dwarf64>(debug_info, abbrevs, ok);
} else {
ret.init_size<Dwarf32>(debug_info, abbrevs, ok);
}
return ret;
}
template <typename D> void DwarfCompilationUnit::init_size(DwarfSpan* debug_info, DwarfAbbrevs& abbrevs, bool* ok) {
auto h = DwarfSpan(*debug_info).read<Dwarf4CompilationUnitHeader<D>>(ok);
if (!*ok) {
return;
}
if (2 <= h->version && h->version <= 4) {
init<Dwarf4CompilationUnitHeader<D>>(debug_info, abbrevs, ok);
} else if (h->version == 5) {
auto hh = DwarfSpan(*debug_info).read<Dwarf5CompilationUnitHeader<D>>(ok);
if (!*ok) {
return;
}
if (hh->unit_type == DW_UT_skeleton || hh->unit_type == DW_UT_split_compile) {
init<Dwarf5SkeletonSplitCompilationUnitHeader<D>>(debug_info, abbrevs, ok);
} else {
init<Dwarf5CompilationUnitHeader<D>>(debug_info, abbrevs, ok);
}
} else {
LOG(warn) << "Unknown compilation unit version " << h->version;
*ok = false;
}
}
template <typename H> void DwarfCompilationUnit::init(DwarfSpan* debug_info, DwarfAbbrevs& abbrevs, bool* ok) {
DwarfSpan span(*debug_info);
auto h = span.read<H>(ok);
if (!*ok) {
return;
}
uint64_t length = h->preamble.unit_length;
if (length >= UINT64_MAX - 12) {
LOG(warn) << "Invalid CU length";
*ok = false;
return;
}
debug_info->consume(length + sizeof(h->preamble));
DwarfAbbrevSet& abbrev_set = abbrevs.lookup(h->debug_abbrev_offset);
die_ = make_unique<DwarfDIE>(span, abbrev_set, sizeof(typename H::Size::Offset), h->address_size, ok);
if (!*ok) {
return;
}
if (die_->tag() != DW_TAG_compile_unit &&
die_->tag() != DW_TAG_partial_unit &&
die_->tag() != DW_TAG_skeleton_unit) {
LOG(warn) << "CU DIE is not DW_TAG_compilation_unit/DW_TAG_partial_unit/DW_TAG_skeleton_unit!";
*ok = false;
return;
}
entry_size_ = H::Size::EntrySize;
h->install_dwo_id(this);
}
uint64_t DwarfCompilationUnit::read_entry_sized_value(DwarfSpan span, bool* ok) const {
if (entry_size() == 4) {
return span.read_value<uint32_t>(ok);
} else if (entry_size() == 8) {
return span.read_value<uint64_t>(ok);
} else {
LOG(warn) << "Unknown entry size " << entry_size();
*ok = false;
return 0;
}
}
DwarfLineNumberTable::DwarfLineNumberTable(const DwarfCompilationUnit& cu, DwarfSpan span, const DebugStrSpans& debug_str, bool* ok) {
uint32_t word = DwarfSpan(span).read_value<uint32_t>(ok);
if (!*ok) {
return;
}
if (word == 0xFFFFFFFF) {
init_size<Dwarf64>(cu, span, debug_str, ok);
} else {
init_size<Dwarf32>(cu, span, debug_str, ok);
}
}
template <typename D> void DwarfLineNumberTable::init_size(const DwarfCompilationUnit& cu, DwarfSpan span, const DebugStrSpans& debug_str, bool* ok) {
auto h = DwarfSpan(span).read<Dwarf2LineNumberTableHeader<D>>(ok);
if (!*ok) {
return;
}
if (2 <= h->version && h->version <= 3) {
init<Dwarf2LineNumberTableHeader<D>>(cu, span, debug_str, ok);
} else if (h->version == 4) {
init<Dwarf4LineNumberTableHeader<D>>(cu, span, debug_str, ok);
} else if (h->version == 5) {
init<Dwarf5LineNumberTableHeader<D>>(cu, span, debug_str, ok);
} else {
LOG(warn) << "Unknown compilation unit version " << h->version;
*ok = false;
}
}
static bool read_dwarf2_directories(DwarfSpan span, std::vector<const char*>& directories, std::vector<DwarfSourceFile>& files) {
bool ok = true;
directories.push_back(nullptr);
while (true) {
const char* dir = span.read_null_terminated_string(&ok);
if (!ok) {
return ok;
}
if (!*dir) {
break;
}
directories.push_back(dir);
}
files.push_back({ 0, nullptr });
while (true) {
const char* file = span.read_null_terminated_string(&ok);
if (!ok) {
return ok;
}
if (!*file) {
break;
}
uint64_t dir = span.read_uleb(&ok);
if (dir >= directories.size()) {
LOG(warn) << "Invalid directory index, bailing";
return false;
}
span.read_uleb(&ok); // timestamp
span.read_uleb(&ok); // length
if (!ok) {
return ok;
}
files.push_back({ dir, file });
}
return ok;
}
template<typename T>
bool Dwarf2LineNumberTableHeader<T>::read_directories(const DwarfCompilationUnit&,
DwarfSpan span,
const DebugStrSpans&,
std::vector<const char*>& directories,
std::vector<DwarfSourceFile>& files) const {
return read_dwarf2_directories(span, directories, files);
}
template<typename T>
bool Dwarf4LineNumberTableHeader<T>::read_directories(const DwarfCompilationUnit&,
DwarfSpan span,
const DebugStrSpans&,
std::vector<const char*>& directories,
std::vector<DwarfSourceFile>& files) const {
return read_dwarf2_directories(span, directories, files);
}
struct FileEntryFormat {
DWLnct content_type;
DWForm form;
};
template<typename T>
bool Dwarf5LineNumberTableHeader<T>::read_directories(const DwarfCompilationUnit& cu,
DwarfSpan span,
const DebugStrSpans& debug_str,
std::vector<const char*>& directories,
std::vector<DwarfSourceFile>& files) const {
bool ok = true;
uint64_t directory_entry_format_count = span.read_uleb(&ok);
if (!ok) {
return ok;
}
bool seen_lnct_path = false;
std::vector<FileEntryFormat> directory_formats;
for (uint64_t i = 0; i < directory_entry_format_count; ++i) {
DWLnct content_type = (DWLnct)span.read_uleb(&ok);
if (!ok) {
return ok;
}
if (content_type == DW_LNCT_path) {
if (seen_lnct_path) {
LOG(warn) << "DW_LNCT_path appears twice in directories!";
return false;
}
seen_lnct_path = true;
}
DWForm form = (DWForm)span.read_uleb(&ok);
if (!ok) {
return ok;
}
directory_formats.push_back({ content_type, form });
}
if (!seen_lnct_path) {
LOG(warn) << "DW_LNCT_path does not appear in directories";
return false;
}
uint64_t directories_count = span.read_uleb(&ok);
if (!ok) {
return ok;
}
for (uint64_t i = 0; i < directories_count; ++i) {
for (auto format: directory_formats) {
switch (format.content_type) {
case DW_LNCT_path: {
size_t size = form_size(format.form, address_size, Size::EntrySize, &span, &ok);
DwarfSpan a_span = span.consume(size);
auto directory = decode_string(cu, a_span, format.form, debug_str, &ok);
if (!ok) {
return ok;
}
directories.push_back(directory);
break;
}
default:
LOG(warn) << "Unknown DW_LNCT " << format.content_type << " for directory";
return false;
}
}
}
uint64_t file_entry_format_count = span.read_uleb(&ok);
if (!ok) {
return ok;
}
seen_lnct_path = false;
std::vector<FileEntryFormat> file_formats;
for (uint64_t i = 0; i < file_entry_format_count; ++i) {
DWLnct content_type = (DWLnct)span.read_uleb(&ok);
if (!ok) {
return ok;
}
if (content_type == DW_LNCT_path) {
if (seen_lnct_path) {
LOG(warn) << "DW_LNCT_path appears twice in files!";
return false;
}
seen_lnct_path = true;
}
DWForm form = (DWForm)span.read_uleb(&ok);
if (!ok) {
return ok;
}
file_formats.push_back({ content_type, form });
}
if (!seen_lnct_path) {
LOG(warn) << "DW_LNCT_path does not appear in files";
return false;
}
uint64_t files_count = span.read_uleb(&ok);
if (!ok) {
return ok;
}
for (uint64_t i = 0; i < files_count; ++i) {
uint64_t directory_index = 0;
const char* file_path = NULL;
for (auto format: file_formats) {
switch (format.content_type) {
case DW_LNCT_path: {
size_t size = form_size(format.form, address_size, Size::EntrySize, &span, &ok);
DwarfSpan a_span = span.consume(size);
file_path = decode_string(cu, a_span, format.form, debug_str, &ok);
if (!ok) {
return ok;
}
break;
}
case DW_LNCT_directory_index: {
size_t size = form_size(format.form, address_size, Size::EntrySize, &span, &ok);
DwarfSpan a_span = span.consume(size);
directory_index = decode_unsigned(a_span, format.form, &ok);
if (!ok) {
return ok;
}
break;
}
case DW_LNCT_md5: {
if (format.form != DW_FORM_data16) {
LOG(warn) << "md5 has unexpected form " << format.form;
return false;
}
size_t size = form_size(format.form, address_size, Size::EntrySize, &span, &ok);
span.consume(size);
break;
}
default:
LOG(warn) << "Unknown DW_LNCT " << format.content_type << " for file";
return false;
}
}
files.push_back({ directory_index, file_path });
}
return true;
}
template <typename H> void DwarfLineNumberTable::init(const DwarfCompilationUnit& cu,
DwarfSpan span,
const DebugStrSpans& debug_str,
bool* ok) {
auto h = span.read<H>(ok);
if (!*ok) {
return;
}
for (uint8_t i = 1; i < h->opcode_base; ++i) {
span.read_uleb(ok);
}
if (!*ok) {
return;
}
*ok = h->read_directories(cu, span, debug_str, directories_, file_names_);
}
} // namespace rr