#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF #include "symbols/dwarf/resolver.hpp" #include #include "symbols/symbols.hpp" #include "utils/common.hpp" #include "utils/error.hpp" #include "binary/object.hpp" #include "binary/mach-o.hpp" #include "utils/utils.hpp" #include #include #include #include #include #include #include namespace cpptrace { namespace detail { namespace libdwarf { #if IS_APPLE struct target_object { std::string object_path; bool path_ok = true; optional> symbols; std::unique_ptr resolver; target_object(std::string object_path) : object_path(std::move(object_path)) {} std::unique_ptr& get_resolver() { if(!resolver) { // this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if // exceptions are thrown, e.g. if the path doesn't exist resolver = std::unique_ptr(new null_resolver); resolver = make_dwarf_resolver(object_path); } return resolver; } std::unordered_map& get_symbols() { if(!symbols) { // this is an attempt to not repeatedly try to reprocess mach-o files if exceptions are thrown, e.g. if // the path doesn't exist std::unordered_map symbols; this->symbols = symbols; auto obj = mach_o::open_mach_o(object_path); if(!obj) { return this->symbols.unwrap(); } auto symbol_table = obj.unwrap_value().symbol_table(); if(!symbol_table) { return this->symbols.unwrap(); } for(const auto& symbol : symbol_table.unwrap_value()) { symbols[symbol.name] = symbol.address; } this->symbols = std::move(symbols); } return symbols.unwrap(); } CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING frame_with_inlines resolve_frame( const object_frame& frame_info, const std::string& symbol_name, std::size_t offset ) { const auto& symbol_table = get_symbols(); auto it = symbol_table.find(symbol_name); if(it != symbol_table.end()) { auto frame = frame_info; // substitute a translated address object for the target file in frame.object_address = it->second + offset; auto res = get_resolver()->resolve_frame(frame); // replace the translated address with the object address in the binary res.frame.object_address = frame_info.object_address; return res; } else { return { { frame_info.raw_address, frame_info.object_address, nullable::null(), nullable::null(), frame_info.object_path, symbol_name, false }, {} }; } } }; struct debug_map_symbol_info { uint64_t source_address; uint64_t size; std::string name; nullable target_address; // T(-1) is used as a sentinel std::size_t object_index; }; class debug_map_resolver : public symbol_resolver { std::vector target_objects; std::vector symbols; public: debug_map_resolver(const std::string& source_object_path) { // load mach-o // TODO: Cache somehow? auto obj = mach_o::open_mach_o(source_object_path); if(!obj) { return; } mach_o& source_mach = obj.unwrap_value(); auto source_debug_map = source_mach.get_debug_map(); if(!source_debug_map) { return; } // get symbol entries from debug map, as well as the various object files used to make this binary for(auto& entry : source_debug_map.unwrap_value()) { // object it came from target_objects.push_back({entry.first}); // push the symbols auto& map_entry_symbols = entry.second; symbols.reserve(symbols.size() + map_entry_symbols.size()); for(auto& symbol : map_entry_symbols) { symbols.push_back({ symbol.source_address, symbol.size, std::move(symbol.name), nullable::null(), target_objects.size() - 1 }); } } // sort for binary lookup later std::sort( symbols.begin(), symbols.end(), [] ( const debug_map_symbol_info& a, const debug_map_symbol_info& b ) { return a.source_address < b.source_address; } ); } CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING frame_with_inlines resolve_frame(const object_frame& frame_info) override { // resolve object frame: // find the symbol in this executable corresponding to the object address // resolve the symbol in the object it came from, based on the symbol name auto closest_symbol_it = first_less_than_or_equal( symbols.begin(), symbols.end(), frame_info.object_address, [] ( uint64_t pc, const debug_map_symbol_info& symbol ) { return pc < symbol.source_address; } ); if(closest_symbol_it != symbols.end()) { if(frame_info.object_address <= closest_symbol_it->source_address + closest_symbol_it->size) { return target_objects[closest_symbol_it->object_index].resolve_frame( { frame_info.raw_address, // the resolver doesn't care about the object address here, only the offset from the start // of the symbol and it'll lookup the symbol's base-address frame_info.object_address, frame_info.object_path }, closest_symbol_it->name, frame_info.object_address - closest_symbol_it->source_address ); } } // There was either no closest symbol or the closest symbol didn't end up containing the address we're // looking for, so just return a blank frame return { { frame_info.raw_address, frame_info.object_address, nullable::null(), nullable::null(), frame_info.object_path, "", false }, {} }; }; }; std::unique_ptr make_debug_map_resolver(const std::string& object_path) { return std::unique_ptr(new debug_map_resolver(object_path)); } #endif } } } #endif