Optimize dwarf handling by caching line contexts so the line info table only has to be computed once per TU per object per trace

This commit is contained in:
Jeremy 2023-09-17 00:54:11 -04:00
parent e47cb7147d
commit e4eab1d426
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4

View File

@ -558,288 +558,334 @@ namespace libdwarf {
frame.symbol = name + "(" + join(params, ", ") + ")";*/ frame.symbol = name + "(" + join(params, ", ") + ")";*/
} }
// returns true if this call found the symbol struct line_context {
bool retrieve_symbol(
Dwarf_Debug dbg,
const die_object& die,
Dwarf_Addr pc,
Dwarf_Half dwversion,
stacktrace_frame& frame
) {
bool found = false;
walk_die_list(
dbg,
die,
[pc, dwversion, &frame, &found] (Dwarf_Debug dbg, const die_object& die) {
if(dump_dwarf) {
fprintf(
stderr,
"-------------> %08llx %s %s\n",
(unsigned long long) die.get_global_offset(),
die.get_tag_name(),
die.get_name().c_str()
);
}
if(!(die.get_tag() == DW_TAG_namespace || pc_in_die(dbg, die.get(), dwversion, pc))) {
if(dump_dwarf) {
fprintf(stderr, "pc not in die\n");
}
} else {
if(trace_dwarf) {
fprintf(
stderr,
"%s %08llx %s\n",
die.get_tag() == DW_TAG_namespace ? "pc maybe in die (namespace)" : "pc in die",
(unsigned long long) die.get_global_offset(),
die.get_tag_name()
);
}
if(die.get_tag() == DW_TAG_subprogram) {
retrieve_symbol_for_subprogram(dbg, die, pc, dwversion, frame);
found = true;
return false;
}
auto child = die.get_child();
if(child) {
if(retrieve_symbol(dbg, child, pc, dwversion, frame)) {
found = true;
return false;
}
} else {
if(dump_dwarf) {
fprintf(stderr, "(no child)\n");
}
}
}
return true;
}
);
return found;
}
void retrieve_line_info(
Dwarf_Debug dbg,
const die_object& die,
Dwarf_Addr pc,
Dwarf_Half dwversion,
stacktrace_frame& frame
) {
Dwarf_Unsigned version; Dwarf_Unsigned version;
Dwarf_Small table_count; Dwarf_Small table_count;
Dwarf_Line_Context ctxt; Dwarf_Line_Context ctx;
Dwarf_Bool is_found = false; };
(void)dwversion;
int ret = dwarf_srclines_b(
die.get(),
&version,
&table_count,
&ctxt,
nullptr
);
if(ret == DW_DLV_NO_ENTRY) {
fprintf(stderr, "dwarf_srclines_b error\n");
return;
}
if(table_count == 1) {
Dwarf_Line *linebuf = 0;
Dwarf_Signed linecount = 0;
Dwarf_Addr prev_lineaddr = 0;
dwarf_srclines_from_linecontext(ctxt, &linebuf, struct dwarf_resolver {
&linecount, nullptr); std::string obj_path;
Dwarf_Line prev_line = 0; Dwarf_Debug dbg;
for(int i = 0; i < linecount; i++) { std::unordered_map<Dwarf_Off, line_context> line_contexts;
Dwarf_Line line = linebuf[i];
Dwarf_Addr lineaddr = 0;
dwarf_lineaddr(line, &lineaddr, nullptr); CPPTRACE_FORCE_NO_INLINE
if(pc == lineaddr) { dwarf_resolver(const std::string& object_path) {
/* Print the last line entry containing current pc. */ obj_path = object_path;
Dwarf_Line last_pc_line = line; #if IS_APPLE
if(directory_exists(obj_path + ".dSYM")) {
for(int j = i + 1; j < linecount; j++) { obj_path += ".dSYM/Contents/Resources/DWARF/" + basename(object_path);
Dwarf_Line j_line = linebuf[j];
dwarf_lineaddr(j_line, &lineaddr, nullptr);
if(pc == lineaddr) {
last_pc_line = j_line;
}
}
is_found = true;
print_line(dbg, last_pc_line, pc, frame);
break;
} else if(prev_line && pc > prev_lineaddr &&
pc < lineaddr) {
is_found = true;
print_line(dbg, prev_line, pc, frame);
break;
}
Dwarf_Bool is_lne;
dwarf_lineendsequence(line, &is_lne, nullptr);
if(is_lne) {
prev_line = 0;
} else {
prev_lineaddr = lineaddr;
prev_line = line;
}
} }
} #endif
dwarf_srclines_dealloc_b(ctxt);
}
void walk_compilation_units(Dwarf_Debug dbg, Dwarf_Addr pc, stacktrace_frame& frame) { Dwarf_Ptr errarg = 0;
// 0 passed as the die to the first call of dwarf_siblingof_b immediately after dwarf_next_cu_header_d auto ret = dwarf_init_path(
// to fetch the cu die obj_path.c_str(),
die_object cu_die(dbg, nullptr);
cu_die = cu_die.get_sibling();
if(!cu_die) {
if(dump_dwarf) {
fprintf(stderr, "End walk_compilation_units\n");
}
return;
}
walk_die_list(
dbg,
cu_die,
[&frame, pc] (Dwarf_Debug dbg, 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);
if(trace_dwarf) {
fprintf(stderr, "CU: %d %s\n", dwversion, cu_die.get_name().c_str());
}
Dwarf_Unsigned offset = 0;
// TODO: I'm unsure if I'm supposed to take DW_AT_rnglists_base into account here
// However it looks like it is correct when not taking an offset into account and incorrect
// otherwise
//if(dwversion >= 5) {
// Dwarf_Attribute attr;
// int ret = dwarf_attr(cu_die.get(), DW_AT_rnglists_base, &attr, nullptr);
// CPPTRACE_VERIFY(ret == DW_DLV_OK);
// Dwarf_Unsigned uval = 0;
// ret = dwarf_global_formref(attr, &uval, nullptr);
// offset = uval;
// dwarf_dealloc_attribute(attr);
//}
//fprintf(stderr, "------------> pc: %llx offset: %llx final: %llx\n", pc, offset, pc - offset);
if(pc_in_die(dbg, cu_die.get(), dwversion, pc - offset)) {
if(trace_dwarf) {
fprintf(
stderr,
"pc in die %08llx %s (now searching for %08llx)\n",
(unsigned long long) cu_die.get_global_offset(),
cu_die.get_tag_name(),
pc - offset
);
}
retrieve_line_info(dbg, cu_die, pc, dwversion, frame); // no offset for line info
retrieve_symbol(dbg, cu_die, pc - offset, dwversion, frame);
return false;
}
return true;
}
);
}
void walk_dbg(Dwarf_Debug dbg, Dwarf_Addr pc, stacktrace_frame& frame) {
// libdwarf keeps track of where it is in the file, dwarf_next_cu_header_d is statefull
Dwarf_Unsigned next_cu_header;
Dwarf_Half header_cu_type;
while(true) {
int ret = dwarf_next_cu_header_d(
dbg,
true,
nullptr, nullptr,
nullptr, 0,
nullptr, DW_GROUPNUMBER_ANY,
nullptr, err_handler,
nullptr, errarg,
nullptr, &dbg,
nullptr,
nullptr,
&next_cu_header,
&header_cu_type,
nullptr nullptr
); );
if(ret == DW_DLV_NO_ENTRY) { if(ret == DW_DLV_NO_ENTRY) {
// fail, no debug info
} else if(ret != DW_DLV_OK) {
fprintf(stderr, "Error\n");
} else {
}
}
CPPTRACE_FORCE_NO_INLINE
~dwarf_resolver() {
dwarf_finish(dbg);
for(auto& entry : line_contexts) {
dwarf_srclines_dealloc_b(entry.second.ctx);
}
}
// returns true if this call found the symbol
CPPTRACE_FORCE_NO_INLINE
bool retrieve_symbol(
Dwarf_Debug dbg,
const die_object& die,
Dwarf_Addr pc,
Dwarf_Half dwversion,
stacktrace_frame& frame
) {
bool found = false;
walk_die_list(
dbg,
die,
[this, pc, dwversion, &frame, &found] (Dwarf_Debug dbg, const die_object& die) {
if(dump_dwarf) {
fprintf(
stderr,
"-------------> %08llx %s %s\n",
(unsigned long long) die.get_global_offset(),
die.get_tag_name(),
die.get_name().c_str()
);
}
if(!(die.get_tag() == DW_TAG_namespace || pc_in_die(dbg, die.get(), dwversion, pc))) {
if(dump_dwarf) {
fprintf(stderr, "pc not in die\n");
}
} else {
if(trace_dwarf) {
fprintf(
stderr,
"%s %08llx %s\n",
die.get_tag() == DW_TAG_namespace ? "pc maybe in die (namespace)" : "pc in die",
(unsigned long long) die.get_global_offset(),
die.get_tag_name()
);
}
if(die.get_tag() == DW_TAG_subprogram) {
retrieve_symbol_for_subprogram(dbg, die, pc, dwversion, frame);
found = true;
return false;
}
auto child = die.get_child();
if(child) {
if(retrieve_symbol(dbg, child, pc, dwversion, frame)) {
found = true;
return false;
}
} else {
if(dump_dwarf) {
fprintf(stderr, "(no child)\n");
}
}
}
return true;
}
);
return found;
}
CPPTRACE_FORCE_NO_INLINE
void retrieve_line_info(
Dwarf_Debug dbg,
const die_object& die,
Dwarf_Addr pc,
Dwarf_Half dwversion,
stacktrace_frame& frame
) {
Dwarf_Unsigned version;
Dwarf_Small table_count;
Dwarf_Line_Context ctxt;
Dwarf_Bool is_found = false;
(void)dwversion;
auto off = die.get_global_offset();
auto it = line_contexts.find(off);
if(it != line_contexts.end()) {
auto& entry = it->second;
version = entry.version;
table_count = entry.table_count;
ctxt = entry.ctx;
} else {
int ret = dwarf_srclines_b(
die.get(),
&version,
&table_count,
&ctxt,
nullptr
);
line_contexts.insert({off, {version, table_count, ctxt}});
if(ret == DW_DLV_NO_ENTRY) {
fprintf(stderr, "dwarf_srclines_b error\n");
return;
}
}
if(table_count == 1) {
Dwarf_Line *linebuf = 0;
Dwarf_Signed linecount = 0;
Dwarf_Addr prev_lineaddr = 0;
dwarf_srclines_from_linecontext(ctxt, &linebuf,
&linecount, nullptr);
Dwarf_Line prev_line = 0;
for(int i = 0; i < linecount; i++) {
Dwarf_Line line = linebuf[i];
Dwarf_Addr lineaddr = 0;
dwarf_lineaddr(line, &lineaddr, nullptr);
if(pc == lineaddr) {
/* Print the last line entry containing current pc. */
Dwarf_Line last_pc_line = line;
for(int j = i + 1; j < linecount; j++) {
Dwarf_Line j_line = linebuf[j];
dwarf_lineaddr(j_line, &lineaddr, nullptr);
if(pc == lineaddr) {
last_pc_line = j_line;
}
}
is_found = true;
print_line(dbg, last_pc_line, pc, frame);
break;
} else if(prev_line && pc > prev_lineaddr &&
pc < lineaddr) {
is_found = true;
print_line(dbg, prev_line, pc, frame);
break;
}
Dwarf_Bool is_lne;
dwarf_lineendsequence(line, &is_lne, nullptr);
if(is_lne) {
prev_line = 0;
} else {
prev_lineaddr = lineaddr;
prev_line = line;
}
}
}
}
CPPTRACE_FORCE_NO_INLINE
void walk_compilation_units(Dwarf_Debug dbg, Dwarf_Addr pc, stacktrace_frame& frame) {
// 0 passed as the die to the first call of dwarf_siblingof_b immediately after dwarf_next_cu_header_d
// to fetch the cu die
die_object cu_die(dbg, nullptr);
cu_die = cu_die.get_sibling();
if(!cu_die) {
if(dump_dwarf) { if(dump_dwarf) {
fprintf(stderr, "End walk_dbg\n"); fprintf(stderr, "End walk_compilation_units\n");
} }
return; return;
} }
if(ret != DW_DLV_OK) { walk_die_list(
fprintf(stderr, "Error\n"); dbg,
return; cu_die,
} [this, &frame, pc] (Dwarf_Debug dbg, const die_object& cu_die) {
walk_compilation_units(dbg, pc, frame); Dwarf_Half offset_size = 0;
} Dwarf_Half dwversion = 0;
} dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
if(trace_dwarf) {
void lookup_pc( fprintf(stderr, "CU: %d %s\n", dwversion, cu_die.get_name().c_str());
const char* object, }
Dwarf_Addr pc, Dwarf_Unsigned offset = 0;
stacktrace_frame& frame // TODO: I'm unsure if I'm supposed to take DW_AT_rnglists_base into account here
) { // However it looks like it is correct when not taking an offset into account and incorrect
if(dump_dwarf) { // otherwise
fprintf(stderr, "%s\n", object); //if(dwversion >= 5) {
fprintf(stderr, "%llx\n", pc); // Dwarf_Attribute attr;
} // int ret = dwarf_attr(cu_die.get(), DW_AT_rnglists_base, &attr, nullptr);
Dwarf_Debug dbg; // CPPTRACE_VERIFY(ret == DW_DLV_OK);
Dwarf_Ptr errarg = 0; // Dwarf_Unsigned uval = 0;
auto ret = dwarf_init_path( // ret = dwarf_global_formref(attr, &uval, nullptr);
object, // offset = uval;
nullptr, // dwarf_dealloc_attribute(attr);
0, //}
DW_GROUPNUMBER_ANY, //fprintf(stderr, "------------> pc: %llx offset: %llx final: %llx\n", pc, offset, pc - offset);
err_handler, if(pc_in_die(dbg, cu_die.get(), dwversion, pc - offset)) {
errarg, if(trace_dwarf) {
&dbg, fprintf(
nullptr stderr,
); "pc in die %08llx %s (now searching for %08llx)\n",
if(ret == DW_DLV_NO_ENTRY) { (unsigned long long) cu_die.get_global_offset(),
// fail, no debug info cu_die.get_tag_name(),
} else if(ret != DW_DLV_OK) { pc - offset
fprintf(stderr, "Error\n"); );
} else { }
walk_dbg(dbg, pc, frame); retrieve_line_info(dbg, cu_die, pc, dwversion, frame); // no offset for line info
} retrieve_symbol(dbg, cu_die, pc - offset, dwversion, frame);
dwarf_finish(dbg); return false;
} }
return true;
stacktrace_frame resolve_frame(const dlframe& frame_info) { }
stacktrace_frame frame{};
frame.filename = frame_info.obj_path;
frame.symbol = frame_info.symbol;
frame.address = frame_info.raw_address;
std::string obj_path = frame_info.obj_path;
#if IS_APPLE
if(directory_exists(obj_path + ".dSYM")) {
obj_path += ".dSYM/Contents/Resources/DWARF/" + basename(frame_info.obj_path);
}
#endif
if(trace_dwarf) {
fprintf(
stderr,
"Starting resolution for %s %08llx %s\n",
obj_path.c_str(),
(unsigned long long)frame_info.obj_address,
frame_info.symbol.c_str()
); );
} }
lookup_pc(
obj_path.c_str(),
frame_info.obj_address,
frame
);
return frame;
}
CPPTRACE_FORCE_NO_INLINE
void walk_dbg(Dwarf_Debug dbg, Dwarf_Addr pc, stacktrace_frame& frame) {
// libdwarf keeps track of where it is in the file, dwarf_next_cu_header_d is statefull
Dwarf_Unsigned next_cu_header;
Dwarf_Half header_cu_type;
while(true) {
int ret = dwarf_next_cu_header_d(
dbg,
true,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&next_cu_header,
&header_cu_type,
nullptr
);
if(ret == DW_DLV_NO_ENTRY) {
if(dump_dwarf) {
fprintf(stderr, "End walk_dbg\n");
}
return;
}
if(ret != DW_DLV_OK) {
fprintf(stderr, "Error\n");
return;
}
walk_compilation_units(dbg, pc, frame);
}
}
CPPTRACE_FORCE_NO_INLINE
void lookup_pc(
const char* object,
Dwarf_Addr pc,
stacktrace_frame& frame
) {
if(dump_dwarf) {
fprintf(stderr, "%s\n", object);
fprintf(stderr, "%llx\n", pc);
}
walk_dbg(dbg, pc, frame);
}
CPPTRACE_FORCE_NO_INLINE
stacktrace_frame resolve_frame(const dlframe& frame_info) {
stacktrace_frame frame{};
frame.filename = frame_info.obj_path;
frame.symbol = frame_info.symbol;
frame.address = frame_info.raw_address;
if(trace_dwarf) {
fprintf(
stderr,
"Starting resolution for %s %08llx %s\n",
obj_path.c_str(),
(unsigned long long)frame_info.obj_address,
frame_info.symbol.c_str()
);
}
lookup_pc(
obj_path.c_str(),
frame_info.obj_address,
frame
);
return frame;
}
};
CPPTRACE_FORCE_NO_INLINE
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) { std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> trace; std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
trace.reserve(frames.size()); const auto dlframes = get_frames_object_info(frames);
for(const auto& frame : get_frames_object_info(frames)) { for(const auto& obj_entry : collate_frames(dlframes, trace)) {
trace.push_back(resolve_frame(frame)); const auto& obj_name = obj_entry.first;
dwarf_resolver resolver(obj_name);
for(const auto& entry : obj_entry.second) {
const auto& dlframe = entry.first.get();
auto& frame = entry.second.get();
frame = resolver.resolve_frame(dlframe);
}
} }
return trace; return trace;
} }