Add very basic dwarfdump tool

This commit is contained in:
Jeremy Rifkin 2025-02-02 19:28:01 -06:00
parent 8e7b4a953f
commit b724d1328c
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
6 changed files with 244 additions and 5 deletions

View File

@ -350,11 +350,14 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
endif() endif()
endif() endif()
if(CPPTRACE_CONAN) if(CPPTRACE_CONAN)
set(dwarf_lib libdwarf::libdwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf) target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
elseif(CPPTRACE_VCPKG) elseif(CPPTRACE_VCPKG)
set(dwarf_lib libdwarf::dwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF) elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
if(DEFINED LIBDWARF_LIBRARIES) if(DEFINED LIBDWARF_LIBRARIES)
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES}) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
else() else()
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static, # 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() else()
message(FATAL_ERROR "Couldn't find libdwarf target name to link against") message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
endif() endif()
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES}) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
endif() endif()
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if # 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") message(FATAL_ERROR "Couldn't find libdwarf.h")
endif() endif()
else() else()
set(dwarf_lib libdwarf::dwarf-static)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
endif() endif()
if(UNIX) if(UNIX)
@ -509,7 +514,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/InstallRules.cmake) include(cmake/InstallRules.cmake)
endif() endif()
# ===================================================== Demo/test ====================================================== # ================================================== Demo/test/tools ===================================================
if(CPPTRACE_BUILD_TESTING) if(CPPTRACE_BUILD_TESTING)
if(PROJECT_IS_TOP_LEVEL) if(PROJECT_IS_TOP_LEVEL)
@ -521,3 +526,7 @@ endif()
if(CPPTRACE_BUILD_BENCHMARKING) if(CPPTRACE_BUILD_BENCHMARKING)
add_subdirectory(benchmarking) add_subdirectory(benchmarking)
endif() endif()
if(CPPTRACE_BUILD_TOOLS)
add_subdirectory(tools)
endif()

View File

@ -9,12 +9,12 @@ help: # with thanks to Ben Rady
build: debug ## build in debug mode build: debug ## build in debug mode
build/configured-debug: 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 rm -f build/configured-release
touch build/configured-debug touch build/configured-debug
build/configured-release: 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 rm -f build/configured-debug
touch build/configured-release touch build/configured-release
@ -34,12 +34,12 @@ release: configure-release ## build in release mode (with debug info)
.PHONY: debug-msvc .PHONY: debug-msvc
debug-msvc: ## build in debug mode 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 cmake --build build --config Debug
.PHONY: release-msvc .PHONY: release-msvc
release-msvc: ## build in release mode (with debug info) 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 cmake --build build --config RelWithDebInfo
.PHONY: clean .PHONY: clean

View File

@ -151,6 +151,7 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
if(PROJECT_IS_TOP_LEVEL) if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TESTING "" OFF) option(CPPTRACE_BUILD_TESTING "" OFF)
option(CPPTRACE_BUILD_TOOLS "" OFF)
option(CPPTRACE_BUILD_BENCHMARK "" OFF) option(CPPTRACE_BUILD_BENCHMARK "" OFF)
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF) option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF) option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
@ -158,6 +159,7 @@ if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF) option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
mark_as_advanced( mark_as_advanced(
CPPTRACE_BUILD_TESTING CPPTRACE_BUILD_TESTING
CPPTRACE_BUILD_TOOLS
CPPTRACE_BUILD_BENCHMARK CPPTRACE_BUILD_BENCHMARK
CPPTRACE_BUILD_NO_SYMBOLS CPPTRACE_BUILD_NO_SYMBOLS
CPPTRACE_BUILD_TESTING_SPLIT_DWARF CPPTRACE_BUILD_TESTING_SPLIT_DWARF

46
tools/CMakeLists.txt Normal file
View File

@ -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)

View File

@ -0,0 +1,3 @@
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
binary(dwarfdump LIBS ${dwarf_lib})
endif()

179
tools/dwarfdump/main.cpp Normal file
View File

@ -0,0 +1,179 @@
#include <lyra/lyra.hpp>
#include <fmt/format.h>
#include <fmt/std.h>
#include <fmt/ostream.h>
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include <filesystem>
#include "symbols/dwarf/dwarf.hpp"
using namespace std::literals;
using namespace cpptrace::detail::libdwarf;
template<> struct fmt::formatter<lyra::cli> : 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<int(Args...)>()(std::forward<Args2>(std::declval<Args2>())..., nullptr)
),
void
>::value,
int
>::type = 0
>
int wrap(int (*f)(Args...), Args2&&... args) const {
Dwarf_Error error = nullptr;
int ret = f(std::forward<Args2>(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<bool(const die_object&)>& 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();
}