Debuginfod is a lightweight web service that indexes ELF/DWARF debugging resources by build-id and serves them over HTTP. This patch enables dyninst to query debuginfod servers for a file's separate debuginfo when it otherwise cannot be found. This patch also adds a cmake option -DENABLE_DEBUGINFOD to control whether dyninst is built with debuginfod support. This requires having the debuginfod client library (libdebuginfod) and header installed. Debuginfod is distributed with elfutils, for more information see https://sourceware.org/elfutils/Debuginfod.html --- cmake/ElfUtils.cmake | 37 ++++++++--- cmake/Modules/FindLibDebuginfod.cmake | 76 +++++++++++++++++++++ cmake/options.cmake | 2 + elf/CMakeLists.txt | 3 + elf/src/Elf_X.C | 95 ++++++++++++++++++++------- 5 files changed, 178 insertions(+), 35 deletions(-) create mode 100644 cmake/Modules/FindLibDebuginfod.cmake --- dyninst-10.2.1/dyninst-10.2.1/cmake/ElfUtils.cmake +++ dyninst-10.2.1/dyninst-10.2.1/cmake/ElfUtils.cmake @@ -28,7 +28,7 @@ # #====================================================================================== -if(LibElf_FOUND AND LibDwarf_FOUND) +if(LibElf_FOUND AND LibDwarf_FOUND AND (LibDebuginfod_FOUND OR NOT ENABLE_DEBUGINFOD)) return() endif() @@ -37,7 +37,12 @@ if(NOT UNIX) endif() # Minimum acceptable version of elfutils -set(_min_version 0.178) +if(ENABLE_DEBUGINFOD) + set(_min_version 0.179) +else() + set(_min_version 0.178) +endif() + set(ElfUtils_MIN_VERSION ${_min_version} CACHE STRING "Minimum acceptable elfutils version") if(${ElfUtils_MIN_VERSION} VERSION_LESS ${_min_version}) @@ -62,7 +67,7 @@ set(ElfUtils_LIBRARYDIR "${ElfUtils_ROOT_DIR}/lib" CACHE PATH "Hint directory that contains the elfutils library files") # libelf/dwarf-specific directory hints -foreach(l LibElf LibDwarf) +foreach(l LibElf LibDwarf LibDebuginfod) foreach(d ROOT_DIR INCLUDEDIR LIBRARYDIR) set(${l}_${d} ${ElfUtils_${d}}) endforeach() @@ -72,18 +77,30 @@ endforeach() find_package(LibElf ${ElfUtils_MIN_VERSION}) -# Don't search for libdw if we didn't find a suitable libelf +# Don't search for libdw or libdebuginfod if we didn't find a suitable libelf if(LibElf_FOUND) find_package(LibDwarf ${ElfUtils_MIN_VERSION}) + if (ENABLE_DEBUGINFOD) + find_package(LibDebuginfod ${ElfUtils_MIN_VERSION}) + endif() endif() # -------------- SOURCE BUILD ------------------------------------------------- -if(LibElf_FOUND AND LibDwarf_FOUND) - set(_eu_root ${ElfUtils_ROOT_DIR}) - set(_eu_inc_dirs ${LibElf_INCLUDE_DIRS} ${LibDwarf_INCLUDE_DIRS}) - set(_eu_lib_dirs ${LibElf_LIBRARY_DIRS} ${LibDwarf_LIBRARY_DIRS}) - set(_eu_libs ${LibElf_LIBRARIES} ${LibDwarf_LIBRARIES}) +if(LibElf_FOUND AND LibDwarf_FOUND AND (NOT ENABLE_DEBUGINFOD OR LibDebuginfod_FOUND)) + if(ENABLE_DEBUGINFOD AND LibDebuginfod_FOUND) + set(_eu_root ${ElfUtils_ROOT_DIR}) + set(_eu_inc_dirs ${LibElf_INCLUDE_DIRS} ${LibDwarf_INCLUDE_DIRS} ${LibDebuginfod_INCLUDE_DIRS}) + set(_eu_lib_dirs ${LibElf_LIBRARY_DIRS} ${LibDwarf_LIBRARY_DIRS} ${LibDebuginfod_LIBRARY_DIRS}) + set(_eu_libs ${LibElf_LIBRARIES} ${LibDwarf_LIBRARIES} ${LibDebuginfod_LIBRARIES}) + else() + set(_eu_root ${ElfUtils_ROOT_DIR}) + set(_eu_inc_dirs ${LibElf_INCLUDE_DIRS} ${LibDwarf_INCLUDE_DIRS}) + set(_eu_lib_dirs ${LibElf_LIBRARY_DIRS} ${LibDwarf_LIBRARY_DIRS}) + set(_eu_libs ${LibElf_LIBRARIES} ${LibDwarf_LIBRARIES}) + endif() add_library(ElfUtils SHARED IMPORTED) +elseif(ENABLE_DEBUGINFOD AND NOT LibDebuginfod_FOUND) + message(FATAL_ERROR "Debuginfod enabled but not found") elseif(NOT (LibElf_FOUND AND LibDwarf_FOUND) AND STERILE_BUILD) message(FATAL_ERROR "Elfutils not found and cannot be downloaded because build is sterile.") else() --- /dev/null +++ dyninst-10.2.1/dyninst-10.2.1/cmake/Modules/FindLibDebuginfod.cmake @@ -0,0 +1,76 @@ +#======================================================================================== +# FindDebuginfod +# ----------- +# +# Find debuginfod library and headers +# +# The module defines the following variables: +# +# This module reads hints about search locations from variables:: +# +# LibDebuginfod_ROOT_DIR - Base directory the of libdebuginfod installation +# LibDebuginfod_INCLUDEDIR - Hint directory that contains the libdebuginfod headers files +# LibDebuginfod_LIBRARYDIR - Hint directory that contains the libdebuginfod library files +# +# and saves search results persistently in CMake cache entries:: +# +# LibDebuginfod_FOUND - True if headers and requested libraries were found +# LibDebuginfod_INCLUDE_DIRS - libdebuginfod include directories +# LibDebuginfod_LIBRARY_DIRS - Link directories for libdebuginfod libraries +# LibDebuginfod_LIBRARIES - libdebuginfod library files +# +# Utilize package config (e.g. /usr/lib64/pkgconfig/libdebuginfod.pc) to fetch +# version information. +# +#======================================================================================== + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_Debuginfod QUIET REQUIRED libdebuginfod>=${ElfUtils_MIN_VERSION}) +set(LibDebuginfod_VERSION "${PC_Debuginfod_VERSION}") + +find_path(LibDebuginfod_INCLUDE_DIRS + NAMES + debuginfod.h + HINTS + ${PC_Debuginfod_INCLUDEDIR} + ${PC_Debuginfod_INCLUDE_DIRS} + ${LibDebuginfod_ROOT_DIR}/include + ${LibDebuginfod_ROOT_DIR} + ${LibDebuginfod_INCLUDEDIR} + PATHS + ${DYNINST_SYSTEM_INCLUDE_PATHS} + PATH_SUFFIXES + ${_path_suffixes} + DOC + "libdebuginfod include directories") + +find_library(LibDebuginfod_LIBRARIES + NAMES + libdebuginfod.so.1 libdebuginfod.so + HINTS + ${PC_Debuginfod_LIBDIR} + ${PC_Debuginfod_LIBRARY_DIRS} + ${LibDebuginfod_ROOT_DIR}/lib + ${LibDebuginfod_ROOT_DIR} + ${LibDebuginfod_LIBRARYDIR} + PATHS + ${DYNINST_SYSTEM_LIBRARY_PATHS} + PATH_SUFFIXES + ${_path_suffixes}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibDebuginfod + FOUND_VAR + LibDebuginfod_FOUND + REQUIRED_VARS + LibDebuginfod_INCLUDE_DIRS + LibDebuginfod_LIBRARIES + VERSION_VAR + LibDebuginfod_VERSION) + +if(LibDebuginfod_FOUND) + set(LibDebuginfod_INCLUDE_DIRS ${LibDebuginfod_INCLUDE_DIRS}) + set(LibDebuginfod_LIBRARIES ${LibDebuginfod_LIBRARIES}) + get_filename_component(_debuginfod_dir ${LibDebuginfod_LIBRARIES} DIRECTORY) + set(LibDebuginfod_LIBRARY_DIRS ${_debuginfod_dir} "${_debuginfod_dir}/elfutils") +endif() --- dyninst-10.2.1/dyninst-10.2.1/cmake/options.cmake +++ dyninst-10.2.1/dyninst-10.2.1/cmake/options.cmake @@ -16,6 +16,8 @@ option(USE_COTIRE "Enable Cotire precompiled headers") option (ENABLE_LTO "Enable Link-Time Optimization" OFF) +option(ENABLE_DEBUGINFOD "Enable debuginfod support" OFF) + # Some global on/off switches if (LIGHTWEIGHT_SYMTAB) add_definitions (-DWITHOUT_SYMTAB_API -DWITH_SYMLITE) --- dyninst-10.2.1/dyninst-10.2.1/elf/CMakeLists.txt +++ dyninst-10.2.1/dyninst-10.2.1/elf/CMakeLists.txt @@ -27,5 +27,8 @@ endif() add_dependencies(dynElf ElfUtils) target_link_private_libraries(dynElf ${ElfUtils_LIBRARIES}) +if (ENABLE_DEBUGINFOD AND LibDebuginfod_FOUND) + add_definitions(-DDEBUGINFOD_LIB) +endif() add_definitions(-DDYNELF_LIB) --- dyninst-10.2.1/dyninst-10.2.1/elf/src/Elf_X.C +++ dyninst-10.2.1/dyninst-10.2.1/elf/src/Elf_X.C @@ -47,6 +47,9 @@ #include #include +#if DEBUGINFOD_LIB +#include +#endif using namespace std; using boost::crc_32_type; @@ -1722,37 +1725,79 @@ bool Elf_X::findDebugFile(std::string origfilename, string &output_name, char* & } } - if (debugFileFromDebugLink.empty()) - return false; + if (!debugFileFromDebugLink.empty()) { + char *mfPathNameCopy = strdup(origfilename.c_str()); + string objectFileDirName = dirname(mfPathNameCopy); - char *mfPathNameCopy = strdup(origfilename.c_str()); - string objectFileDirName = dirname(mfPathNameCopy); + vector fnames = list_of + (objectFileDirName + "/" + debugFileFromDebugLink) + (objectFileDirName + "/.debug/" + debugFileFromDebugLink) + ("/usr/lib/debug/" + objectFileDirName + "/" + debugFileFromDebugLink); - vector fnames = list_of - (objectFileDirName + "/" + debugFileFromDebugLink) - (objectFileDirName + "/.debug/" + debugFileFromDebugLink) - ("/usr/lib/debug/" + objectFileDirName + "/" + debugFileFromDebugLink); + free(mfPathNameCopy); - free(mfPathNameCopy); + for(unsigned i = 0; i < fnames.size(); i++) { + bool result = loadDebugFileFromDisk(fnames[i], output_buffer, output_buffer_size); + if (!result) + continue; - for(unsigned i = 0; i < fnames.size(); i++) { - bool result = loadDebugFileFromDisk(fnames[i], output_buffer, output_buffer_size); - if (!result) - continue; - - boost::crc_32_type crcComputer; - crcComputer.process_bytes(output_buffer, output_buffer_size); - if(crcComputer.checksum() != debugFileCrc) { - munmap(output_buffer, output_buffer_size); - continue; - } + boost::crc_32_type crcComputer; + crcComputer.process_bytes(output_buffer, output_buffer_size); + if(crcComputer.checksum() != debugFileCrc) { + munmap(output_buffer, output_buffer_size); + continue; + } + + output_name = fnames[i]; + cached_debug_buffer = output_buffer; + cached_debug_size = output_buffer_size; + cached_debug_name = output_name; + return true; + } + } - output_name = fnames[i]; - cached_debug_buffer = output_buffer; - cached_debug_size = output_buffer_size; - cached_debug_name = output_name; - return true; +#ifdef DEBUGINFOD_LIB + if (!debugFileFromBuildID.empty()) { + // Given /usr/lib/debug/.buildid/XX/YYYYYY.debug, isolate XXYYYYYY. + size_t idx1 = debugFileFromBuildID.find_last_of("/"); + size_t idx2 = debugFileFromBuildID.find_last_of("."); + + if (idx1 == string::npos || idx2 == string::npos + || idx1 < 2 || idx1 > idx2) + return false; + + idx1 -= 2; + string buildid(debugFileFromBuildID.substr(idx1, idx2 - idx1)); + buildid.erase(2, 1); + + debuginfod_client *client = debuginfod_begin(); + if (client == NULL) + return false; + + char *filename; + int fd = debuginfod_find_debuginfo(client, + (const unsigned char *)buildid.c_str(), + 0, &filename); + debuginfod_end(client); + + if (fd >= 0) { + string fname = string(filename); + free(filename); + close(fd); + + bool result = loadDebugFileFromDisk(fname, + output_buffer, + output_buffer_size); + if (result) { + output_name = fname; + cached_debug_buffer = output_buffer; + cached_debug_size = output_buffer_size; + cached_debug_name = output_name; + return true; + } + } } +#endif return false; }