Scraps of an implementation of debug-fission handling

This commit is contained in:
Jeremy 2024-05-27 17:12:52 -05:00
parent 5556aedddb
commit f153d39e67
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
4 changed files with 263 additions and 23 deletions

View File

@ -572,14 +572,19 @@ namespace cpptrace {
# Supported Debug Formats
| Format | Supported |
| ------------------------------------------------ | --------- |
| DWARF in binary | ✔️ |
| DWARF in separate binary (binary gnu debug link) | ️️✔️ |
| DWARF in separate binary (split dwarf) | ✔️ |
| DWARF in dSYM | ✔️ |
| DWARF in via Mach-O debug map | ✔️ |
| Windows debug symbols in PDB | ✔️ |
| Format | Supported |
| --------------------------------- | --------- |
| DWARF in binary | ✔️ |
| GNU debug link | ️️✔️ |
| Split dwarf with dwarf 5 | ✔️* |
| Split dwarf with extended dwarf 4 | ❌** |
| DWARF in dSYM | ✔️ |
| DWARF via Mach-O debug map | ✔️ |
| Windows debug symbols in PDB | ✔️ |
*There seem to be a couple issues upstream with libdwarf however they will hopefully be resolved soon
**Waiting on support from upstream libdwarf
DWARF5 added DWARF package files. As far as I can tell no compiler implements these yet.

View File

@ -8,6 +8,7 @@
#include "../../utils/dwarf.hpp" // has dwarf #includes
#include "../../utils/error.hpp"
#include "../../utils/utils.hpp"
#include "../../utils/path.hpp"
#include "../../utils/program_name.hpp" // For CPPTRACE_MAX_PATH
#include "../../binary/mach-o.hpp"
@ -33,7 +34,7 @@ namespace detail {
namespace libdwarf {
// printbugging as we go
constexpr bool dump_dwarf = false;
constexpr bool trace_dwarf = false;
constexpr bool trace_dwarf = true;
struct subprogram_entry {
die_object die;
@ -67,6 +68,16 @@ namespace libdwarf {
std::vector<line_entry> line_entries;
};
using addr_table = std::vector<Dwarf_Unsigned>;
// used to describe data from an upstream binary to a resolver for the .dwo
struct split_info {
// addr_table addresses;
// std::size_t addr_base;
rangelist_entries ranges_vec;
Dwarf_Debug executable_dbg;
};
class dwarf_resolver : public symbol_resolver {
std::string object_path;
Dwarf_Debug dbg = nullptr;
@ -83,6 +94,8 @@ namespace libdwarf {
bool generated_cu_cache = false;
// Map from CU -> {srcfiles, count}
std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
// info for resolving a dwo object
optional<split_info> parent_info;
private:
// Error handling helper
@ -112,8 +125,10 @@ namespace libdwarf {
public:
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
dwarf_resolver(const std::string& _object_path) {
object_path = _object_path;
dwarf_resolver(const std::string& object_path, optional<split_info> parent_info_ = nullopt)
: object_path(object_path),
parent_info(std::move(parent_info_))
{
// use a buffer when invoking dwarf_init_path, which allows it to automatically find debuglink or dSYM
// sources
bool use_buffer = true;
@ -170,10 +185,15 @@ namespace libdwarf {
PANIC("Unknown return code from dwarf_init_path");
}
if(parent_info) {
VERIFY(wrap(dwarf_set_tied_dbg, dbg, parent_info.unwrap().executable_dbg) == DW_DLV_OK);
}
if(ok) {
// Check for .debug_aranges for fast lookup
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
}
std::cout<<ok<<std::endl;
}
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
@ -285,11 +305,23 @@ namespace libdwarf {
Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0;
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
auto ranges_vec = cu_die.get_rangelist_entries(dwversion);
for(auto range : ranges_vec) {
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
if(parent_info) {
// TODO: Somewhat hackilly assuming a single CU and the address is in it
// Precedence for this assumption is https://dwarfstd.org/doc/DWARF5.pdf#subsection.3.1.3
auto& ranges_vec = parent_info.unwrap().ranges_vec;
for(auto range : ranges_vec) {
// TODO: Reduce cloning here
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
}
return false;
} else {
auto ranges_vec = cu_die.get_rangelist_entries(dwversion);
for(auto range : ranges_vec) {
// TODO: Reduce cloning here
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
}
return true;
}
return true;
});
std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
return a.low < b.low;
@ -515,9 +547,12 @@ namespace libdwarf {
switch(die.get_tag()) {
case DW_TAG_subprogram:
{
die.print();
auto ranges_vec = die.get_rangelist_entries(dwversion);
// TODO: Feels super inefficient and some day should maybe use an interval tree.
for(auto range : ranges_vec) {
// TODO: Reduce cloning here
std::cout<<" "<<range.first<<" "<<range.second<<std::endl;
vec.push_back({ die.clone(), range.first, range.second });
}
// Walk children to get things like lambdas
@ -564,15 +599,21 @@ namespace libdwarf {
if(get_cache_mode() == cache_mode::prioritize_memory) {
retrieve_symbol_walk(cu_die, cu_die, pc, dwversion, frame, inlines);
} else {
std::cout<<"Here"<<std::endl;
auto off = cu_die.get_global_offset();
auto it = subprograms_cache.find(off);
if(it == subprograms_cache.end()) {
std::cout<<"Preprocess"<<std::endl;
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
std::vector<subprogram_entry> vec;
preprocess_subprograms(cu_die, dwversion, vec);
std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) {
return a.low < b.low;
});
std::cout<<"---"<<vec.size()<<std::endl;
for(const auto& s : vec) {
s.die.print();
}
subprograms_cache.emplace(off, std::move(vec));
it = subprograms_cache.find(off);
}
@ -821,7 +862,7 @@ namespace libdwarf {
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
optional<cu_info> lookup_cu(Dwarf_Addr pc) {
// Check for .debug_aranges for fast lookup
if(aranges) {
if(aranges && !parent_info) { // don't bother under split dwarf
// Try to find pc in aranges
Dwarf_Arange arange;
if(wrap(dwarf_get_arange, aranges, arange_count, pc, &arange) == DW_DLV_OK) {
@ -850,7 +891,7 @@ namespace libdwarf {
if(get_cache_mode() == cache_mode::prioritize_memory) {
// walk for the cu and go from there
optional<cu_info> info;
walk_compilation_units([pc, &info] (const die_object& cu_die) {
walk_compilation_units([this, pc, &info] (const die_object& cu_die) {
Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0;
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
@ -860,7 +901,8 @@ namespace libdwarf {
if(trace_dwarf) {
std::fprintf(stderr, "CU: %d %s\n", dwversion, cu_die.get_name().c_str());
}
if(cu_die.pc_in_die(dwversion, pc)) {
// TODO: Somewhat hackilly I'm assuming a single CU and the address should be in it
if(parent_info || cu_die.pc_in_die(dwversion, pc)) {
if(trace_dwarf) {
std::fprintf(
stderr,
@ -890,12 +932,16 @@ namespace libdwarf {
);
// If the vector has been empty this can happen
if(vec_it != cu_cache.end()) {
std::cout<<"Here2"<<std::endl;
//vec_it->die.print();
if(vec_it->die.pc_in_die(vec_it->dwversion, pc)) {
// TODO: Cache the range list?
// TODO: Assumption
if(parent_info || vec_it->die.pc_in_die(vec_it->dwversion, pc)) {
// resolve_pc(vec_it->die, vec_it->dwversion, pc, frame, inlines);
return cu_info{maybe_owned_die_object::ref(vec_it->die), vec_it->dwversion};
}
} else {
std::cout<<"Here3"<<std::endl;
// I've had this happen for _start, where there is a cached CU for the object but _start is outside
// of the CU's PC range
// ASSERT(cu_cache.size() == 0, "Vec should be empty?");
@ -904,9 +950,143 @@ namespace libdwarf {
}
}
// TODO: What is dwp????
// TODO: dwarf_cu_header_basics passes out an is_dwo flag
// TODO: dwarf_get_xu_index_header???
// addr_table load_addr_table() {
// // std::cout<<"addr section:"<<std::endl;
// // Dwarf_Addr address;
// // Dwarf_Unsigned size;
// // VERIFY(
// // wrap(
// // dwarf_get_section_info_by_name_a,
// // dbg,
// // ".debug_addr",
// // &address,
// // &size,
// // nullptr,
// // nullptr
// // ) == DW_DLV_OK
// // );
// // std::cout<<std::hex<<address<<std::endl;
// // std::cout<<size<<std::endl;
// // std::cout<<"addr_table:"<<std::endl;
// addr_table table;
// Dwarf_Unsigned section_offset = 0;
// while(true) {
// Dwarf_Debug_Addr_Table table_header;
// Dwarf_Unsigned length = 0;
// Dwarf_Half version = 0;
// Dwarf_Small address_size = 0;
// Dwarf_Unsigned at_addr_base = 0;
// Dwarf_Unsigned entry_count = 0;
// Dwarf_Unsigned next_table_offset = 0;
// int res = wrap(
// dwarf_debug_addr_table,
// dbg,
// section_offset,
// &table_header,
// &length,
// &version,
// &address_size,
// &at_addr_base,
// &entry_count,
// &next_table_offset
// );
// if(res == DW_DLV_NO_ENTRY) {
// break;
// }
// for(Dwarf_Unsigned i = 0; i < entry_count; i++) {
// Dwarf_Unsigned address = 0;
// VERIFY(
// wrap(
// dwarf_debug_addr_by_index,
// table_header,
// i,
// &address
// ) == DW_DLV_OK
// );
// // std::cout<<" "<<i<<" "<<std::hex<<address<<std::dec<<std::endl;
// table.push_back(address);
// }
// section_offset = next_table_offset;
// }
// return table;
// }
optional<std::string> get_dwo_name(const die_object& cu_die) {
if(auto dwo_name = cu_die.get_string_attribute(DW_AT_GNU_dwo_name)) {
return dwo_name;
} else if(auto dwo_name = cu_die.get_string_attribute(DW_AT_dwo_name)) {
return dwo_name;
} else {
return nullopt;
}
}
void perform_dwarf_fission_resolution(
Dwarf_Addr pc,
const die_object& cu_die,
const object_frame& object_frame_info,
stacktrace_frame& frame,
std::vector<stacktrace_frame>& inlines
) {
// Split dwarf / debug fission / dwo is handled here
// https://gcc.gnu.org/wiki/DebugFission
// Some oddities:
// - The .debug_line table remains in the main object
// - Due to relocations the dwo file won't have object addresses and instead DW_AT_low_pc will be of
// type DW_FORM_addr_index referencing the .debug_addr table stored in the main object
// - These index is relative to the DW_AT_addr_base
// DW_AT_dwo_name/DW_AT_GNU_dwo_name
// DW_AT_comp_dir
// DW_AT_addr_base/DW_AT_GNU_addr_base
// DW_AT_GNU_dwo_id/...?
// DW_TAG_skeleton_unit vs ...
// TODO: Handle gnu dwarf4 extensions for this
// TODO: DWO ID
std::cout<<"------------------------"<<std::endl;
if(auto dwo_name = get_dwo_name(cu_die)) {
auto comp_dir = cu_die.get_string_attribute(DW_AT_comp_dir);
Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0;
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
std::cout<<"dwo_name: "<<dwo_name.unwrap()<<std::endl;
std::cout<<"comp_dir: "<<comp_dir.unwrap()<<std::endl;
std::string path;
if(is_absolute(dwo_name.unwrap())) {
path = dwo_name.unwrap();
} else if(comp_dir) {
path = comp_dir.unwrap() + PATH_SEP + dwo_name.unwrap();
} else {
// maybe default to dwo_name but for now not doing anything
return;
}
std::cout<<path<<std::endl;
// auto addr_table = load_addr_table();
dwarf_resolver resolver(
path,
split_info{/*std::move(addr_table), 0,*/ cu_die.get_rangelist_entries(dwversion), dbg}
);
std::cout<<"-sub resolve-"<<std::endl;
auto res = resolver.resolve_frame(object_frame_info);
frame = res.frame;
inlines = res.inlines;
stacktrace_frame line_frame;
retrieve_line_info(cu_die, pc, line_frame);
frame.filename = line_frame.filename;
frame.line = line_frame.line;
frame.column = line_frame.column;
std::cout<<"---------------end sub resolve----------------"<<std::endl;
return;
}
}
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
void resolve_pc(
Dwarf_Addr pc,
const object_frame& object_frame_info,
stacktrace_frame& frame,
std::vector<stacktrace_frame>& inlines
) {
@ -915,10 +1095,17 @@ namespace libdwarf {
std::fprintf(stderr, "%llx\n", to_ull(pc));
}
optional<cu_info> cu = lookup_cu(pc);
std::cout<<"Here1 "<<!!cu<<std::endl;
if(cu) {
const auto& cu_die = cu.unwrap().cu_die.get();
retrieve_line_info(cu_die, pc, frame);
retrieve_symbol(cu_die, pc, cu.unwrap().dwversion, frame, inlines);
cu_die.print();
// TODO: Handle gnu dwarf4 extensions for this
if(cu_die.get_tag() == DW_TAG_skeleton_unit) {
perform_dwarf_fission_resolution(pc, cu_die, object_frame_info, frame, inlines);
} else {
retrieve_line_info(cu_die, pc, frame);
retrieve_symbol(cu_die, pc, cu.unwrap().dwversion, frame, inlines);
}
}
}
@ -954,6 +1141,7 @@ namespace libdwarf {
std::vector<stacktrace_frame> inlines;
resolve_pc(
frame_info.object_address,
frame_info,
frame,
inlines
);

View File

@ -28,6 +28,8 @@ 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");
using rangelist_entries = std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>>;
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
Dwarf_Unsigned ev = dwarf_errno(error);
char* msg = dwarf_errmsg(error);
@ -387,10 +389,13 @@ namespace libdwarf {
// callback should return true to keep going
void dwarf_ranges(int version, F callback) const {
Dwarf_Addr lowpc = (std::numeric_limits<Dwarf_Addr>::max)();
std::cout<<1<<std::endl;
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
std::cout<<2<<std::endl;
Dwarf_Addr highpc = 0;
enum Dwarf_Form_Class return_class;
if(wrap(dwarf_highpc_b, die, &highpc, nullptr, &return_class) == DW_DLV_OK) {
std::cout<<3<<std::endl;
if(return_class == DW_FORM_CLASS_CONSTANT) {
highpc += lowpc;
}
@ -399,6 +404,7 @@ namespace libdwarf {
}
}
}
std::cout<<4<<std::endl;
if(version >= 5) {
dwarf5_ranges(callback);
} else {
@ -406,8 +412,8 @@ namespace libdwarf {
}
}
std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>> get_rangelist_entries(int version) const {
std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>> vec;
rangelist_entries get_rangelist_entries(int version) const {
rangelist_entries vec;
dwarf_ranges(version, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
// Simple coalescing optimization:
// Sometimes the range list entries are really continuous: [100, 200), [200, 300)

41
src/utils/path.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef PATH_HPP
#define PATH_HPP
#include "common.hpp"
#if IS_WINDOWS
#include <windows.h>
#endif
namespace cpptrace {
namespace detail {
#if IS_WINDOWS
constexpr char PATH_SEP = '\\';
inline bool is_absolute(const std::string& path) {
// I don't want to bring in shlwapi as a dependency just for PathIsRelativeA so I'm following the guidance of
// https://stackoverflow.com/a/71941552/15675011 and
// https://github.com/wine-mirror/wine/blob/b210a204137dec8d2126ca909d762454fd47e963/dlls/kernelbase/path.c#L982
if(path.empty() || IsDBCSLeadByte(path[0])) {
return false;
}
if(path[0] == '\\') {
return true;
}
if(path.size() >= 2 && std::isalpha(path[0]) && path[1] == ':') {
return true;
}
return false;
}
#else
constexpr char PATH_SEP = '/';
inline bool is_absolute(const std::string& path) {
if(path.empty()) {
return false;
}
return path[0] == '/';
}
#endif
}
}
#endif