Skip to content

Commit 3db4194

Browse files
committed
Detect dwo references to clang ast files (used in -gmodules) and don't treat them as normal debug fission
1 parent d525775 commit 3db4194

File tree

1 file changed

+100
-45
lines changed

1 file changed

+100
-45
lines changed

src/symbols/dwarf/dwarf_resolver.cpp

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "utils/error.hpp"
1212
#include "utils/utils.hpp"
1313
#include "utils/lru_cache.hpp"
14+
#include "utils/io/file.hpp"
1415
#include "platform/path.hpp"
1516
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
1617
#include "logging.hpp"
@@ -904,53 +905,112 @@ namespace libdwarf {
904905
}
905906
}
906907

908+
optional<std::string> get_dwo_path(const die_object& cu_die, std::string dwo_name) {
909+
if(is_absolute(dwo_name)) {
910+
return dwo_name;
911+
}
912+
if(auto comp_dir = cu_die.get_string_attribute(DW_AT_comp_dir)) {
913+
return comp_dir.unwrap() + PATH_SEP + dwo_name;
914+
}
915+
// maybe default to dwo_name but for now not doing anything
916+
return nullopt;
917+
}
918+
919+
Result<bool, internal_error> is_clang_pcm_file(cstring_view dwo_path) {
920+
// Clang AST file magic bytes are CPCH:
921+
// https://github.com/llvm/llvm-project/blob/main/clang/lib/Serialization/ASTWriter.cpp#L5483-L5487
922+
auto file_res = file::open(dwo_path);
923+
if(file_res.is_error()) {
924+
return std::move(file_res).unwrap_error();
925+
}
926+
auto& file_obj = file_res.unwrap_value();
927+
auto magic = file_obj.read<std::array<char, 4>>(0);
928+
if(magic.is_error()) {
929+
return std::move(magic).unwrap_error();
930+
}
931+
return magic.unwrap_value() == std::array<char, 4>{'C', 'P', 'C', 'H'};
932+
}
933+
907934
void perform_dwarf_fission_resolution(
908935
const die_object& cu_die,
909-
const optional<std::string>& dwo_name,
936+
cstring_view dwo_path,
910937
const object_frame& object_frame_info,
911938
stacktrace_frame& frame,
912939
std::vector<stacktrace_frame>& inlines
913940
) {
914941
// Split dwarf / debug fission / dwo is handled here
915942
// Location of the split full CU is a combination of DW_AT_dwo_name/DW_AT_GNU_dwo_name and DW_AT_comp_dir
916943
// https://gcc.gnu.org/wiki/DebugFission
917-
if(dwo_name) {
918-
// TODO: DWO ID?
919-
auto comp_dir = cu_die.get_string_attribute(DW_AT_comp_dir);
920-
Dwarf_Half offset_size = 0;
921-
Dwarf_Half dwversion = 0;
922-
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
923-
std::string path;
924-
if(is_absolute(dwo_name.unwrap())) {
925-
path = dwo_name.unwrap();
926-
} else if(comp_dir) {
927-
path = comp_dir.unwrap() + PATH_SEP + dwo_name.unwrap();
928-
} else {
929-
// maybe default to dwo_name but for now not doing anything
930-
return;
944+
// TODO: DWO ID?
945+
Dwarf_Half offset_size = 0;
946+
Dwarf_Half dwversion = 0;
947+
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
948+
// todo: slight inefficiency in this copy-back strategy due to other frame members
949+
frame_with_inlines res;
950+
if(get_cache_mode() == cache_mode::prioritize_memory) {
951+
dwarf_resolver resolver(
952+
dwo_path,
953+
skeleton_info{cu_die.clone(), dwversion, *this}
954+
);
955+
res = resolver.resolve_frame(object_frame_info);
956+
} else {
957+
auto off = cu_die.get_global_offset();
958+
auto it = split_full_cu_resolvers.find(off);
959+
if(it == split_full_cu_resolvers.end()) {
960+
it = split_full_cu_resolvers.emplace(
961+
off,
962+
detail::make_unique<dwarf_resolver>(
963+
dwo_path,
964+
skeleton_info{cu_die.clone(), dwversion, *this}
965+
)
966+
).first;
931967
}
932-
// todo: slight inefficiency in this copy-back strategy due to other frame members
933-
frame_with_inlines res;
934-
if(get_cache_mode() == cache_mode::prioritize_memory) {
935-
dwarf_resolver resolver(
936-
path,
937-
skeleton_info{cu_die.clone(), dwversion, *this}
938-
);
939-
res = resolver.resolve_frame(object_frame_info);
940-
} else {
941-
auto off = cu_die.get_global_offset();
942-
auto it = split_full_cu_resolvers.find(off);
943-
if(it == split_full_cu_resolvers.end()) {
944-
it = split_full_cu_resolvers.emplace(
945-
off,
946-
detail::make_unique<dwarf_resolver>(path, skeleton_info{cu_die.clone(), dwversion, *this})
947-
).first;
948-
}
949-
res = it->second->resolve_frame(object_frame_info);
968+
res = it->second->resolve_frame(object_frame_info);
969+
}
970+
frame = std::move(res.frame);
971+
inlines = std::move(res.inlines);
972+
}
973+
974+
// returns true if the cu looks like it's debug fission, informs resolve_frame_core to not go down the normal
975+
// path
976+
bool try_perform_dwarf_fission_resolution(
977+
const die_object& cu_die,
978+
const object_frame& object_frame_info,
979+
stacktrace_frame& frame,
980+
std::vector<stacktrace_frame>& inlines
981+
) {
982+
// gnu non-standard debug-fission may create non-skeleton CU DIEs and just add dwo attributes
983+
// clang emits dwo names in the split CUs, so guard against going down the dwarf fission path (which
984+
// doesn't infinitely recurse because it's not emitted as an absolute path and there's no comp dir but
985+
// it's good to guard against the infinite recursion anyway)
986+
auto dwo_name = get_dwo_name(cu_die);
987+
if(dwo_name && !skeleton) {
988+
auto dwo_path = get_dwo_path(cu_die, dwo_name.unwrap());
989+
if(!dwo_path) {
990+
return true;
950991
}
951-
frame = std::move(res.frame);
952-
inlines = std::move(res.inlines);
992+
// Clang -gmodules emits a CU with a DW_AT_dwo_name reference to a .pcm file, which is a clang ast file.
993+
// The reference is just for type information, function information is still written to the object
994+
// file's dwarf. Even though the CU seems to be generated with no range information and so lookup_cu
995+
// should never find this CU, let's check if the file looks like a clang ast file and if so not go down
996+
// the fission path.
997+
auto res = is_clang_pcm_file(dwo_path.unwrap());
998+
if(res.is_error()) {
999+
// if res is an error either the file couldn't be found or four bytes from it couldn't be read,
1000+
// libdwarf will probably error too but we'll continue down the fission path anyway
1001+
res.drop_error();
1002+
} else if(res.unwrap_value()) {
1003+
// if this is a clang pcm file ignore it, it doesn't contain dwarf
1004+
return false;
1005+
}
1006+
perform_dwarf_fission_resolution(cu_die, dwo_path.unwrap(), object_frame_info, frame, inlines);
1007+
return true;
1008+
}
1009+
if(cu_die.get_tag() == DW_TAG_skeleton_unit) {
1010+
// It's possible for a DW_TAG_skeleton_unit to exist with no dwo name
1011+
return true;
9531012
}
1013+
return false;
9541014
}
9551015

9561016
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
@@ -967,17 +1027,12 @@ namespace libdwarf {
9671027
optional<cu_info> cu = lookup_cu(pc);
9681028
if(cu) {
9691029
const auto& cu_die = cu.unwrap().cu_die.get();
970-
// gnu non-standard debug-fission may create non-skeleton CU DIEs and just add dwo attributes
971-
// clang emits dwo names in the split CUs, so guard against going down the dwarf fission path (which
972-
// doesn't infinitely recurse because it's not emitted as an absolute path and there's no comp dir but
973-
// it's good to guard against the infinite recursion anyway)
974-
auto dwo_name = get_dwo_name(cu_die);
975-
if(cu_die.get_tag() == DW_TAG_skeleton_unit || (dwo_name && !skeleton)) {
976-
perform_dwarf_fission_resolution(cu_die, dwo_name, object_frame_info, frame, inlines);
977-
} else {
978-
retrieve_line_info(cu_die, pc, frame);
979-
retrieve_symbol(cu_die, pc, cu.unwrap().dwversion, frame, inlines);
1030+
if(try_perform_dwarf_fission_resolution(cu_die, object_frame_info, frame, inlines)) {
1031+
return;
9801032
}
1033+
// normal path: resolve in this CU
1034+
retrieve_line_info(cu_die, pc, frame);
1035+
retrieve_symbol(cu_die, pc, cu.unwrap().dwversion, frame, inlines);
9811036
}
9821037
}
9831038

0 commit comments

Comments
 (0)