503 lines
18 KiB
C++
503 lines
18 KiB
C++
#ifndef DWARF_HPP
|
|
#define DWARF_HPP
|
|
|
|
#include <cpptrace/cpptrace.hpp>
|
|
#include "../utils/error.hpp"
|
|
#include "../utils/utils.hpp"
|
|
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <type_traits>
|
|
|
|
#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH
|
|
#include <libdwarf/libdwarf.h>
|
|
#include <libdwarf/dwarf.h>
|
|
#else
|
|
#ifndef CPPTRACE_CONDA_LIBDWARF_WEIRDNESS
|
|
#include <libdwarf.h>
|
|
#include <dwarf.h>
|
|
#else
|
|
#include <libdwarf.h>
|
|
#include <libdwarf-0/dwarf.h>
|
|
#endif
|
|
#endif
|
|
|
|
namespace cpptrace {
|
|
namespace detail {
|
|
namespace libdwarf {
|
|
static_assert(std::is_pointer<Dwarf_Die>::value, "Dwarf_Die not a pointer");
|
|
static_assert(std::is_pointer<Dwarf_Debug>::value, "Dwarf_Debug not a pointer");
|
|
|
|
[[noreturn]] void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
|
|
Dwarf_Unsigned ev = dwarf_errno(error);
|
|
char* msg = dwarf_errmsg(error);
|
|
(void)dbg;
|
|
// dwarf_dealloc_error(dbg, error);
|
|
throw internal_error("dwarf error {} {}", ev, msg);
|
|
}
|
|
|
|
struct die_object {
|
|
Dwarf_Debug dbg = nullptr;
|
|
Dwarf_Die die = nullptr;
|
|
|
|
// Error handling helper
|
|
// For some reason R (*f)(Args..., void*)-style deduction isn't possible, seems like a bug in all compilers
|
|
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56190
|
|
template<
|
|
typename... Args,
|
|
typename... Args2,
|
|
typename std::enable_if<
|
|
std::is_same<
|
|
decltype(
|
|
(void)std::declval<int(Args...)>()(std::forward<Args2>(std::declval<Args2>())..., nullptr)
|
|
),
|
|
void
|
|
>::value,
|
|
int
|
|
>::type = 0
|
|
>
|
|
int wrap(int (*f)(Args...), Args2&&... args) const {
|
|
Dwarf_Error error = nullptr;
|
|
int ret = f(std::forward<Args2>(args)..., &error);
|
|
if(ret == DW_DLV_ERROR) {
|
|
handle_dwarf_error(dbg, error);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
die_object(Dwarf_Debug dbg, Dwarf_Die die) : dbg(dbg), die(die) {
|
|
ASSERT(dbg != nullptr);
|
|
}
|
|
|
|
~die_object() {
|
|
if(die) {
|
|
dwarf_dealloc_die(die);
|
|
}
|
|
}
|
|
|
|
die_object(const die_object&) = delete;
|
|
|
|
die_object& operator=(const die_object&) = delete;
|
|
|
|
die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
|
|
// done for finding mistakes, attempts to use the die_object after this should segfault
|
|
// a valid use otherwise would be moved_from.get_sibling() which would get the next CU
|
|
other.dbg = nullptr;
|
|
other.die = nullptr;
|
|
}
|
|
|
|
die_object& operator=(die_object&& other) noexcept {
|
|
std::swap(dbg, other.dbg);
|
|
std::swap(die, other.die);
|
|
return *this;
|
|
}
|
|
|
|
die_object clone() const {
|
|
Dwarf_Off global_offset = get_global_offset();
|
|
Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die);
|
|
Dwarf_Die die_copy = nullptr;
|
|
VERIFY(wrap(dwarf_offdie_b, dbg, global_offset, is_info, &die_copy) == DW_DLV_OK);
|
|
return {dbg, die_copy};
|
|
}
|
|
|
|
die_object get_child() const {
|
|
Dwarf_Die child = nullptr;
|
|
int ret = wrap(dwarf_child, die, &child);
|
|
if(ret == DW_DLV_OK) {
|
|
return die_object(dbg, child);
|
|
} else if(ret == DW_DLV_NO_ENTRY) {
|
|
return die_object(dbg, nullptr);
|
|
} else {
|
|
PANIC();
|
|
}
|
|
}
|
|
|
|
die_object get_sibling() const {
|
|
Dwarf_Die sibling = nullptr;
|
|
int ret = wrap(dwarf_siblingof_b, dbg, die, true, &sibling);
|
|
if(ret == DW_DLV_OK) {
|
|
return die_object(dbg, sibling);
|
|
} else if(ret == DW_DLV_NO_ENTRY) {
|
|
return die_object(dbg, nullptr);
|
|
} else {
|
|
PANIC();
|
|
}
|
|
}
|
|
|
|
operator bool() const {
|
|
return die != nullptr;
|
|
}
|
|
|
|
Dwarf_Die get() const {
|
|
return die;
|
|
}
|
|
|
|
std::string get_name() const {
|
|
char empty[] = "";
|
|
char* name = empty;
|
|
int ret = wrap(dwarf_diename, die, &name);
|
|
auto wrapper = raii_wrap(name, [this] (char* str) { dwarf_dealloc(dbg, str, DW_DLA_STRING); });
|
|
std::string str;
|
|
if(ret != DW_DLV_NO_ENTRY) {
|
|
str = name;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
optional<std::string> get_string_attribute(Dwarf_Half attr_num) const {
|
|
Dwarf_Attribute attr;
|
|
if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) {
|
|
auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
|
char* raw_str;
|
|
VERIFY(wrap(dwarf_formstring, attr, &raw_str) == DW_DLV_OK);
|
|
auto strwrapper = raii_wrap(raw_str, [this] (char* str) { dwarf_dealloc(dbg, str, DW_DLA_STRING); });
|
|
std::string str = raw_str;
|
|
return str;
|
|
} else {
|
|
return nullopt;
|
|
}
|
|
}
|
|
|
|
optional<Dwarf_Unsigned> get_unsigned_attribute(Dwarf_Half attr_num) const {
|
|
Dwarf_Attribute attr;
|
|
if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) {
|
|
auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
|
// Dwarf_Half form = 0;
|
|
// VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
|
Dwarf_Unsigned val;
|
|
VERIFY(wrap(dwarf_formudata, attr, &val) == DW_DLV_OK);
|
|
return val;
|
|
} else {
|
|
return nullopt;
|
|
}
|
|
}
|
|
|
|
bool has_attr(Dwarf_Half attr_num) const {
|
|
Dwarf_Bool present = false;
|
|
VERIFY(wrap(dwarf_hasattr, die, attr_num, &present) == DW_DLV_OK);
|
|
return present;
|
|
}
|
|
|
|
Dwarf_Half get_tag() const {
|
|
Dwarf_Half tag = 0;
|
|
VERIFY(wrap(dwarf_tag, die, &tag) == DW_DLV_OK);
|
|
return tag;
|
|
}
|
|
|
|
const char* get_tag_name() const {
|
|
const char* tag_name;
|
|
if(dwarf_get_TAG_name(get_tag(), &tag_name) == DW_DLV_OK) {
|
|
return tag_name;
|
|
} else {
|
|
return "<unknown tag name>";
|
|
}
|
|
}
|
|
|
|
Dwarf_Off get_global_offset() const {
|
|
Dwarf_Off off;
|
|
VERIFY(wrap(dwarf_dieoffset, die, &off) == DW_DLV_OK);
|
|
return off;
|
|
}
|
|
|
|
die_object resolve_reference_attribute(Dwarf_Half attr_num) const {
|
|
Dwarf_Attribute attr;
|
|
VERIFY(dwarf_attr(die, attr_num, &attr, nullptr) == DW_DLV_OK);
|
|
auto wrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
|
Dwarf_Half form = 0;
|
|
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
|
switch(form) {
|
|
case DW_FORM_ref1:
|
|
case DW_FORM_ref2:
|
|
case DW_FORM_ref4:
|
|
case DW_FORM_ref8:
|
|
case DW_FORM_ref_udata:
|
|
{
|
|
Dwarf_Off off = 0;
|
|
Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die);
|
|
VERIFY(wrap(dwarf_formref, attr, &off, &is_info) == DW_DLV_OK);
|
|
Dwarf_Off global_offset = 0;
|
|
VERIFY(wrap(dwarf_convert_to_global_offset, attr, off, &global_offset) == DW_DLV_OK);
|
|
Dwarf_Die target = nullptr;
|
|
VERIFY(wrap(dwarf_offdie_b, dbg, global_offset, is_info, &target) == DW_DLV_OK);
|
|
return die_object(dbg, target);
|
|
}
|
|
case DW_FORM_ref_addr:
|
|
{
|
|
Dwarf_Off off;
|
|
VERIFY(wrap(dwarf_global_formref, attr, &off) == DW_DLV_OK);
|
|
int is_info = dwarf_get_die_infotypes_flag(die);
|
|
Dwarf_Die target = nullptr;
|
|
VERIFY(wrap(dwarf_offdie_b, dbg, off, is_info, &target) == DW_DLV_OK);
|
|
return die_object(dbg, target);
|
|
}
|
|
case DW_FORM_ref_sig8:
|
|
{
|
|
Dwarf_Sig8 signature;
|
|
VERIFY(wrap(dwarf_formsig8, attr, &signature) == DW_DLV_OK);
|
|
Dwarf_Die target = nullptr;
|
|
Dwarf_Bool targ_is_info = false;
|
|
VERIFY(wrap(dwarf_find_die_given_sig8, dbg, &signature, &target, &targ_is_info) == DW_DLV_OK);
|
|
return die_object(dbg, target);
|
|
}
|
|
default:
|
|
PANIC(microfmt::format("unknown form for attribute {} {}\n", attr_num, form));
|
|
}
|
|
}
|
|
|
|
Dwarf_Unsigned get_ranges_offset(Dwarf_Attribute attr) const {
|
|
Dwarf_Unsigned off = 0;
|
|
Dwarf_Half form = 0;
|
|
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
|
if (form == DW_FORM_rnglistx) {
|
|
VERIFY(wrap(dwarf_formudata, attr, &off) == DW_DLV_OK);
|
|
} else {
|
|
VERIFY(wrap(dwarf_global_formref, attr, &off) == DW_DLV_OK);
|
|
}
|
|
return off;
|
|
}
|
|
|
|
template<typename F>
|
|
// callback should return true to keep going
|
|
void dwarf5_ranges(F callback) const {
|
|
Dwarf_Attribute attr = nullptr;
|
|
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
|
|
return;
|
|
}
|
|
auto attrwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
|
Dwarf_Unsigned offset = get_ranges_offset(attr);
|
|
Dwarf_Half form = 0;
|
|
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
|
// get .debug_rnglists info
|
|
Dwarf_Rnglists_Head head = nullptr;
|
|
Dwarf_Unsigned rnglists_entries = 0;
|
|
Dwarf_Unsigned dw_global_offset_of_rle_set = 0;
|
|
int res = wrap(
|
|
dwarf_rnglists_get_rle_head,
|
|
attr,
|
|
form,
|
|
offset,
|
|
&head,
|
|
&rnglists_entries,
|
|
&dw_global_offset_of_rle_set
|
|
);
|
|
auto headwrapper = raii_wrap(head, [] (Dwarf_Rnglists_Head head) { dwarf_dealloc_rnglists_head(head); });
|
|
if(res == DW_DLV_NO_ENTRY) {
|
|
return;
|
|
}
|
|
VERIFY(res == DW_DLV_OK);
|
|
for(std::size_t i = 0 ; i < rnglists_entries; i++) {
|
|
unsigned entrylen = 0;
|
|
unsigned rle_value_out = 0;
|
|
Dwarf_Unsigned raw1 = 0;
|
|
Dwarf_Unsigned raw2 = 0;
|
|
Dwarf_Bool unavailable = 0;
|
|
Dwarf_Unsigned cooked1 = 0;
|
|
Dwarf_Unsigned cooked2 = 0;
|
|
res = wrap(
|
|
dwarf_get_rnglists_entry_fields_a,
|
|
head,
|
|
i,
|
|
&entrylen,
|
|
&rle_value_out,
|
|
&raw1,
|
|
&raw2,
|
|
&unavailable,
|
|
&cooked1,
|
|
&cooked2
|
|
);
|
|
if(res == DW_DLV_NO_ENTRY) {
|
|
continue;
|
|
}
|
|
VERIFY(res == DW_DLV_OK);
|
|
if(unavailable) {
|
|
continue;
|
|
}
|
|
switch(rle_value_out) {
|
|
// Following the same scheme from libdwarf-addr2line
|
|
case DW_RLE_end_of_list:
|
|
case DW_RLE_base_address:
|
|
case DW_RLE_base_addressx:
|
|
// Already handled
|
|
break;
|
|
case DW_RLE_offset_pair:
|
|
case DW_RLE_startx_endx:
|
|
case DW_RLE_start_end:
|
|
case DW_RLE_startx_length:
|
|
case DW_RLE_start_length:
|
|
if(!callback(cooked1, cooked2)) {
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
PANIC("Something is wrong");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename F>
|
|
// callback should return true to keep going
|
|
void dwarf4_ranges(Dwarf_Addr lowpc, F callback) const {
|
|
Dwarf_Attribute attr = nullptr;
|
|
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
|
|
return;
|
|
}
|
|
auto attrwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
|
Dwarf_Unsigned offset;
|
|
if(wrap(dwarf_global_formref, attr, &offset) != DW_DLV_OK) {
|
|
return;
|
|
}
|
|
Dwarf_Addr baseaddr = 0;
|
|
if(lowpc != (std::numeric_limits<Dwarf_Addr>::max)()) {
|
|
baseaddr = lowpc;
|
|
}
|
|
Dwarf_Ranges* ranges = nullptr;
|
|
Dwarf_Signed count = 0;
|
|
VERIFY(
|
|
wrap(
|
|
dwarf_get_ranges_b,
|
|
dbg,
|
|
offset,
|
|
die,
|
|
nullptr,
|
|
&ranges,
|
|
&count,
|
|
nullptr
|
|
) == DW_DLV_OK
|
|
);
|
|
auto rangeswrapper = raii_wrap(
|
|
ranges,
|
|
[this, count] (Dwarf_Ranges* ranges) { dwarf_dealloc_ranges(dbg, ranges, count); }
|
|
);
|
|
for(int i = 0; i < count; i++) {
|
|
if(ranges[i].dwr_type == DW_RANGES_ENTRY) {
|
|
if(!callback(baseaddr + ranges[i].dwr_addr1, baseaddr + ranges[i].dwr_addr2)) {
|
|
return;
|
|
}
|
|
} else if(ranges[i].dwr_type == DW_RANGES_ADDRESS_SELECTION) {
|
|
baseaddr = ranges[i].dwr_addr2;
|
|
} else {
|
|
ASSERT(ranges[i].dwr_type == DW_RANGES_END);
|
|
baseaddr = lowpc;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename F>
|
|
// callback should return true to keep going
|
|
void dwarf_ranges(int version, optional<Dwarf_Addr> pc, F callback) const {
|
|
Dwarf_Addr lowpc = (std::numeric_limits<Dwarf_Addr>::max)();
|
|
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
|
|
if(pc.has_value() && pc.unwrap() == lowpc) {
|
|
callback(lowpc, lowpc + 1);
|
|
return;
|
|
}
|
|
Dwarf_Addr highpc = 0;
|
|
enum Dwarf_Form_Class return_class;
|
|
if(wrap(dwarf_highpc_b, die, &highpc, nullptr, &return_class) == DW_DLV_OK) {
|
|
if(return_class == DW_FORM_CLASS_CONSTANT) {
|
|
highpc += lowpc;
|
|
}
|
|
if(!callback(lowpc, highpc)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if(version >= 5) {
|
|
dwarf5_ranges(callback);
|
|
} else {
|
|
dwarf4_ranges(lowpc, callback);
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>> get_rangelist_entries(int version) const {
|
|
std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>> vec;
|
|
dwarf_ranges(version, nullopt, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
|
|
// Simple coalescing optimization:
|
|
// Sometimes the range list entries are really continuous: [100, 200), [200, 300)
|
|
// Other times there's just one byte of separation [300, 399), [400, 500)
|
|
// Those are the main two cases I've observed.
|
|
// This will not catch all cases, presumably, as the range lists aren't sorted. But compilers/linkers
|
|
// seem to like to emit the ranges in sorted order.
|
|
if(!vec.empty() && low - vec.back().second <= 1) {
|
|
vec.back().second = high;
|
|
} else {
|
|
vec.push_back({low, high});
|
|
}
|
|
return true;
|
|
});
|
|
return vec;
|
|
}
|
|
|
|
Dwarf_Bool pc_in_die(int version, Dwarf_Addr pc) const {
|
|
bool found = false;
|
|
dwarf_ranges(version, pc, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
|
|
if(pc >= low && pc < high) {
|
|
found = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
return found;
|
|
}
|
|
|
|
void print() const {
|
|
std::fprintf(
|
|
stderr,
|
|
"%08llx %s %s\n",
|
|
to_ull(get_global_offset()),
|
|
get_tag_name(),
|
|
get_name().c_str()
|
|
);
|
|
}
|
|
};
|
|
|
|
// walk die list, callback is called on each die and should return true to
|
|
// continue traversal
|
|
// returns true if traversal should continue
|
|
bool walk_die_list(
|
|
const die_object& die,
|
|
const std::function<bool(const die_object&)>& fn
|
|
) {
|
|
// TODO: Refactor so there is only one fn call
|
|
bool continue_traversal = true;
|
|
if(fn(die)) {
|
|
die_object current = die.get_sibling();
|
|
while(current) {
|
|
if(fn(current)) {
|
|
current = current.get_sibling();
|
|
} else {
|
|
continue_traversal = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return continue_traversal;
|
|
}
|
|
|
|
// walk die list, recursing into children, callback is called on each die
|
|
// and should return true to continue traversal
|
|
// returns true if traversal should continue
|
|
bool walk_die_list_recursive(
|
|
const die_object& die,
|
|
const std::function<bool(const die_object&)>& fn
|
|
) {
|
|
return walk_die_list(
|
|
die,
|
|
[&fn](const die_object& die) {
|
|
auto child = die.get_child();
|
|
if(child) {
|
|
if(!walk_die_list_recursive(child, fn)) {
|
|
return false;
|
|
}
|
|
}
|
|
return fn(die);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|