diff --git a/CMakePresets.json b/CMakePresets.json index a20dbaa..e7c59e9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -48,6 +48,20 @@ "POORPROF_DEPS": "vcpkg" } }, + { + "name": "debug-vcpkg-local", + "displayName": "Debug vcpkg", + "description": "Debug build using deps from local instance of vcpkg", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/debug-vcpkg-local", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "/home/sergey/dev/vcpkg/scripts/buildsystems/vcpkg.cmake", + "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "FORCE_COLORED_OUTPUT": true, + "POORPROF_DEPS": "vcpkg" + } + }, { "name": "release-system", "displayName": "Release system", @@ -105,6 +119,12 @@ "configurePreset": "debug-system", "displayName": "Debug system", "description": "Debug build using deps from system" + }, + { + "name": "debug-vcpkg-local", + "configurePreset": "debug-vcpkg-local", + "displayName": "Debug vcpkg with local installation", + "description": "Debug build using deps from vcpkg" } ] } diff --git a/cmake/dependencies/system.cmake b/cmake/dependencies/system.cmake index b7e4805..2ba2526 100644 --- a/cmake/dependencies/system.cmake +++ b/cmake/dependencies/system.cmake @@ -6,6 +6,9 @@ list(APPEND POORPROF_PRIVATE_LIBRARIES fmt::fmt) find_package(ElfUtils REQUIRED) list(APPEND POORPROF_PRIVATE_LIBRARIES ${LIBDWARF_LIBRARIES} ${LIBDW_LIBRARIES} ${LIBELF_LIBRARIES}) +find_package(LibDebugInfoD REQUIRED) +list(APPEND POORPROF_PRIVATE_LIBRARIES ${LIBDEBUGINFOD_LIBRARIES}) + find_package(absl REQUIRED) list(APPEND POORPROF_PRIVATE_LIBRARIES absl::flat_hash_map) @@ -14,7 +17,3 @@ list(APPEND POORPROF_PRIVATE_LIBRARIES spdlog::spdlog) find_package(re2 REQUIRED) list(APPEND POORPROF_PRIVATE_LIBRARIES re2::re2) - -# set(Boost_USE_STATIC_LIBS ON) -# find_package(Boost REQUIRED COMPONENTS iostreams) -# list(APPEND POORPROF_PRIVATE_LIBRARIES Boost::iostreams) diff --git a/cmake/dependencies/vcpkg.cmake b/cmake/dependencies/vcpkg.cmake index b7e4805..2ba2526 100644 --- a/cmake/dependencies/vcpkg.cmake +++ b/cmake/dependencies/vcpkg.cmake @@ -6,6 +6,9 @@ list(APPEND POORPROF_PRIVATE_LIBRARIES fmt::fmt) find_package(ElfUtils REQUIRED) list(APPEND POORPROF_PRIVATE_LIBRARIES ${LIBDWARF_LIBRARIES} ${LIBDW_LIBRARIES} ${LIBELF_LIBRARIES}) +find_package(LibDebugInfoD REQUIRED) +list(APPEND POORPROF_PRIVATE_LIBRARIES ${LIBDEBUGINFOD_LIBRARIES}) + find_package(absl REQUIRED) list(APPEND POORPROF_PRIVATE_LIBRARIES absl::flat_hash_map) @@ -14,7 +17,3 @@ list(APPEND POORPROF_PRIVATE_LIBRARIES spdlog::spdlog) find_package(re2 REQUIRED) list(APPEND POORPROF_PRIVATE_LIBRARIES re2::re2) - -# set(Boost_USE_STATIC_LIBS ON) -# find_package(Boost REQUIRED COMPONENTS iostreams) -# list(APPEND POORPROF_PRIVATE_LIBRARIES Boost::iostreams) diff --git a/cmake/modules/FindLibDebugInfoD.cmake b/cmake/modules/FindLibDebugInfoD.cmake new file mode 100644 index 0000000..cfe6f2a --- /dev/null +++ b/cmake/modules/FindLibDebugInfoD.cmake @@ -0,0 +1,60 @@ +# - Try to find libdebuginfod +# Once done this will define +# +# LIBDEBUGINFOD_FOUND - system has libdebuginfod +# LIBDEBUGINFOD_INCLUDE_DIRS - the libdebuginfod include directory +# LIBDEBUGINFOD_LIBRARIES - Link these to use libdebuginfod +# LIBDEBUGINFOD_DEFINITIONS - Compiler switches required for using libdebuginfod +# + +find_package(PkgConfig QUIET) + +if(PKG_CONFIG_FOUND) + set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) + pkg_check_modules(PC_LIBDEBUGINFOD QUIET libdebuginfod) +endif() + +find_path (LIBDEBUGINFOD_INCLUDE_DIR + NAMES + debuginfod.h + HINTS + ${PC_LIBDEBUGINFOD_INCLUDE_DIRS} + PATHS + /usr/include + /usr/include/elfutils + /usr/local/include + /usr/local/include/elfutils + /opt/local/include + /opt/local/include/elfutils + /sw/include + /sw/include/elfutils + ENV CPATH) + +find_library (LIBDEBUGINFOD_LIBRARY + NAMES + debuginfod + HINTS + ${PC_LIBDEBUGINFOD_LIBRARY_DIRS} + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ENV LIBRARY_PATH + ENV LD_LIBRARY_PATH) + +# Transitive deps +find_package(CURL) + +include (FindPackageHandleStandardArgs) + +find_package_handle_standard_args(LibDebugInfoD DEFAULT_MSG + LIBDEBUGINFOD_LIBRARY + LIBDEBUGINFOD_INCLUDE_DIR + CURL_LIBRARIES) + + +mark_as_advanced(LIBDEBUGINFOD_LIBRARY LIBDEBUGINFOD_INCLUDE_DIR) + +set(LIBDEBUGINFOD_LIBRARIES ${LIBDEBUGINFOD_LIBRARY} ${CURL_LIBRARIES}) +set(LIBDEBUGINFOD_INCLUDE_DIRS ${LIBDEBUGINFOD_INCLUDE_DIR} ) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 0582570..aa49967 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -1,7 +1,9 @@ +#include "dw/debuginfod.h" #include "dw/gdb_index.h" #include "util/ctrlc.h" #include "util/defer.h" #include "util/demangle.h" +#include "util/error.h" #include "util/iterator.h" #include "util/literals.h" @@ -76,11 +78,11 @@ class DwflError : public std::runtime_error { }; #define CHECK_DWFL(...) \ - if (int err = (__VA_ARGS__); err != 0) { \ + if (auto err = (__VA_ARGS__); err != 0) { \ if (err < 0) { \ throw DwflError{}; \ } \ - throw std::system_error{err, std::system_category()}; \ + throw std::system_error{static_cast(err), std::system_category()}; \ } class Unwinder { @@ -125,7 +127,45 @@ class Unwinder { using SymbolList = absl::InlinedVector; -public: + static int FindDebugInfo(Dwfl_Module* mod, void** userdata, + const char* modname, Dwarf_Addr base, + const char* file_name, const char* debuglink_name, + GElf_Word debuglink_crc, char** debuginfo_filename) + { + spdlog::info("FindDebugInfo for module {} (file_name: {})", modname, file_name); + + // Try to use standard search facility + int fd = dwfl_standard_find_debuginfo(mod, userdata, modname, base, file_name, debuglink_name, debuglink_crc, debuginfo_filename); + if (fd >= 0) { + return fd; + } + + // Try to find debuginfo using debuginfod + if (mod == nullptr) { + return -1; + } + + GElf_Addr bias; + const unsigned char* buildIdBytes; + int len = dwfl_module_build_id(mod, &buildIdBytes, &bias); + if (len < 0) { + throw DwflError{}; + } + std::string buildId(reinterpret_cast(buildIdBytes), len); + + if (!userdata) { + spdlog::error("Module {} is not registered in the unwinder", modname); + return -1; + } + Unwinder* that = static_cast(*userdata); + std::optional res = that->RemoteDebugInfo_.FindDebugInfo(buildId); + if (res) { + return *res; + } + + return -1; + } + public: Unwinder(Options opts) : Options_{std::move(opts)} @@ -135,7 +175,7 @@ class Unwinder { , DebugInfoPath_{DebugInfo_ ? DebugInfo_->data() : nullptr} , Callbacks_{ .find_elf = dwfl_linux_proc_find_elf, - .find_debuginfo = dwfl_standard_find_debuginfo, + .find_debuginfo = &FindDebugInfo, .debuginfo_path = &DebugInfoPath_, } { @@ -144,6 +184,7 @@ class Unwinder { } InitializeDwfl(); FillDwflReport(); + RegisterDwflModules(); AttachToProcess(); ValidateAttachedPid(); } @@ -667,9 +708,8 @@ class Unwinder { dwfl_report_begin(Dwfl_); if (CustomMaps_) { FILE* maps = fopen(CustomMaps_->data(), "r"); - DEFER { - fclose(maps); - }; + DEFER { fclose(maps); }; + int code = dwfl_linux_proc_maps_report(Dwfl_, maps); CHECK_DWFL(code); } else { @@ -679,6 +719,20 @@ class Unwinder { CHECK_DWFL(dwfl_report_end(Dwfl_, nullptr, nullptr)); } + // Fill userdata in each dwfl module. + void RegisterDwflModules() { + ptrdiff_t offset = 0; + do { + offset = dwfl_getmodules(Dwfl_, +[](Dwfl_Module* mod, void**, const char* , Dwarf_Addr, void* arg) -> int { + void** userdata; + dwfl_module_info(mod, &userdata, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + *userdata = arg; + return DWARF_CB_OK; + }, this, offset); + } while (offset > 0); + CHECK_DWFL(offset); + } + void AttachToProcess() { CHECK_DWFL(dwfl_linux_proc_attach(Dwfl_, Pid_, /*assume_ptrace_stopped=*/false)); } @@ -704,6 +758,8 @@ class Unwinder { absl::flat_hash_map ThreadNameCache_; absl::flat_hash_map Traces_; absl::flat_hash_map> Modules_; + + RemoteDebugInfo RemoteDebugInfo_; }; #undef CHECK_DWFL diff --git a/src/dw/CMakeLists.txt b/src/dw/CMakeLists.txt index 9f1be7e..7fb038e 100644 --- a/src/dw/CMakeLists.txt +++ b/src/dw/CMakeLists.txt @@ -1,10 +1,12 @@ set( SOURCES + debuginfod.cpp dw.cpp ) set( HEADERS + debuginfod.h dw.h ) @@ -16,7 +18,7 @@ set_target_properties(poorprof_dw PROPERTIES ) target_link_libraries(poorprof_dw - PUBLIC ${LIBDWARF_LIBRARIES} + PUBLIC ${LIBDWARF_LIBRARIES} ${LIBDEBUGINFOD_LIBRARIES} PUBLIC ${CMAKE_DL_LIBS} ) diff --git a/src/dw/debuginfod.cpp b/src/dw/debuginfod.cpp new file mode 100644 index 0000000..caccc80 --- /dev/null +++ b/src/dw/debuginfod.cpp @@ -0,0 +1,59 @@ +#include "debuginfod.h" + +#include "util/assert.h" +#include "util/defer.h" + +#include + +#include + +#ifdef __linux__ +#include +#endif + + +namespace poorprof::dw { + +RemoteDebugInfo::RemoteDebugInfo() +{ + Client_ = debuginfod_begin(); + if (Client_) { + debuginfod_set_progressfn(Client_, +[](debuginfod_client*, long a, long b) { + spdlog::info("Loading {}/{}", a, b); + return 0; + }); + spdlog::info("Successfully initialized debuginfod client"); + } else { + spdlog::info("Failed to initialize debuginfod client"); + } +} + +RemoteDebugInfo::~RemoteDebugInfo() { + if (Client_) { + debuginfod_end(std::exchange(Client_, nullptr)); + } +} + +std::optional RemoteDebugInfo::FindDebugInfo(std::string buildId) { + if (!Client_) { + return std::nullopt; + } + + char* path = nullptr; + DEFER { + ::free(path); + }; + + spdlog::info("Loading remote debug info"); + const unsigned char* ptr = reinterpret_cast(buildId.data()); + int fd = debuginfod_find_debuginfo(Client_, ptr, buildId.size(), &path); + if (fd <= 0) { + spdlog::warn("Failed to find remote debug info: {}", strerror(-fd)); + return std::nullopt; + } + + spdlog::info("Successfully fetched remote debug info"); + return fd; +} + +} // namespace poorprof::dw diff --git a/src/dw/debuginfod.h b/src/dw/debuginfod.h new file mode 100644 index 0000000..85d786c --- /dev/null +++ b/src/dw/debuginfod.h @@ -0,0 +1,30 @@ +#pragma once + +#include "util/fs.h" +#include "util/noncopyable.h" + +#include + +#include + + +struct debuginfod_client; + +namespace poorprof::dw { + +using FileDescriptor = int; + +class RemoteDebugInfo : util::NonCopyable { + struct DebugInfoLib; + +public: + RemoteDebugInfo(); + ~RemoteDebugInfo(); + + std::optional FindDebugInfo(std::string buildId); + +private: + debuginfod_client* Client_ = nullptr; +}; + +} // namespace poorprof::dw diff --git a/src/util/fs.h b/src/util/fs.h new file mode 100644 index 0000000..ec6b82c --- /dev/null +++ b/src/util/fs.h @@ -0,0 +1,10 @@ +#pragma once + +#include + + +namespace poorprof { + +namespace fs = std::filesystem; + +} // namespace poorprof diff --git a/src/util/noncopyable.h b/src/util/noncopyable.h new file mode 100644 index 0000000..001fefd --- /dev/null +++ b/src/util/noncopyable.h @@ -0,0 +1,17 @@ +#pragma once + +namespace util { + +struct NonCopyable { + NonCopyable() = default; + + // Non copyable + NonCopyable(const NonCopyable&) = delete; + NonCopyable& operator=(NonCopyable&) = delete; + + // But moveable + NonCopyable(NonCopyable&&) noexcept = default; + NonCopyable& operator=(NonCopyable&&) = default; +}; + +} // namespace util diff --git a/vcpkg.json b/vcpkg.json index d91c2d0..7c4fcef 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,7 +7,10 @@ "name": "abseil", "features": ["cxx17"] }, - "elfutils", + { + "name": "elfutils", + "features": ["libdebuginfod"] + }, "fmt", "spdlog", "re2"