From 2be3543c7bfdf0ad8fdbed83abd36f7d3b9bf44f Mon Sep 17 00:00:00 2001 From: "Jonathan R. Madsen" Date: Thu, 11 Jul 2024 20:22:26 -0500 Subject: [PATCH] Parse ELF format for `rocprofiler_configure` symbol (#970) * Parse ELF format to search for rocprofiler_configure * Use ELF parsing in registration --- .gitmodules | 3 + cmake/rocprofiler_config_interfaces.cmake | 10 + cmake/rocprofiler_interfaces.cmake | 2 + external/CMakeLists.txt | 17 ++ external/elfio | 1 + source/lib/common/CMakeLists.txt | 12 +- source/lib/common/elf_utils.cpp | 213 ++++++++++++++++++++ source/lib/common/elf_utils.hpp | 121 +++++++++++ source/lib/rocprofiler-sdk/registration.cpp | 31 ++- 9 files changed, 403 insertions(+), 7 deletions(-) create mode 160000 external/elfio create mode 100644 source/lib/common/elf_utils.cpp create mode 100644 source/lib/common/elf_utils.hpp diff --git a/.gitmodules b/.gitmodules index 4985459020..391aea4832 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "external/perfetto"] path = external/perfetto url = https://android.googlesource.com/platform/external/perfetto +[submodule "external/elfio"] + path = external/elfio + url = https://github.com/serge1/ELFIO.git diff --git a/cmake/rocprofiler_config_interfaces.cmake b/cmake/rocprofiler_config_interfaces.cmake index c4ae0d6cf0..e25f47302b 100644 --- a/cmake/rocprofiler_config_interfaces.cmake +++ b/cmake/rocprofiler_config_interfaces.cmake @@ -266,3 +266,13 @@ find_library( target_include_directories(rocprofiler-drm SYSTEM INTERFACE ${drm_INCLUDE_DIR} ${xf86drm_INCLUDE_DIR}) target_link_libraries(rocprofiler-drm INTERFACE ${drm_LIBRARY} ${drm_amdgpu_LIBRARY}) + +# ----------------------------------------------------------------------------------------# +# +# ELFIO library +# +# ----------------------------------------------------------------------------------------# + +# get_target_property(ELFIO_INCLUDE_DIR elfio::elfio INTERFACE_INCLUDE_DIRECTORIES) +# target_include_directories(rocprofiler-elfio SYSTEM INTERFACE ${ELFIO_INCLUDE_DIR}) +target_link_libraries(rocprofiler-elfio INTERFACE elfio::elfio) diff --git a/cmake/rocprofiler_interfaces.cmake b/cmake/rocprofiler_interfaces.cmake index 3efe0eb895..15377d5a3b 100644 --- a/cmake/rocprofiler_interfaces.cmake +++ b/cmake/rocprofiler_interfaces.cmake @@ -52,6 +52,8 @@ rocprofiler_add_interface_library(rocprofiler-cxx-filesystem "C++ filesystem lib rocprofiler_add_interface_library(rocprofiler-ptl "Parallel Tasking Library" INTERNAL) rocprofiler_add_interface_library(rocprofiler-elf "ElfUtils elf library" INTERNAL) rocprofiler_add_interface_library(rocprofiler-dw "ElfUtils dw library" INTERNAL) +rocprofiler_add_interface_library(rocprofiler-elfio "ELFIO header-only C++ library" + INTERNAL) # # interface for libraries (ROCm-specific) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index b52bf2009f..ee361ab3c5 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -151,6 +151,7 @@ if(ROCPROFILER_BUILD_DOCS) REPO_BRANCH "main") endif() +# perfetto rocprofiler_checkout_git_submodule( RECURSIVE RELATIVE_PATH external/perfetto @@ -172,3 +173,19 @@ set_target_properties( OUTPUT_NAME rocprofiler-sdk-perfetto) target_link_libraries(rocprofiler-perfetto INTERFACE $) + +# ELFIO +rocprofiler_checkout_git_submodule( + RECURSIVE + RELATIVE_PATH external/elfio + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + REPO_URL https://github.com/serge1/ELFIO.git + REPO_BRANCH "Release_3.12") + +set(ELFIO_BUILD_EXAMPLES OFF) +set(ELFIO_BUILD_TESTS OFF) +add_subdirectory(elfio EXCLUDE_FROM_ALL) +if(TARGET rocprofiler-elfio) + get_target_property(ELFIO_INCLUDE_DIR elfio::elfio INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(rocprofiler-elfio SYSTEM INTERFACE ${ELFIO_INCLUDE_DIR}) +endif() diff --git a/external/elfio b/external/elfio new file mode 160000 index 0000000000..8ae6cec5d6 --- /dev/null +++ b/external/elfio @@ -0,0 +1 @@ +Subproject commit 8ae6cec5d60495822ecd57d736f66149da9b1830 diff --git a/source/lib/common/CMakeLists.txt b/source/lib/common/CMakeLists.txt index 8e4ebe5ceb..28d859c98a 100644 --- a/source/lib/common/CMakeLists.txt +++ b/source/lib/common/CMakeLists.txt @@ -3,19 +3,22 @@ # rocprofiler_activate_clang_tidy() -set(common_sources environment.cpp demangle.cpp logging.cpp static_object.cpp utility.cpp - xml.cpp) +set(common_sources demangle.cpp elf_utils.cpp environment.cpp logging.cpp + static_object.cpp utility.cpp xml.cpp) set(common_headers abi.hpp defines.hpp - environment.hpp demangle.hpp + elf_utils.hpp + environment.hpp + filesystem.hpp logging.hpp mpl.hpp scope_destructor.hpp static_object.hpp stringize_arg.hpp synchronized.hpp + units.hpp utility.hpp xml.hpp) @@ -41,7 +44,8 @@ target_link_libraries( $ $ $ - $) + $ + $) set_target_properties(rocprofiler-common-library PROPERTIES OUTPUT_NAME rocprofiler-common) diff --git a/source/lib/common/elf_utils.cpp b/source/lib/common/elf_utils.cpp new file mode 100644 index 0000000000..54bb538295 --- /dev/null +++ b/source/lib/common/elf_utils.cpp @@ -0,0 +1,213 @@ +// MIT License +// +// Copyright (c) 2023 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "lib/common/elf_utils.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib/common/logging.hpp" + +namespace rocprofiler +{ +namespace common +{ +namespace elf_utils +{ +namespace +{ +const ELFIO::Elf_Xword PAGE_SIZE = sysconf(_SC_PAGESIZE); + +template +std::string +as_hex_string(Tp&& _v, size_t _w = 16) +{ + auto _ss = std::stringstream{}; + _ss.fill('0'); + _ss << "0x" << std::hex << std::setw(_w) << std::forward(_v); + return _ss.str(); +} +} // namespace + +SymbolEntry::SymbolEntry(unsigned int _idx, const accessor_type& _accessor) +: index{_idx} +{ + if(!_accessor.get_symbol(index, name, value, size, bind, type, section_index, other)) + throw std::runtime_error("Error in ELFIO::symbol_section_accessor::get_symbol"); +} + +DynamicEntry::DynamicEntry(unsigned int _idx, const accessor_type& _accessor) +: index{_idx} +{ + if(!_accessor.get_entry(_idx, tag, value, name)) return; +} + +RelocationEntry::RelocationEntry(unsigned int _idx, const accessor_type& _accessor) +: index{_idx} +{ + if(!_accessor.get_entry(_idx, offset, symbol, type, addend)) + throw std::runtime_error("Error in ELFIO::relocation_section_accessor::get_entry"); +} + +ElfInfo::ElfInfo(std::string _fname) +: filename{std::move(_fname)} +{} + +bool +ElfInfo::has_symbol(std::regex&& _re) const +{ + for(const auto& itr : symbol_entries) + { + if(!itr.name.empty() && std::regex_search(itr.name, _re)) return true; + } + + return false; +} + +ElfInfo +read(const std::string& _inp) +{ + auto _info = ElfInfo{_inp}; + auto& reader = _info.reader; + auto& sections = _info.sections; + auto& symbol_entries = _info.symbol_entries; + auto& dynamic_entries = _info.dynamic_entries; + auto& reloc_entries = _info.reloc_entries; + + ROCP_TRACE << "\nReading " << _inp; + + if(!reader.load(_inp)) throw std::runtime_error("Could not load elf file " + _inp); + + if(reader.get_class() == ELFIO::ELFCLASS32) + ROCP_TRACE << "ELF 32-bit"; + else + ROCP_TRACE << "ELF 64-bit"; + + ROCP_TRACE << "ELF file encoding: " + << ((reader.get_encoding() == ELFIO::ELFDATA2LSB) ? std::string_view{"Little endian"} + : std::string_view{"Big endian"}); + + ROCP_TRACE << "ELF version: " << reader.get_elf_version(); + ROCP_TRACE << "ELF header size: " << reader.get_header_size(); + ROCP_TRACE << "ELF OS ABI: " << reader.get_os_abi(); + + // Print ELF file sections info + ELFIO::Elf_Half sec_num = reader.sections.size(); + ROCP_TRACE << "Number of sections: " << sec_num; + + for(ELFIO::Elf_Half j = 0; j < sec_num; ++j) + { + ELFIO::section* psec = reader.sections[j]; + sections.emplace_back(psec); + } + + std::sort(sections.begin(), sections.end(), [](const Section* lhs, const Section* rhs) { + return std::string_view{lhs->get_name()} < std::string_view{rhs->get_name()}; + }); + + for(ELFIO::Elf_Half j = 0; j < sec_num; ++j) + { + Section* psec = sections.at(j); + ROCP_TRACE << " [" << j << "] \t" << std::setw(20) << psec->get_name() << "\t : \t" + << "size / entry-size = " << std::setw(6) << psec->get_size() << " / " + << std::setw(3) << psec->get_entry_size() + << " | addr: " << as_hex_string(psec->get_address()) + << " | offset: " << as_hex_string(psec->get_offset()); + + if(psec->get_size() == 0) continue; + + if(psec->get_type() == ELFIO::SHT_SYMTAB) + { + const ELFIO::symbol_section_accessor _symbols(reader, psec); + ROCP_TRACE << " Number of symbol_entries: " << _symbols.get_symbols_num(); + for(ELFIO::Elf_Xword k = 0; k < _symbols.get_symbols_num(); ++k) + symbol_entries.emplace_back(k, _symbols); + } + else if(psec->get_type() == ELFIO::SHT_DYNAMIC) + { + const ELFIO::dynamic_section_accessor dynamic{reader, psec}; + ROCP_TRACE << " Number of dynamic entries: " << dynamic.get_entries_num(); + for(ELFIO::Elf_Xword k = 0; k < dynamic.get_entries_num(); ++k) + dynamic_entries.emplace_back(k, dynamic); + } + else if(psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA) + { + const ELFIO::relocation_section_accessor reloc{reader, psec}; + ROCP_TRACE << " Number of relocation entries: " << reloc.get_entries_num(); + for(ELFIO::Elf_Xword k = 0; k < reloc.get_entries_num(); ++k) + reloc_entries.emplace_back(k, reloc); + } + } + + ROCP_TRACE << "Symbols:"; + for(size_t k = 0; k < symbol_entries.size(); ++k) + { + if(!symbol_entries.at(k).name.empty()) + ROCP_TRACE << " [" << k << "] " << symbol_entries.at(k).name; + } + + ROCP_TRACE << "Dynamic entries:"; + for(size_t k = 0; k < dynamic_entries.size(); ++k) + { + if(!dynamic_entries.at(k).name.empty()) + ROCP_TRACE << " [" << k << "] " << dynamic_entries.at(k).name; + } + + ROCP_TRACE << "Relocation entries:"; + for(size_t k = 0; k < reloc_entries.size(); ++k) + { + auto _sym_idx = reloc_entries.at(k).symbol; + auto _name = std::string{}; + if(_sym_idx < symbol_entries.size()) _name = symbol_entries.at(_sym_idx).name; + if(!_name.empty()) ROCP_TRACE << " [" << k << "] " << _name; + } + + // Print ELF file segments info + ELFIO::Elf_Half seg_num = reader.segments.size(); + ROCP_TRACE << "Number of segments: " << seg_num; + for(ELFIO::Elf_Half j = 0; j < seg_num; ++j) + { + const ELFIO::segment* pseg = reader.segments[j]; + ROCP_TRACE << " [" << std::setw(2) << j << "] flags: " << as_hex_string(pseg->get_flags()) + << " offset: " << as_hex_string(pseg->get_offset()) + << " align: " << as_hex_string(pseg->get_align()) + << " virt: " << as_hex_string(pseg->get_virtual_address()) + << " phys: " << as_hex_string(pseg->get_physical_address()) + << " fsize: " << std::setw(8) << pseg->get_file_size() + << " msize: " << std::setw(8) << pseg->get_memory_size(); + } + + return _info; +} +} // namespace elf_utils +} // namespace common +} // namespace rocprofiler diff --git a/source/lib/common/elf_utils.hpp b/source/lib/common/elf_utils.hpp new file mode 100644 index 0000000000..33946bad32 --- /dev/null +++ b/source/lib/common/elf_utils.hpp @@ -0,0 +1,121 @@ +// MIT License +// +// Copyright (c) 2023 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace rocprofiler +{ +namespace common +{ +namespace elf_utils +{ +using Section = ELFIO::section; +using Segment = ELFIO::segment; + +struct SymbolEntry +{ + using accessor_type = ELFIO::symbol_section_accessor; + + SymbolEntry(unsigned int _idx, const accessor_type& _accessor); + + unsigned int index = 0; + std::string name = {}; + ELFIO::Elf64_Addr value = {}; + ELFIO::Elf_Xword size = {}; + unsigned char bind = {}; + unsigned char type = {}; + ELFIO::Elf_Half section_index = {}; + unsigned char other = {}; +}; + +struct DynamicEntry +{ + using accessor_type = ELFIO::dynamic_section_accessor; + + DynamicEntry(unsigned int _idx, const accessor_type& _accessor); + + unsigned int index = 0; + std::string name = {}; + ELFIO::Elf_Xword tag = {}; + ELFIO::Elf_Xword value = {}; +}; + +struct RelocationEntry +{ + using accessor_type = ELFIO::relocation_section_accessor; + + RelocationEntry(unsigned int _idx, const accessor_type& _accessor); + + unsigned int index = 0; + ELFIO::Elf64_Addr offset = {}; + ELFIO::Elf_Word symbol = {}; + ELFIO::Elf_Word type = {}; + ELFIO::Elf_Sxword addend = {}; +}; + +struct ElfInfo +{ + explicit ElfInfo(std::string); + + std::string filename = {}; + ELFIO::elfio reader = {}; + std::vector sections = {}; + std::vector symbol_entries = {}; + std::vector dynamic_entries = {}; + std::vector reloc_entries = {}; + + bool has_symbol(std::regex&&) const; + + friend bool operator==(const ElfInfo& lhs, const ElfInfo& rhs) + { + return (lhs.filename == rhs.filename); + } + + friend bool operator<(const ElfInfo& lhs, const ElfInfo& rhs) + { + return (lhs.filename < rhs.filename); + } + + friend bool operator>(const ElfInfo& lhs, const ElfInfo& rhs) + { + return !(lhs == rhs || lhs < rhs); + } + + friend bool operator<=(const ElfInfo& lhs, const ElfInfo& rhs) { return !(lhs > rhs); } + friend bool operator>=(const ElfInfo& lhs, const ElfInfo& rhs) { return !(lhs < rhs); } +}; + +ElfInfo +read(const std::string& _inp); +} // namespace elf_utils +} // namespace common +} // namespace rocprofiler diff --git a/source/lib/rocprofiler-sdk/registration.cpp b/source/lib/rocprofiler-sdk/registration.cpp index 8561c95920..2c5dfcd33c 100644 --- a/source/lib/rocprofiler-sdk/registration.cpp +++ b/source/lib/rocprofiler-sdk/registration.cpp @@ -23,7 +23,9 @@ #define _GNU_SOURCE 1 #include "lib/rocprofiler-sdk/registration.hpp" +#include "lib/common/elf_utils.hpp" #include "lib/common/environment.hpp" +#include "lib/common/filesystem.hpp" #include "lib/common/logging.hpp" #include "lib/common/static_object.hpp" #include "lib/rocprofiler-sdk/agent.hpp" @@ -88,6 +90,8 @@ namespace registration { namespace { +namespace fs = ::rocprofiler::common::filesystem; + // invoke all rocprofiler_configure symbols bool invoke_client_configures(); @@ -129,8 +133,7 @@ std::vector get_link_map() { auto chain = std::vector{}; - void* handle = nullptr; - handle = dlopen(nullptr, RTLD_LAZY | RTLD_NOLOAD); + void* handle = dlopen(nullptr, RTLD_LAZY | RTLD_NOLOAD); if(handle) { @@ -245,6 +248,16 @@ find_clients() { ROCP_INFO << "[env] searching " << itr << " for rocprofiler_configure"; + if(fs::exists(itr)) + { + auto elfinfo = common::elf_utils::read(itr); + if(!elfinfo.has_symbol(std::regex{"^rocprofiler_configure$"})) + { + ROCP_FATAL << "rocprofiler tool library " << itr + << " did not contain rocprofiler_configure symbol"; + } + } + void* handle = dlopen(itr.c_str(), RTLD_NOLOAD | RTLD_LAZY); if(!handle) @@ -294,12 +307,24 @@ find_clients() // if there are two "rocprofiler_configures", we need to trigger a search of all the shared // libraries - if(_next_configure) + if(_default_configure) { for(const auto& itr : get_link_map()) { ROCP_INFO << "searching " << itr << " for rocprofiler_configure"; + if(fs::exists(itr)) + { + auto elfinfo = common::elf_utils::read(itr); + if(!elfinfo.has_symbol(std::regex{"^rocprofiler_configure$"})) continue; + } + else + { + continue; + } + + ROCP_INFO << "dlopening " << itr << " for rocprofiler_configure"; + void* handle = dlopen(itr.c_str(), RTLD_LAZY | RTLD_NOLOAD); ROCP_ERROR_IF(handle == nullptr) << "error dlopening " << itr;