#ifndef DWARF_HPP #define DWARF_HPP #include #include "../utils/error.hpp" #include "../utils/utils.hpp" #include #include #include #ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH #include #include #else #ifndef CPPTRACE_CONDA_LIBDWARF_WEIRDNESS #include #include #else #include #include #endif #endif namespace cpptrace { namespace detail { namespace libdwarf { static_assert(std::is_pointer::value, "Dwarf_Die not a pointer"); static_assert(std::is_pointer::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()(std::forward(std::declval())..., nullptr) ), void >::value, int >::type = 0 > int wrap(int (*f)(Args...), Args2&&... args) const { Dwarf_Error error = nullptr; int ret = f(std::forward(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 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 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 ""; } } 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 // 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 // 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::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 // callback should return true to keep going void dwarf_ranges(int version, optional pc, F callback) const { Dwarf_Addr lowpc = (std::numeric_limits::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> get_rangelist_entries(int version) const { std::vector> 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& 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& 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