From b724d1328ced0d71315c28f6ec6e11d0652062bc Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:28:01 -0600 Subject: [PATCH] Add very basic dwarfdump tool --- CMakeLists.txt | 11 +- Makefile | 8 +- cmake/OptionVariables.cmake | 2 + tools/CMakeLists.txt | 46 +++++++++ tools/dwarfdump/CMakeLists.txt | 3 + tools/dwarfdump/main.cpp | 179 +++++++++++++++++++++++++++++++++ 6 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/dwarfdump/CMakeLists.txt create mode 100644 tools/dwarfdump/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 02b8a91..a7d549b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,11 +350,14 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) endif() endif() if(CPPTRACE_CONAN) + set(dwarf_lib libdwarf::libdwarf) target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf) elseif(CPPTRACE_VCPKG) + set(dwarf_lib libdwarf::dwarf) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf) elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF) if(DEFINED LIBDWARF_LIBRARIES) + set(dwarf_lib ${LIBDWARF_LIBRARIES}) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES}) else() # if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static, @@ -372,6 +375,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) else() message(FATAL_ERROR "Couldn't find libdwarf target name to link against") endif() + set(dwarf_lib ${LIBDWARF_LIBRARIES}) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES}) endif() # There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if @@ -394,6 +398,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) message(FATAL_ERROR "Couldn't find libdwarf.h") endif() else() + set(dwarf_lib libdwarf::dwarf-static) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) endif() if(UNIX) @@ -509,7 +514,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES) include(cmake/InstallRules.cmake) endif() -# ===================================================== Demo/test ====================================================== +# ================================================== Demo/test/tools =================================================== if(CPPTRACE_BUILD_TESTING) if(PROJECT_IS_TOP_LEVEL) @@ -521,3 +526,7 @@ endif() if(CPPTRACE_BUILD_BENCHMARKING) add_subdirectory(benchmarking) endif() + +if(CPPTRACE_BUILD_TOOLS) + add_subdirectory(tools) +endif() diff --git a/Makefile b/Makefile index cc82b60..aa32f33 100644 --- a/Makefile +++ b/Makefile @@ -9,12 +9,12 @@ help: # with thanks to Ben Rady build: debug ## build in debug mode build/configured-debug: - cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On + cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On rm -f build/configured-release touch build/configured-debug build/configured-release: - cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On + cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On rm -f build/configured-debug touch build/configured-release @@ -34,12 +34,12 @@ release: configure-release ## build in release mode (with debug info) .PHONY: debug-msvc debug-msvc: ## build in debug mode - cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On + cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On cmake --build build --config Debug .PHONY: release-msvc release-msvc: ## build in release mode (with debug info) - cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On + cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On cmake --build build --config RelWithDebInfo .PHONY: clean diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 48aec5b..c46d6e6 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -151,6 +151,7 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF) if(PROJECT_IS_TOP_LEVEL) option(CPPTRACE_BUILD_TESTING "" OFF) + option(CPPTRACE_BUILD_TOOLS "" OFF) option(CPPTRACE_BUILD_BENCHMARK "" OFF) option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF) option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF) @@ -158,6 +159,7 @@ if(PROJECT_IS_TOP_LEVEL) option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF) mark_as_advanced( CPPTRACE_BUILD_TESTING + CPPTRACE_BUILD_TOOLS CPPTRACE_BUILD_BENCHMARK CPPTRACE_BUILD_NO_SYMBOLS CPPTRACE_BUILD_TESTING_SPLIT_DWARF diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..8ccb62c --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,46 @@ +include(FetchContent) + +FetchContent_Declare( + lyra + GIT_SHALLOW ON + GIT_REPOSITORY "https://github.com/bfgroup/Lyra.git" + GIT_TAG "ee3c076fa6b9d64c9d249a21f5b9b5a8dae92cd8" +) +FetchContent_MakeAvailable(lyra) + +FetchContent_Declare( + fmt + GIT_SHALLOW TRUE + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281 # v10.2.1 +) +FetchContent_MakeAvailable(fmt) + +set( + COMMON_LIBS + cpptrace::cpptrace + bfg::lyra + fmt::fmt +) + +function(binary TARGET) + cmake_parse_arguments(BINARY "" "" "SOURCES;LIBS;FLAGS;DEFS" ${ARGN}) + add_executable(${TARGET} main.cpp) + if(BINARY_SOURCES) + add_library(${TARGET}_OBJ OBJECT ${BINARY_SOURCES}) + target_link_libraries(${TARGET}_OBJ PUBLIC ${COMMON_LIBS}) + endif() + target_link_libraries(${TARGET} PUBLIC ${COMMON_LIBS}) + target_link_libraries(${TARGET} PUBLIC ${BINARY_LIBS}) + target_compile_definitions(${TARGET} PUBLIC ${BINARY_DEFS}) + target_compile_options(${TARGET} PUBLIC ${BINARY_FLAGS} ${debug} ${warning_options}) + target_include_directories(${TARGET} PUBLIC "${PROJECT_SOURCE_DIR}/src") + target_compile_features(${TARGET} PRIVATE cxx_std_20) + set_target_properties( + ${TARGET} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + ) +endfunction() + +add_subdirectory(dwarfdump) diff --git a/tools/dwarfdump/CMakeLists.txt b/tools/dwarfdump/CMakeLists.txt new file mode 100644 index 0000000..e517073 --- /dev/null +++ b/tools/dwarfdump/CMakeLists.txt @@ -0,0 +1,3 @@ +if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) + binary(dwarfdump LIBS ${dwarf_lib}) +endif() diff --git a/tools/dwarfdump/main.cpp b/tools/dwarfdump/main.cpp new file mode 100644 index 0000000..21d3eca --- /dev/null +++ b/tools/dwarfdump/main.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "symbols/dwarf/dwarf.hpp" + +using namespace std::literals; +using namespace cpptrace::detail::libdwarf; + +template<> struct fmt::formatter : ostream_formatter {}; + +class DwarfDumper { + std::string object_path; + Dwarf_Debug dbg = 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 + // TODO: Duplicate + 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; + } + + // TODO: Duplicate + // walk all CU's in a dbg, callback is called on each die and should return true to + // continue traversal + void walk_compilation_units(const std::function& fn) { + // 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 = wrap( + dwarf_next_cu_header_d, + dbg, + true, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &next_cu_header, + &header_cu_type + ); + if(ret == DW_DLV_NO_ENTRY) { + fmt::println("End walk_dbg"); + return; + } + if(ret != DW_DLV_OK) { + PANIC("Unexpected return code from dwarf_next_cu_header_d"); + return; + } + // 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) { + break; + } + if(!walk_die_list(cu_die, fn)) { + break; + } + } + fmt::println("End walk_compilation_units"); + } + + void dump_die_tree(const die_object& die, int depth) { + walk_die_list( + die, + [this, depth] (const die_object& die) { + fmt::println("{:016x}{: <{}} {}", die.get_global_offset(), "", depth * 2, die.get_tag_name()); + fmt::println("{: <16}{: <{}} name: {}", "", "", depth * 2, die.get_name()); + fmt::println(""); + auto child = die.get_child(); + if(child) { + dump_die_tree(child, depth + 1); + } + return true; + } + ); + } + + void dump_cu(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); + fmt::println("{:016x} Compile Unit: version = {}, unit type = {}", cu_die.get_global_offset(), dwversion, cu_die.get_tag_name()); + dump_die_tree(cu_die, 0); + } + +public: + DwarfDumper() = default; + ~DwarfDumper() { + dwarf_finish(dbg); + } + + void dump(std::filesystem::path path) { + object_path = path; + auto ret = wrap( + dwarf_init_path_a, + object_path.c_str(), + nullptr, + 0, + DW_GROUPNUMBER_ANY, + 0, + nullptr, + nullptr, + &dbg + ); + if(ret == DW_DLV_OK) { + // ok + } else if(ret == DW_DLV_NO_ENTRY) { + // fail, no debug info + fmt::println(stderr, "No debug info"); + std::exit(1); + } else { + fmt::println(stderr, "Error: Unknown return code from dwarf_init_path {}", ret); + std::exit(1); + } + walk_compilation_units([this] (const die_object& cu_die) { + dump_cu(cu_die); + return true; + }); + } +}; + +int main(int argc, char** argv) CPPTRACE_TRY { + bool show_help = false; + std::filesystem::path path; + auto cli = lyra::cli() + | lyra::help(show_help) + | lyra::arg(path, "binary path")("binary to dwarfdump").required(); + if(auto result = cli.parse({ argc, argv }); !result) { + fmt::println(stderr, "Error in command line: {}", result.message()); + fmt::println("{}", cli); + return 1; + } + if(show_help) { + fmt::println("{}", cli); + return 0; + } + if(!std::filesystem::exists(path)) { + fmt::println(stderr, "Error: Path doesn't exist {}", path); + return 1; + } + if(!std::filesystem::is_regular_file(path)) { + fmt::println(stderr, "Error: Path isn't a regular file {}", path); + return 1; + } + DwarfDumper{}.dump(path); +} CPPTRACE_CATCH(const std::exception& e) { + fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what()); + cpptrace::from_current_exception().print(); +}