From 6f31c9b6deca7ba217bbe136baff18d044c2730b Mon Sep 17 00:00:00 2001 From: Christian von Elm Date: Wed, 6 Nov 2024 10:56:17 +0100 Subject: [PATCH 1/6] feat(cmake): remove binutils, add libelf --- CMakeLists.txt | 19 ++++++------ cmake/FindLibElf.cmake | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 cmake/FindLibElf.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c3f91f3..f7a91def 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ set_property(CACHE lo2s_USE_STATIC_LIBS PROPERTY STRINGS "MOSTLY" "OFF" "ALL") IfUpdatedUnsetAll(lo2s_USE_STATIC_LIBS Dl_USE_STATIC_LIBS - Binutils_USE_STATIC_LIBS + LibElf_USE_STATIC_LIBS OTF2_USE_STATIC_LIBS OTF2XX_USE_STATIC_LIBS Libpfm_USE_STATIC_LIBS @@ -39,7 +39,7 @@ IfUpdatedUnsetAll(lo2s_USE_STATIC_LIBS if(lo2s_USE_STATIC_LIBS STREQUAL "OFF") set(Dl_USE_STATIC_LIBS OFF CACHE BOOL "") - set(Binutils_USE_STATIC_LIBS OFF CACHE BOOL "") + set(LibElf_USE_STATIC_LIBS OFF CACHE BOOL "") set(OTF2_USE_STATIC_LIBS OFF CACHE BOOL "") set(OTF2XX_USE_STATIC_LIBS OFF CACHE BOOL "") set(X86Adapt_STATIC OFF CACHE BOOL "") @@ -51,7 +51,7 @@ endif() if(lo2s_USE_STATIC_LIBS STREQUAL "MOSTLY") set(Dl_USE_STATIC_LIBS OFF CACHE BOOL "") - set(Binutils_USE_STATIC_LIBS ON CACHE BOOL "") + set(LibElf_USE_STATIC_LIBS ON CACHE BOOL "") set(OTF2_USE_STATIC_LIBS ON CACHE BOOL "") set(OTF2XX_USE_STATIC_LIBS ON CACHE BOOL "") set(X86Adapt_STATIC ON CACHE BOOL "") @@ -64,7 +64,7 @@ endif() if(lo2s_USE_STATIC_LIBS STREQUAL "ALL") set(Dl_USE_STATIC_LIBS ON CACHE BOOL "") - set(Binutils_USE_STATIC_LIBS ON CACHE BOOL "") + set(LibElf_USE_STATIC_LIBS ON CACHE BOOL "") set(OTF2_USE_STATIC_LIBS ON CACHE BOOL "") set(OTF2XX_USE_STATIC_LIBS ON CACHE BOOL "") set(X86Adapt_STATIC ON CACHE BOOL "") @@ -101,7 +101,6 @@ include(lib/x86_adapt/x86_adapt.cmake) # find external dependencies find_package(Git) -find_package(Binutils REQUIRED) set(THREADS_PREFER_PTHREAD_FLAG true) find_package(Threads REQUIRED) find_package(Doxygen COMPONENTS dot) @@ -112,6 +111,7 @@ find_package(Veosinfo) find_package(Libpfm) find_package(PkgConfig) find_package(CUDAToolkit) +find_package(LibElf REQUIRED) if(PkgConfig_FOUND) pkg_check_modules(Audit audit) @@ -206,8 +206,7 @@ set(SOURCE_FILES src/config.cpp src/main.cpp src/monitor/process_monitor.cpp src/platform.cpp - src/topology.cpp src/bfd_resolve.cpp src/pipe.cpp - src/mmap.cpp + src/topology.cpp src/pipe.cpp src/dwarf_resolve.cpp src/util.cpp src/perf/util.cpp src/syscalls.cpp @@ -226,10 +225,12 @@ target_link_libraries(lo2s Nitro::dl Nitro::options Threads::Threads - Binutils::Binutils fmt::fmt std::filesystem -) + LibElf::LibElf + LibElf::LibDw + debuginfod + ) # old glibc versions require -lrt for clock_gettime() if(NOT CLOCK_GETTIME_FOUND) diff --git a/cmake/FindLibElf.cmake b/cmake/FindLibElf.cmake new file mode 100644 index 00000000..a35e0ff5 --- /dev/null +++ b/cmake/FindLibElf.cmake @@ -0,0 +1,69 @@ +# Copyright (c) 2022, Technische Universität Dresden, Germany +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions +# and the following disclaimer in the documentation and/or other materials provided with the +# distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse +# or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(${CMAKE_CURRENT_LIST_DIR}/UnsetIfUpdated.cmake) + +# Linking libelf isn't a great default because it produces warnings +option(LibElf_USE_STATIC_LIBS "Link libelf statically." OFF) + +UnsetIfUpdated(LibElf_LIBRARIES LibElf_USE_STATIC_LIBS) + +find_path(LibElf_INCLUDE_DIRS libelf.h + PATHS ENV C_INCLUDE_PATH ENV CPATH + PATH_SUFFIXES include) + +find_path(LibDw_INCLUDE_DIRS elfutils/libdw.h + PATHS ENV C_INCLUDE_PATH ENV CPATH + PATH_SUFFIXES include) + +if(LibElf_USE_STATIC_LIBS) + find_library(LibElf_LIBRARY NAMES libelf.a + HINTS ENV LIBRARY_PATH) + find_library(LibDw_LIBRARY NAMES libdw.a + HINTS ENV LIBRARY_PATH) + set(LibElf_LIBRARIES ${LibElf_LIBRARY} ${LibDw_LIBRARY}) +else() + find_library(LibElf_LIBRARY NAMES libelf.so + HINTS ENV LIBRARY_PATH LD_LIBRARY_PATH) + find_library(LibDw_LIBRARY NAMES libdw.so + HINTS ENV LIBRARY_PATH LD_LIBRARY_PATH) + set(LibElf_LIBRARIES ${LibElf_LIBRARY} ${LibDw_LIBRARY}) +endif() + +include (FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibElf DEFAULT_MSG + LibElf_LIBRARIES + LibElf_INCLUDE_DIRS) + +if(LibElf_FOUND) + add_library(LibElf::LibElf UNKNOWN IMPORTED) + set_property(TARGET LibElf::LibElf PROPERTY IMPORTED_LOCATION ${LibElf_LIBRARY}) + target_include_directories(LibElf::LibElf INTERFACE ${LibElf_INCLUDE_DIRS}) + add_library(LibElf::LibDw UNKNOWN IMPORTED) + set_property(TARGET LibElf::LibDw PROPERTY IMPORTED_LOCATION ${LibDw_LIBRARY}) + target_include_directories(LibElf::LibDw INTERFACE ${LibDw_INCLUDE_DIRS}) +endif() + +mark_as_advanced(LibElf_LIBRARIES LibElf_INCLUDE_DIRS) From 462e37a56fddf8af2050223fb23e582b6760e901 Mon Sep 17 00:00:00 2001 From: Christian von Elm Date: Wed, 6 Nov 2024 10:58:43 +0100 Subject: [PATCH 2/6] nuke libbfd based implementation from orbit --- include/lo2s/bfd_resolve.hpp | 179 -------------------------- src/bfd_resolve.cpp | 240 ----------------------------------- 2 files changed, 419 deletions(-) delete mode 100644 include/lo2s/bfd_resolve.hpp delete mode 100644 src/bfd_resolve.cpp diff --git a/include/lo2s/bfd_resolve.hpp b/include/lo2s/bfd_resolve.hpp deleted file mode 100644 index ddad8085..00000000 --- a/include/lo2s/bfd_resolve.hpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * This file is part of the lo2s software. - * Linux OTF2 sampling - * - * Copyright (c) 2016, - * Technische Universitaet Dresden, Germany - * - * lo2s is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * lo2s is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with lo2s. If not, see . - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include -#include - -using std::hex; - -#include - -extern "C" -{ -// https://sourceware.org/bugzilla/show_bug.cgi?id=14243 -#define PACKAGE 1 -#define PACKAGE_VERSION 1 -#include -// A really cool API hides it's magic constants! -#ifndef DMGL_PARAMS -#define DMGL_NO_OPTS 0 /* For readability... */ -#define DMGL_PARAMS (1 << 0) /* Include function args */ -#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ -#endif - -// Don't mind me, just randomly change the public interface -// https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=commitdiff;h=fd3619828e94a24a92cddec42cbc0ab33352eeb4 -#ifndef bfd_get_section_size -#define bfd_get_section_size bfd_section_size -#endif -#ifndef bfd_get_section_name -#define bfd_get_section_name(bfd, sec) bfd_section_name(sec) -#endif - -#include -} - -namespace lo2s -{ -namespace bfdr -{ - -struct Initializer -{ - Initializer() - { - bfd_init(); - } -}; - -class InitError : public std::runtime_error -{ -public: - InitError(const std::string& what, const std::string& lib) - : std::runtime_error(what + ": " + lib) - { - } - - InitError(const std::string& what) : std::runtime_error(what) - { - } -}; - -class InvalidFileError : public std::runtime_error -{ -public: - InvalidFileError(const std::string& what, const std::string& dso) - : std::runtime_error(what + ": " + dso) - { - } -}; - -class LookupError : public std::runtime_error -{ -public: - LookupError(const std::string& what, Address addr) : std::runtime_error(msg(what, addr)) - { - } - -private: - static std::string msg(const std::string& what, Address addr) - { - std::stringstream ss; - ss << what << ": 0x" << hex << addr; - return ss.str(); - } -}; - -struct bfd_handle_deleter -{ - void operator()(bfd* p) const - { - bfd_close(p); - } -}; - -struct char_handle_deleter -{ - void operator()(char* p) const - { - free(p); - } -}; - -using unique_bfd_ptr = std::unique_ptr; -using unique_char_ptr = std::unique_ptr; - -class Lib -{ -public: - Lib(const std::string& name); - Lib(const Lib&) = delete; - Lib(Lib&&) = delete; - Lib& operator=(const Lib&) = delete; - Lib& operator=(Lib&&) = delete; - - LineInfo lookup(Address addr) const; - - const std::string& name() const - { - return name_; - } - -private: - void filter_symbols() - { - symbols_.erase(std::remove_if(symbols_.begin(), symbols_.end(), [](const asymbol* sym) { - return (sym != nullptr) && !(sym->flags & BSF_FUNCTION); - })); - } - - void read_symbols(); - - void read_sections(); - - template - static T check_symtab(T sz) - { - if (sz < 0) - { - throw std::runtime_error("Error getting symtab size"); - } - return sz; - } - - std::string name_; - unique_bfd_ptr handle_; - std::vector symbols_; - std::map sections_; - - static Initializer dummy_; -}; -} // namespace bfdr -} // namespace lo2s diff --git a/src/bfd_resolve.cpp b/src/bfd_resolve.cpp deleted file mode 100644 index eaf56ac9..00000000 --- a/src/bfd_resolve.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/* - * This file is part of the lo2s software. - * Linux OTF2 sampling - * - * Copyright (c) 2016, - * Technische Universitaet Dresden, Germany - * - * lo2s is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * lo2s is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with lo2s. If not, see . - */ -#include -#include -#include - -#include - -#include - -#include -#include - -namespace lo2s -{ -namespace bfdr -{ - -static std::filesystem::path check_path(const std::string& name) -{ - std::error_code ec; - auto path = std::filesystem::canonical(name, ec); - if (ec) - { - throw InvalidFileError("could not resolve to canonical path", name); - } - - auto status = std::filesystem::status(path, ec); - if (ec) - { - throw InvalidFileError("could not read status", name); - } - - if (!std::filesystem::is_regular_file(status)) - { - throw InvalidFileError("not a regular file", name); - } - - return path; -} - -Initializer Lib::dummy_; - -static void dummy_bfd_error_handler(const char*, [[maybe_unused]] va_list argp) -{ - return; -} - -Lib::Lib(const std::string& name) -: name_(name), handle_(bfd_openr(check_path(name).c_str(), nullptr)) -{ - if (!handle_) - { - throw InitError("failed to open BFD handle", name); - } - - nitro::log::severity_level sl = lo2s::logging::get_min_severity_level(); - - if (sl > nitro::log::severity_level::debug) - { - bfd_set_error_handler(&dummy_bfd_error_handler); - } - - // init stuff - // Not sure if those are needed for side-effects - bfd_get_arch_size(handle_.get()); - bfd_find_target(nullptr, handle_.get()); - if (!bfd_check_format(handle_.get(), bfd_object)) - { - throw InitError("Not a valid bfd_object", name); - } - - // if (bfd_get_flavour(handle_) != bfd_target_elf_flavour) - // { - // log::error() << "Only supporting elf right now. Need symbol size."; - // throw std::runtime_error("BFD flavor not ELF."); - // } - - read_symbols(); - read_sections(); -} - -LineInfo Lib::lookup(Address addr) const -{ - const char* file = nullptr; - const char* func = nullptr; - unsigned int line = 0; - - try - { - auto section = sections_.at(addr); - // VMA is useless for shared libraries, we remove the offset from the map - // so use filepos instead - // auto base = bfd_get_section_vma(handle_, section); - auto base = section->filepos; - auto size = bfd_get_section_size(section); - auto local_addr = addr - base; - if (local_addr >= base + size) - { - // This should not happen as long as we lookup sections in the range-map by - // address, since base/size is the same as used for the ranges - Log::error() << "address " << addr << " out of section bounds " << name_ << " " - << bfd_get_section_name(handle_.get(), section) << " filepos: " << hex - << base << ", size: " << hex << size; - throw LookupError("address out of .symtab bounds", addr); - } - auto evil_symtab = const_cast(symbols_.data()); - auto found = bfd_find_nearest_line(handle_.get(), section, evil_symtab, local_addr.value(), - &file, &func, &line); - if (!found) - { - Log::debug() << "bfd_find_nearest_line failed " << addr << " in " << name_ - << bfd_get_section_name(handle_.get(), section); - throw LookupError("bfd_find_nearest_line failed", addr); - } - if (func != nullptr) - { - unique_char_ptr demangled(bfd_demangle(handle_.get(), func, DMGL_PARAMS | DMGL_ANSI)); - if (demangled) - { - LineInfo line_info = LineInfo::for_function(file, demangled.get(), line, name_); - return line_info; - } - } - return LineInfo::for_function(file, func, line, name_); - } - catch (std::out_of_range&) - { - // TODO handle better - // unfortunately this happens often, so it's only debug - Log::debug() << "could not find section for " << addr << " in " << name_; - throw LookupError("could not find section", addr); - } -} - -void Lib::read_symbols() -{ - // read this: https://blogs.oracle.com/ali/entry/inside_elf_symbol_tables - try - { - symbols_.resize(check_symtab(bfd_get_symtab_upper_bound(handle_.get()))); - // Note: Keep the extra NULL element at the end for later use! - symbols_.resize(1 + check_symtab(bfd_canonicalize_symtab(handle_.get(), symbols_.data()))); - // filter_symbols(); - } - catch (std::exception& e) - { - Log::debug() << "failed to get symtab: " << e.what(); - symbols_.resize(0); - } - if (symbols_.size() <= 1) - { - try - { - // Use dynamic symtab only when regular symbols are not available - // Usually regular symtab should be a superset. There also doesn't seem to be much - // of a difference in how to handle them. - // See linux/tools/perf/util/symbol-elf.c symsrc__init - symbols_.resize(check_symtab(bfd_get_dynamic_symtab_upper_bound(handle_.get()))); - symbols_.resize( - 1 + check_symtab(bfd_canonicalize_dynamic_symtab(handle_.get(), symbols_.data()))); - // filter_symbols(); - } - catch (std::exception& e) - { - Log::debug() << "failed to get dynsym: " << e.what(); - symbols_.resize(0); - } - } - if (symbols_.size() <= 1) - { - throw InitError("could not find any symbols in .symtab or .dynsym", name_); - } -} - -void Lib::read_sections() -{ - // We could read *all* sections using bfd_map_ver_sections, however some sections are - // overlapping :-(, so we couldn't put them in our range-map. So instead use only the - // sections that are referenced by a function symbol. - for (auto sym : symbols_) - { - if (sym == nullptr || !(sym->flags & BSF_FUNCTION)) - { - continue; - } - auto section = sym->section; - if (bfd_is_und_section(section)) - { - continue; - } - // VMA is useless for shared libraries, we remove the offset from the map - // so use filepos instead - // auto start = bfd_get_section_vma(handle_, section); - auto start = section->filepos; - auto size = bfd_get_section_size(section); - if (size == 0) - { - Log::debug() << "skipping empty section: " - << bfd_get_section_name(handle_.get(), section); - continue; - } - try - { - auto r = sections_.emplace(std::piecewise_construct, - std::forward_as_tuple(start, start + size), - std::forward_as_tuple(section)); - if (r.second) - { - Log::trace() << "Added section: " << bfd_get_section_name(handle_.get(), section) - << " from symbol " << sym->name; - } - } - catch (Range::Error& e) - { - Log::warn() << "failed to add section " << bfd_get_section_name(handle_.get(), section) - << "due to " << e.what(); - } - } -} -} // namespace bfdr -} // namespace lo2s From a622be3d479d3aa0bba30864015e998f013f0dc3 Mon Sep 17 00:00:00 2001 From: Christian von Elm Date: Thu, 7 Nov 2024 08:07:33 +0100 Subject: [PATCH 3/6] Move event definitions to include/perf/types.hpp Not everything that wants to interact with perf events is a derived class of EventReader, such as the process memory map handling code. So, move relevant event definitions into neutral ground. --- include/lo2s/perf/event_reader.hpp | 42 +-------- include/lo2s/perf/types.hpp | 133 +++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 39 deletions(-) create mode 100644 include/lo2s/perf/types.hpp diff --git a/include/lo2s/perf/event_reader.hpp b/include/lo2s/perf/event_reader.hpp index 1a1a1bc1..587e8726 100644 --- a/include/lo2s/perf/event_reader.hpp +++ b/include/lo2s/perf/event_reader.hpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -66,32 +66,8 @@ class EventReader using RecordUnknownType = perf_event_header; using RecordMmapType = lo2s::RecordMmapType; - - struct RecordMmap2Type - { - // BAD things happen if you try this - RecordMmap2Type() = delete; - RecordMmap2Type(const RecordMmap2Type&) = delete; - RecordMmap2Type& operator=(const RecordMmap2Type&) = delete; - RecordMmap2Type(RecordMmap2Type&&) = delete; - RecordMmap2Type& operator=(RecordMmap2Type&&) = delete; - - struct perf_event_header header; - uint32_t pid; - uint32_t tid; - uint64_t addr; - uint64_t len; - uint64_t pgoff; - uint32_t maj; - uint32_t min; - uint64_t ino; - uint64_t ino_generation; - uint32_t prot; - uint32_t flags; - // Note ISO C++ forbids zero-size array, but this struct is exclusively used as pointer - char filename[1]; - // struct sample_id sample_id; - }; + using RecordMmap2Type = lo2s::RecordMmap2Type; + using RecordCommType = lo2s::RecordCommType; struct RecordLostType { @@ -138,18 +114,6 @@ class EventReader // struct sample_id sample_id; }; - /** - * \brief structure for PERF_RECORD_COMM events - **/ - struct RecordCommType - { - struct perf_event_header header; - uint32_t pid; - uint32_t tid; - char comm[1]; // ISO C++ forbits zero-size array - // struct sample_id sample_id; - }; - EventReader() = default; EventReader(EventReader&) = delete; diff --git a/include/lo2s/perf/types.hpp b/include/lo2s/perf/types.hpp new file mode 100644 index 00000000..6d560ee6 --- /dev/null +++ b/include/lo2s/perf/types.hpp @@ -0,0 +1,133 @@ +/* + * This file is part of the lo2s software. + * Linux OTF2 sampling + * + * Copyright (c) 2016, + * Technische Universitaet Dresden, Germany + * + * lo2s is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lo2s is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lo2s. If not, see . + */ + +#pragma once + +#include +#include + +#include +#include + +extern "C" +{ +#include +} + +namespace lo2s +{ + +template +class PerfEventCache +{ +public: + PerfEventCache() = delete; + PerfEventCache(const PerfEventCache&) = delete; + PerfEventCache& operator=(const PerfEventCache&) = delete; + + PerfEventCache(const T* event, size_t size) : data_(std::make_unique(size)) + { + memcpy(data_.get(), event, size); + } + + T* get() + { + return reinterpret_cast(data_.get()); + } + + const T* get() const + { + return reinterpret_cast(data_.get()); + } + +private: + std::unique_ptr data_; +}; + +struct RecordMmapType +{ + // BAD things happen if you try this + RecordMmapType() = delete; + RecordMmapType(const RecordMmapType&) = delete; + RecordMmapType& operator=(const RecordMmapType&) = delete; + RecordMmapType(RecordMmapType&&) = delete; + RecordMmapType& operator=(RecordMmapType&&) = delete; + + struct perf_event_header header; + uint32_t pid, tid; + uint64_t addr; + uint64_t len; + uint64_t pgoff; + // Note ISO C++ forbids zero-size array, but this struct is exclusively used as pointer + char filename[1]; + // struct sample_id id; +}; + +/** + * \brief structure for PERF_RECORD_COMM events + **/ + +struct __attribute((__packed__)) sample_id +{ + uint32_t pid, tid; + uint64_t time; + uint32_t cpu, res; +}; + +struct __attribute((__packed__)) RecordCommType +{ + struct perf_event_header header; + uint32_t pid; + uint32_t tid; + char comm[1]; // ISO C++ forbits zero-size array + // struct sample_id id; +}; + +struct __attribute((__packed__)) RecordMmap2Type +{ + // BAD things happen if you try this + RecordMmap2Type() = delete; + RecordMmap2Type(const RecordMmap2Type&) = delete; + RecordMmap2Type& operator=(const RecordMmap2Type&) = delete; + RecordMmap2Type(RecordMmap2Type&&) = delete; + RecordMmap2Type& operator=(RecordMmap2Type&&) = delete; + + struct perf_event_header header; + uint32_t pid; + uint32_t tid; + uint64_t addr; + uint64_t len; + uint64_t pgoff; + uint32_t maj; + uint32_t min; + uint64_t ino; + uint64_t ino_generation; + uint32_t prot; + uint32_t flags; + // Note ISO C++ forbids zero-size array, but this struct is exclusively used as pointer + char filename[1]; + // struct sample_id sample_id; +}; + +using RawMemoryMapCache = std::deque>; +using RawCommCache = std::deque>; + +} // namespace lo2s From e0101d59a0944e7a87e55f157ae4fc0f7d666860 Mon Sep 17 00:00:00 2001 From: Christian von Elm Date: Thu, 7 Nov 2024 08:38:20 +0100 Subject: [PATCH 4/6] Simplify calling context managment, include timepoint tracking -> rename ref stuff All the stuff just being reference numbers in OTF2 underneath is such an unimportant implementation detail. rename it to "LocalCctx" "GlobalCctx". This way it becomes clear that we first generate local calling contexts in every sample::Writer and then map them to the global calling contexts later on in post processing. -> simplify calling context tracking most of it was needlessly complex, as we don't need to record context in most cases we simply have it. -> use Mmap2 Record format instead of mmap2 according to the perf_event_open man page, RecordMmap does not support adding the sample_id fields, such as the event timestamp to RecordMmap events, only to RecordMmap2 events, so change to the latest and greatest format. -> introduce exec and mmap event timestamp tracking Keep track of execs and when memory mappings happen by time. Currently, lo2s does process tracking solely by pid. This approach failes in the context of execs, where instruction pointer 0xc0ffee can refer to one thing before the exec and to something different altogether after the exec. We now can differentiate between different processes sharing the same pid over time. However, one important part is still missing, time tracking for instruction pointers. This is programmatically the hard part. But for now this at least makes the common case (fork() then immediate exec()) work better. -> simplify cctx tracking Track Thread CCTX (used for calling context enter/leave events) separately from Instruction Pointer CCTx (used for calling context sample events). Remove a layer of indirection. Track instruction pointers per process instead of per thread. (Threads can not have different memory images. --- include/lo2s/monitor/main_monitor.hpp | 8 +- include/lo2s/perf/calling_context_manager.hpp | 137 +++++++++++------- include/lo2s/perf/sample/reader.hpp | 1 + include/lo2s/perf/sample/writer.hpp | 10 +- include/lo2s/perf/types.hpp | 4 +- include/lo2s/trace/trace.hpp | 76 +++------- src/monitor/cpu_set_monitor.cpp | 3 +- src/monitor/main_monitor.cpp | 39 ++++- src/monitor/process_monitor.cpp | 2 +- src/perf/sample/writer.cpp | 49 ++++--- src/trace/trace.cpp | 136 ++++++++--------- 11 files changed, 248 insertions(+), 217 deletions(-) diff --git a/include/lo2s/monitor/main_monitor.hpp b/include/lo2s/monitor/main_monitor.hpp index 3e58f4c9..944bcd11 100644 --- a/include/lo2s/monitor/main_monitor.hpp +++ b/include/lo2s/monitor/main_monitor.hpp @@ -31,7 +31,6 @@ #ifdef HAVE_SENSORS #include #endif -#include #include #ifdef HAVE_VEOSINFO #include @@ -61,16 +60,17 @@ class MainMonitor return trace_; } - void insert_cached_mmap_events(const RawMemoryMapCache& cached_events); + void insert_cached_events(const RawMemoryMapCache& cached_events, + const RawCommCache& cached_comms); - std::map& get_process_infos() + ProcessMap& get_process_infos() { return process_infos_; } protected: trace::Trace trace_; - std::map process_infos_; + ProcessMap process_infos_; metric::plugin::Metrics metrics_; std::vector> tracepoint_monitors_; diff --git a/include/lo2s/perf/calling_context_manager.hpp b/include/lo2s/perf/calling_context_manager.hpp index 7dc32c49..268a0469 100644 --- a/include/lo2s/perf/calling_context_manager.hpp +++ b/include/lo2s/perf/calling_context_manager.hpp @@ -20,62 +20,45 @@ */ #pragma once -#include -#include +#include +#include #include -namespace lo2s +extern "C" { -namespace perf +#include +} + +namespace lo2s { -class CallingContextManager +struct LocalCctx { -public: - CallingContextManager(trace::Trace& trace) : local_cctx_refs_(trace.create_cctx_refs()) + LocalCctx(otf2::definition::calling_context::reference_type r) : ref(r) { } - void thread_enter(Process process, Thread thread) - { - auto ret = - local_cctx_refs_.map.emplace(std::piecewise_construct, std::forward_as_tuple(thread), - std::forward_as_tuple(process, next_cctx_ref_)); - if (ret.second) - { - next_cctx_ref_++; - } + otf2::definition::calling_context::reference_type ref; + std::map children; +}; - current_thread_cctx_refs_ = &(*ret.first); +class LocalCctxMap +{ +public: + LocalCctxMap() + { } void finalize(otf2::writer::local* otf2_writer) { - local_cctx_refs_.ref_count = next_cctx_ref_; + ref_count_ = next_cctx_ref_; // set writer last, because it is used as sentry to confirm that the cctx refs are properly // finalized. - local_cctx_refs_.writer = otf2_writer; - } - - bool thread_changed(Thread thread) - { - return !current_thread_cctx_refs_ || current_thread_cctx_refs_->first != thread; - } - - otf2::definition::calling_context::reference_type current() - { - if (current_thread_cctx_refs_) - { - return current_thread_cctx_refs_->second.entry.ref; - } - else - { - return otf2::definition::calling_context::reference_type::undefined(); - } + writer_ = otf2_writer; } - otf2::definition::calling_context::reference_type sample_ref(uint64_t num_ips, + otf2::definition::calling_context::reference_type sample_ref(Process p, uint64_t num_ips, const uint64_t ips[]) { // For unwind distance definiton, see: @@ -96,40 +79,80 @@ class CallingContextManager // information. // // Having these things in mind, look at this line and tell me, why it is still wrong: - auto children = ¤t_thread_cctx_refs_->second.entry.children; + auto children = &map[p]; + uint64_t ref = -1; for (uint64_t i = num_ips - 1;; i--) { + if (ips[i] == PERF_CONTEXT_KERNEL) + { + if (i <= 1) + { + return ref; + } + continue; + } + else if (ips[i] == PERF_CONTEXT_USER) + { + if (i <= 1) + { + return ref; + } + continue; + } auto it = find_ip_child(ips[i], *children); - // We intentionally discard the last sample as it is somewhere in the kernel - if (i == 1) + ref = it->second.ref; + if (i == 0) { - return it->second.ref; + return ref; } children = &it->second.children; } } - otf2::definition::calling_context::reference_type sample_ref(uint64_t ip) + otf2::definition::calling_context::reference_type sample_ref(Process p, uint64_t ip) { - auto it = find_ip_child(ip, current_thread_cctx_refs_->second.entry.children); + auto it = find_ip_child(ip, map[p]); return it->second.ref; } - void thread_leave(Thread thread) + otf2::definition::calling_context::reference_type thread(Process process, Thread thread) { - assert(current_thread_cctx_refs_); - if (current_thread_cctx_refs_->first != thread) + auto ret = + thread_cctxs_[process].emplace(std::piecewise_construct, std::forward_as_tuple(thread), + std::forward_as_tuple(next_cctx_ref_)); + if (ret.second) { - Log::debug() << "inconsistent leave thread"; // will probably set to trace sooner or - // later + next_cctx_ref_++; } - current_thread_cctx_refs_ = nullptr; + + return ret.first->second.ref; + } + + size_t num_cctx() const + { + return ref_count_; + } + + const std::map>& get_threads() const + { + return thread_cctxs_; + } + + const std::map>& get_functions() const + { + return map; + } + + otf2::writer::local* writer() + { + return writer_; } private: - trace::IpRefMap::iterator find_ip_child(Address addr, trace::IpRefMap& children) + std::map::iterator find_ip_child(Address addr, + std::map& children) { // -1 can't be inserted into the ip map, as it imples a 1-byte region from -1 to 0. if (addr == -1) @@ -147,9 +170,17 @@ class CallingContextManager } private: - trace::ThreadCctxRefMap& local_cctx_refs_; + std::map> map; + std::map> thread_cctxs_; + + /* + * Stores calling context information for each sample writer / monitoring thread. + * While the `Trace` always owns this data, the `sample::Writer` should have exclusive access to + * this data during its lifetime. Only afterwards, the `writer` and `refcount` are set by the + * `sample::Writer`. + */ + std::atomic writer_ = nullptr; + std::atomic ref_count_; size_t next_cctx_ref_ = 0; - trace::ThreadCctxRefMap::value_type* current_thread_cctx_refs_ = nullptr; }; -} // namespace perf } // namespace lo2s diff --git a/include/lo2s/perf/sample/reader.hpp b/include/lo2s/perf/sample/reader.hpp index ffec9d92..36ed89ed 100644 --- a/include/lo2s/perf/sample/reader.hpp +++ b/include/lo2s/perf/sample/reader.hpp @@ -109,6 +109,7 @@ class Reader : public EventReader perf_attr.config1 = sampling_event.config1; perf_attr.mmap = 1; + perf_attr.mmap2 = 1; } else { diff --git a/include/lo2s/perf/sample/writer.hpp b/include/lo2s/perf/sample/writer.hpp index 784c648a..b589dbac 100644 --- a/include/lo2s/perf/sample/writer.hpp +++ b/include/lo2s/perf/sample/writer.hpp @@ -22,7 +22,6 @@ #pragma once #include -#include #include #include @@ -64,7 +63,7 @@ class Writer : public Reader public: using Reader::handle; bool handle(const Reader::RecordSampleType* sample); - bool handle(const Reader::RecordMmapType* mmap_event); + bool handle(const Reader::RecordMmap2Type* mmap_event); bool handle(const Reader::RecordCommType* comm); bool handle(const Reader::RecordSwitchCpuWideType* context_switch); bool handle(const Reader::RecordSwitchType* context_switch); @@ -76,7 +75,7 @@ class Writer : public Reader void update_calling_context(Process process, Thread thread, otf2::chrono::time_point tp, bool switch_out); - void leave_current_thread(Thread thread, otf2::chrono::time_point tp); + void leave_current_thread(Process process, Thread thread, otf2::chrono::time_point tp); otf2::chrono::time_point adjust_timepoints(otf2::chrono::time_point tp); ExecutionScope scope_; @@ -89,8 +88,9 @@ class Writer : public Reader otf2::definition::metric_instance cpuid_metric_instance_; otf2::event::metric cpuid_metric_event_; - CallingContextManager cctx_manager_; + LocalCctxMap& local_cctx_map_; RawMemoryMapCache cached_mmap_events_; + RawCommCache cached_comm_events_; std::unordered_map comms_; const time::Converter time_converter_; @@ -98,6 +98,8 @@ class Writer : public Reader bool first_event_ = true; otf2::chrono::time_point first_time_point_; otf2::chrono::time_point last_time_point_; + Process cur_process_ = Process::invalid(); + Thread cur_thread_ = Thread::invalid(); }; } // namespace sample } // namespace perf diff --git a/include/lo2s/perf/types.hpp b/include/lo2s/perf/types.hpp index 6d560ee6..79188855 100644 --- a/include/lo2s/perf/types.hpp +++ b/include/lo2s/perf/types.hpp @@ -43,9 +43,9 @@ class PerfEventCache PerfEventCache(const PerfEventCache&) = delete; PerfEventCache& operator=(const PerfEventCache&) = delete; - PerfEventCache(const T* event, size_t size) : data_(std::make_unique(size)) + PerfEventCache(const T* event) : data_(std::make_unique(event->header.size)) { - memcpy(data_.get(), event, size); + memcpy(data_.get(), event, event->header.size); } T* get() diff --git a/include/lo2s/trace/trace.hpp b/include/lo2s/trace/trace.hpp index 9075292e..779b76b2 100644 --- a/include/lo2s/trace/trace.hpp +++ b/include/lo2s/trace/trace.hpp @@ -22,11 +22,10 @@ #include "otf2xx/definition/calling_context.hpp" #include #include -#include #include #include #include -#include +#include #include #include #include @@ -48,58 +47,16 @@ namespace trace { class MainMonitor; -template -using IpMap = std::map; - -struct IpRefEntry +struct GlobalCctx { - IpRefEntry(otf2::definition::calling_context::reference_type r) : ref(r) - { - } - - otf2::definition::calling_context::reference_type ref; - IpMap children; -}; - -struct ThreadCctxRefs -{ - ThreadCctxRefs(Process p, otf2::definition::calling_context::reference_type r) - : process(p), entry(r) - { - } - - Process process; - IpRefEntry entry; -}; - -struct IpCctxEntry -{ - IpCctxEntry(otf2::definition::calling_context& c) : cctx(c) + GlobalCctx(otf2::definition::calling_context& c) : cctx(c) { } otf2::definition::calling_context& cctx; - IpMap children; -}; - -/* - * Stores calling context information for each sample writer / monitoring thread. - * While the `Trace` always owns this data, the `sample::Writer` should have exclusive access to - * this data during its lifetime. Only afterwards, the `writer` and `refcount` are set by the - * `sample::Writer`. - */ -struct ThreadCctxRefMap -{ - std::map map; - std::atomic writer = nullptr; - std::atomic ref_count; - - using value_type = std::map::value_type; + std::map children; }; -using IpRefMap = IpMap; -using IpCctxMap = IpMap; - class Trace { public: @@ -123,11 +80,10 @@ class Trace void update_process_name(Process p, const std::string& name); void update_thread_name(Thread t, const std::string& name); - ThreadCctxRefMap& create_cctx_refs(); - otf2::definition::mapping_table - merge_calling_contexts(const std::map& new_ips, size_t num_ip_refs, - const std::map& infos); - void merge_calling_contexts(const std::map& process_infos); + LocalCctxMap& create_local_cctx_map(); + otf2::definition::mapping_table merge_calling_contexts(const LocalCctxMap& local_cctxs, + ProcessMap& infos); + void merge_calling_contexts(ProcessMap& process_infos); otf2::definition::mapping_table merge_syscall_contexts(const std::set& used_syscalls); @@ -342,9 +298,9 @@ class Trace void add_thread_exclusive(Thread thread, const std::string& name, const std::lock_guard&); - void merge_ips(const IpRefMap& new_children, IpCctxMap& children, - std::vector& mapping_table, otf2::definition::calling_context& parent, - const std::map& infos, Process p); + void merge_ips(const std::map& new_children, + std::map& children, std::vector& mapping_table, + otf2::definition::calling_context& parent, ProcessMap& infos, Process p); const otf2::definition::system_tree_node bio_parent_node(BlockDevice& device) { @@ -392,7 +348,9 @@ class Trace // TODO add location groups (processes), read path from /proc/self/exe symlink std::map thread_names_; - std::map calling_context_tree_; + + std::map global_thread_cctxs_; + std::map> calling_context_tree_; otf2::definition::comm_locations_group& comm_locations_group_; otf2::definition::comm_locations_group& hardware_comm_locations_group_; @@ -413,11 +371,11 @@ class Trace ExecutionScopeGroup& groups_; - std::deque cctx_refs_; + std::deque local_cctx_maps_; // Mutex is only used for accessing the cctx_refs_ - std::mutex cctx_refs_mutex_; + std::mutex local_cctx_maps_mutex_; // I wanted to use atomic_flag, but I need test and that's a C++20 exclusive. - std::atomic_bool cctx_refs_finalized_ = false; + std::atomic_bool local_cctx_maps_finalized_ = false; }; } // namespace trace } // namespace lo2s diff --git a/src/monitor/cpu_set_monitor.cpp b/src/monitor/cpu_set_monitor.cpp index 44fb8586..d269e21f 100644 --- a/src/monitor/cpu_set_monitor.cpp +++ b/src/monitor/cpu_set_monitor.cpp @@ -58,8 +58,7 @@ CpuSetMonitor::CpuSetMonitor() : MainMonitor() { pid = std::stol(pid_match[1]); - process_infos_.emplace(std::piecewise_construct, std::forward_as_tuple(pid), - std::forward_as_tuple(Process(pid), false)); + process_infos_.insert(Process(pid), 0, true); } } } diff --git a/src/monitor/main_monitor.cpp b/src/monitor/main_monitor.cpp index e2a168fe..af631709 100644 --- a/src/monitor/main_monitor.cpp +++ b/src/monitor/main_monitor.cpp @@ -128,20 +128,47 @@ MainMonitor::MainMonitor() : trace_(), metrics_(trace_) #endif } -void MainMonitor::insert_cached_mmap_events(const RawMemoryMapCache& cached_events) +static uint64_t mmap_get_time(const RecordMmap2Type* t) { - for (auto& event : cached_events) + struct sample_id* id = + (struct sample_id*)((char*)t + t->header.size - sizeof(struct sample_id)); + return id->time; +} + +static uint64_t comm_get_time(const RecordCommType* t) +{ + struct sample_id* id = + (struct sample_id*)((char*)t + t->header.size - sizeof(struct sample_id)); + return id->time; +} + +void MainMonitor::insert_cached_events(const RawMemoryMapCache& cached_mmaps, + const RawCommCache& cached_execs) +{ + for (auto& event : cached_execs) { - auto process_info = - process_infos_.emplace(std::piecewise_construct, std::forward_as_tuple(event.process), - std::forward_as_tuple(event.process, true)); - process_info.first->second.mmap(event); + process_infos_.insert(Process(event.get()->pid), comm_get_time(event.get()), false); + } + for (auto& event : cached_mmaps) + { + const uint64_t timestamp = mmap_get_time(event.get()); + Process p = Process(event.get()->pid); + + if (!process_infos_.has(p, timestamp)) + { + process_infos_.insert(p, timestamp, false); + } + + ProcessInfo& pinfo = process_infos_.get(p, timestamp); + + pinfo.mmap(*event.get()); } } MainMonitor::~MainMonitor() { // Note: call stop() in reverse order than start() in constructor + // #ifdef HAVE_SENSORS if (config().use_sensors) diff --git a/src/monitor/process_monitor.cpp b/src/monitor/process_monitor.cpp index f4d66047..a5f5bf9c 100644 --- a/src/monitor/process_monitor.cpp +++ b/src/monitor/process_monitor.cpp @@ -48,7 +48,7 @@ void ProcessMonitor::insert_thread(Process process, Thread thread, std::string n if (config().sampling) { - process_infos_.try_emplace(process, process, spawn); + process_infos_.insert(process, 0, !spawn); } if (config().sampling || diff --git a/src/perf/sample/writer.cpp b/src/perf/sample/writer.cpp index 64e56d7c..924384fe 100644 --- a/src/perf/sample/writer.cpp +++ b/src/perf/sample/writer.cpp @@ -58,7 +58,8 @@ Writer::Writer(ExecutionScope scope, monitor::MainMonitor& Monitor, trace::Trace otf2_writer_(trace.sample_writer(scope)), cpuid_metric_instance_(trace.metric_instance(trace.cpuid_metric_class(), otf2_writer_.location(), otf2_writer_.location())), - cpuid_metric_event_(otf2::chrono::genesis(), cpuid_metric_instance_), cctx_manager_(trace), + cpuid_metric_event_(otf2::chrono::genesis(), cpuid_metric_instance_), + local_cctx_map_(trace.create_local_cctx_map()), time_converter_(perf::time::Converter::instance()), first_time_point_(lo2s::time::now()), last_time_point_(first_time_point_) { @@ -66,13 +67,13 @@ Writer::Writer(ExecutionScope scope, monitor::MainMonitor& Monitor, trace::Trace Writer::~Writer() { - if (!cctx_manager_.current().is_undefined()) + if (cur_thread_ != Thread::invalid()) { otf2_writer_.write_calling_context_leave(adjust_timepoints(lo2s::time::now()), - cctx_manager_.current()); + local_cctx_map_.thread(cur_process_, cur_thread_)); } - cctx_manager_.finalize(&otf2_writer_); + local_cctx_map_.finalize(&otf2_writer_); } bool Writer::handle(const Reader::RecordSampleType* sample) @@ -84,19 +85,20 @@ bool Writer::handle(const Reader::RecordSampleType* sample) if (!has_cct_) { - otf2_writer_.write_calling_context_sample(tp, cctx_manager_.sample_ref(sample->ip), 2, - trace_.interrupt_generator().ref()); + otf2_writer_.write_calling_context_sample( + tp, local_cctx_map_.sample_ref(Process(sample->pid), sample->ip), 2, + trace_.interrupt_generator().ref()); } else { - otf2_writer_.write_calling_context_sample(tp, - cctx_manager_.sample_ref(sample->nr, sample->ips), - sample->nr, trace_.interrupt_generator().ref()); + otf2_writer_.write_calling_context_sample( + tp, local_cctx_map_.sample_ref(Process(sample->pid), sample->nr, sample->ips), + sample->nr, trace_.interrupt_generator().ref()); } return false; } -bool Writer::handle(const Reader::RecordMmapType* mmap_event) +bool Writer::handle(const Reader::RecordMmap2Type* mmap_event) { // Since this is an mmap record (as opposed to mmap2), it will only be generated for executable if (!scope_.is_cpu() && scope_ != ExecutionScope(Thread(mmap_event->tid))) @@ -119,23 +121,23 @@ void Writer::update_current_thread(Process process, Thread thread, otf2::chrono: otf2_writer_ << otf2::event::thread_begin(tp, trace_.process_comm(scope_.as_thread()), -1); first_event_ = false; } - if (!cctx_manager_.thread_changed(thread)) + if (thread == cur_thread_) { return; } - else if (!cctx_manager_.current().is_undefined()) + else if (cur_thread_ != Thread::invalid()) { - leave_current_thread(thread, tp); + leave_current_thread(process, thread, tp); } - cctx_manager_.thread_enter(process, thread); - otf2_writer_.write_calling_context_enter(tp, cctx_manager_.current(), 2); + otf2_writer_.write_calling_context_enter(tp, local_cctx_map_.thread(process, thread), 2); } -void Writer::leave_current_thread(Thread thread, otf2::chrono::time_point tp) +void Writer::leave_current_thread(Process process, Thread thread, otf2::chrono::time_point tp) { - otf2_writer_.write_calling_context_leave(tp, cctx_manager_.current()); - cctx_manager_.thread_leave(thread); + otf2_writer_.write_calling_context_leave(tp, local_cctx_map_.thread(process, thread)); + cur_process_ = Process::invalid(); + cur_thread_ = Thread::invalid(); } otf2::chrono::time_point Writer::adjust_timepoints(otf2::chrono::time_point tp) @@ -186,12 +188,12 @@ void Writer::update_calling_context(Process process, Thread thread, otf2::chrono { if (switch_out) { - if (cctx_manager_.current().is_undefined()) + if (cur_thread_ == Thread::invalid()) { Log::debug() << "Leave event but not in a thread!"; return; } - leave_current_thread(thread, tp); + leave_current_thread(process, thread, tp); } else { @@ -217,6 +219,11 @@ bool Writer::handle(const Reader::RecordCommType* comm) trace_.update_process_name(Process(comm->pid), new_command); } } + + if (comm->header.misc & PERF_RECORD_MISC_COMM_EXEC) + { + cached_comm_events_.emplace_back(comm); + } summary().register_process(Process(comm->pid)); comms_[Thread(comm->tid)] = comm->comm; @@ -252,7 +259,7 @@ void Writer::end() trace_.add_threads(comms_); - monitor_.insert_cached_mmap_events(cached_mmap_events_); + monitor_.insert_cached_events(cached_mmap_events_, cached_comm_events_); } } // namespace sample } // namespace perf diff --git a/src/trace/trace.cpp b/src/trace/trace.cpp index a3f7ecba..dffe96d3 100644 --- a/src/trace/trace.cpp +++ b/src/trace/trace.cpp @@ -22,10 +22,8 @@ #include #include -#include #include #include -#include #include #include #include @@ -230,7 +228,7 @@ otf2::chrono::time_point Trace::record_to() const Trace::~Trace() { - if (!cctx_refs_finalized_) + if (!local_cctx_maps_finalized_) { Log::error() << "cctx refs have not been finalized, please report this bug to the developers"; @@ -611,10 +609,9 @@ otf2::definition::metric_class& Trace::metric_class() otf2::common::recorder_kind::abstract); } -void Trace::merge_ips(const IpRefMap& new_children, IpCctxMap& children, - std::vector& mapping_table, - otf2::definition::calling_context& parent, - const std::map& infos, Process process) +void Trace::merge_ips(const std::map& new_children, + std::map& children, std::vector& mapping_table, + otf2::definition::calling_context& parent, ProcessMap& infos, Process process) { for (const auto& elem : new_children) { @@ -623,11 +620,14 @@ void Trace::merge_ips(const IpRefMap& new_children, IpCctxMap& children, auto& local_children = elem.second.children; LineInfo line_info = LineInfo::for_unknown_function(); - auto info_it = infos.find(process); - if (info_it != infos.end()) + if (infos.has(process, UINT64_MAX)) { - MemoryMap maps = info_it->second.maps(); - line_info = maps.lookup_line_info(ip); + line_info = infos.get(process, UINT64_MAX).lookup_line_info(ip); + } + else + { + infos.insert(process, 0, false); + line_info = infos.get(process, UINT64_MAX).lookup_line_info(ip); } Log::trace() << "resolved " << ip << ": " << line_info; @@ -639,11 +639,11 @@ void Trace::merge_ips(const IpRefMap& new_children, IpCctxMap& children, auto r = children.emplace(ip, new_cctx); cctx_it = r.first; - if (config().disassemble && infos.count(process) == 1) + if (config().disassemble && infos.has(process, UINT64_MAX) == 1) { try { - auto instruction = infos.at(process).maps().lookup_instruction(ip); + auto instruction = infos.get(process, UINT64_MAX).lookup_instruction(ip); Log::trace() << "mapped " << ip << " to " << instruction; registry_.create( @@ -663,64 +663,71 @@ void Trace::merge_ips(const IpRefMap& new_children, IpCctxMap& children, } } -otf2::definition::mapping_table -Trace::merge_calling_contexts(const std::map& new_ips, size_t num_ip_refs, - const std::map& infos) +otf2::definition::mapping_table Trace::merge_calling_contexts(const LocalCctxMap& local_cctxs, + ProcessMap& infos) { std::lock_guard guard(mutex_); #ifndef NDEBUG - std::vector mappings(num_ip_refs, -1u); + std::vector mappings(local_cctxs.num_cctx(), -1u); #else - std::vector mappings(num_ip_refs); + std::vector mappings(local_cctxs.num_cctx()); #endif // Merge local thread tree into global thread tree - for (auto& local_thread_cctx : new_ips) + for (auto& process_map : local_cctxs.get_threads()) { + Process process = process_map.first; - auto thread = local_thread_cctx.first; - auto process = local_thread_cctx.second.process; + for (auto& local_thread_cctx : process_map.second) + { + Thread thread = local_thread_cctx.first; + groups_.add_thread(thread, process); - groups_.add_thread(thread, process); - auto local_ref = local_thread_cctx.second.entry.ref; + auto local_ref = local_thread_cctx.second; - auto global_thread_cctx = calling_context_tree_.find(thread); + auto global_thread_cctx = global_thread_cctxs_.find(thread); - if (global_thread_cctx == calling_context_tree_.end()) - { - if (thread != Thread(0)) + if (global_thread_cctx == global_thread_cctxs_.end()) { - - if (auto thread_name = thread_names_.find(thread); - thread_name != thread_names_.end()) + if (thread != Thread(0)) { - add_thread(thread, thread_name->second); - } - else - { - if (auto process_name = thread_names_.find(process.as_thread()); - process_name != thread_names_.end()) + + if (auto thread_name = thread_names_.find(thread); + thread_name != thread_names_.end()) { - add_thread(thread, process_name->second); + add_thread(thread, thread_name->second); } else { - add_thread(thread, ""); + if (auto process_name = thread_names_.find(process.as_thread()); + process_name != thread_names_.end()) + { + add_thread(thread, process_name->second); + } + else + { + add_thread(thread, ""); + } } } + else + { + add_thread(thread, ""); + } } - else - { - add_thread(thread, ""); - } - global_thread_cctx = calling_context_tree_.find(thread); + const auto& foo = global_thread_cctxs_.at(thread).cctx.ref(); + mappings.at(local_ref.ref) = foo; } + } - assert(global_thread_cctx != calling_context_tree_.end()); - mappings.at(local_ref) = global_thread_cctx->second.cctx.ref(); - - merge_ips(local_thread_cctx.second.entry.children, global_thread_cctx->second.children, - mappings, global_thread_cctx->second.cctx, infos, process); + for (auto& local_process_map : local_cctxs.get_functions()) + { + auto& parent = registry_.get( + ByThread(local_process_map.first.as_thread())); + auto ret = + calling_context_tree_.emplace(local_process_map.first, std::map()); + merge_ips(local_process_map.second, ret.first->second, mappings, parent, infos, + local_process_map.first); } #ifndef NDEBUG @@ -785,9 +792,8 @@ void Trace::add_thread_exclusive(Thread thread, const std::string& name, // create calling context auto& thread_cctx = registry_.create( ByThread(thread), thread_region, otf2::definition::source_code_location()); - - calling_context_tree_.emplace(std::piecewise_construct, std::forward_as_tuple(thread), - std::forward_as_tuple(thread_cctx)); + global_thread_cctxs_.emplace(std::piecewise_construct, std::forward_as_tuple(thread), + std::forward_as_tuple(thread_cctx)); } void Trace::add_thread(Thread thread, const std::string& name) @@ -817,8 +823,8 @@ void Trace::add_monitoring_thread(Thread thread, const std::string& name, const auto& lo2s_cctx = registry_.create( ByThread(thread), ret, otf2::definition::source_code_location()); - calling_context_tree_.emplace(std::piecewise_construct, std::forward_as_tuple(thread), - std::forward_as_tuple(lo2s_cctx)); + global_thread_cctxs_.emplace(std::piecewise_construct, std::forward_as_tuple(thread), + std::forward_as_tuple(lo2s_cctx)); } } @@ -873,28 +879,28 @@ const otf2::definition::string& Trace::intern(const std::string& name) return registry_.emplace(ByString(name), name); } -ThreadCctxRefMap& Trace::create_cctx_refs() +LocalCctxMap& Trace::create_local_cctx_map() { - std::lock_guard guard(cctx_refs_mutex_); + std::lock_guard guard(local_cctx_maps_mutex_); - assert(!cctx_refs_finalized_); + assert(!local_cctx_maps_finalized_); - return cctx_refs_.emplace_back(); + return local_cctx_maps_.emplace_back(); } -void Trace::merge_calling_contexts(const std::map& process_infos) +void Trace::merge_calling_contexts(ProcessMap& process_infos) { - for (auto& cctx : cctx_refs_) + for (auto& local_cctx : local_cctx_maps_) { - assert(cctx.writer != nullptr); - if (cctx.ref_count > 0) + assert(local_cctx.writer != nullptr); + if (local_cctx.num_cctx() > 0) { - const auto& mapping = merge_calling_contexts(cctx.map, cctx.ref_count, process_infos); - (*cctx.writer) << mapping; + const auto& mapping = merge_calling_contexts(local_cctx, process_infos); + (*local_cctx.writer()) << mapping; } } - cctx_refs_.clear(); - auto finalized_twice = cctx_refs_finalized_.exchange(true); + local_cctx_maps_.clear(); + auto finalized_twice = local_cctx_maps_finalized_.exchange(true); if (finalized_twice) { Log::error() << "Trace::merge_calling_contexts was called twice." From eda8d33477946842a3ff6437551989d564670798 Mon Sep 17 00:00:00 2001 From: Christian von Elm Date: Thu, 7 Nov 2024 13:13:10 +0100 Subject: [PATCH 5/6] Replace BFD -> libelf, merge MMap class and ProcessInfo, split function and instruction resolution. -> split function resolution and instruction resolution -> integrate mmap code into ProcessInfo The mmap code is not used outside of ProcessInfo and every function in ProcessInfo was already a paper thin wrapper around something in Mmap -> replace binutils/libbfd with elfutils/libdw/libelf - API usable by human beings - Actual documentation - Has support for DWARF --- .github/workflows/cmake.yml | 2 +- CMakeLists.txt | 10 + include/lo2s/config.hpp | 8 + include/lo2s/dwarf_resolve.hpp | 43 ++++ include/lo2s/function_resolver.hpp | 130 ++++++++++++ include/lo2s/instruction_resolver.hpp | 72 +++++++ include/lo2s/mmap.hpp | 292 -------------------------- include/lo2s/perf/types.hpp | 6 +- include/lo2s/process_info.hpp | 65 ++++-- src/config.cpp | 35 +++ src/dwarf_resolve.cpp | 141 +++++++++++++ src/mmap.cpp | 185 ---------------- src/process_info.cpp | 231 ++++++++++++++++++++ src/trace/trace.cpp | 2 +- 14 files changed, 724 insertions(+), 498 deletions(-) create mode 100644 include/lo2s/dwarf_resolve.hpp create mode 100644 include/lo2s/function_resolver.hpp create mode 100644 include/lo2s/instruction_resolver.hpp delete mode 100644 include/lo2s/mmap.hpp create mode 100644 src/dwarf_resolve.cpp delete mode 100644 src/mmap.cpp create mode 100644 src/process_info.cpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d71bb2a1..6379f653 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,7 +22,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install binutils-dev libiberty-dev libsensors-dev + sudo apt-get install libelf-dev libdw-dev libsensors-dev sudo pip install git-archive-all - name: Cache OTF2 id: cache-otf2 diff --git a/CMakeLists.txt b/CMakeLists.txt index f7a91def..85b9134f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ if(NOT HAVE_PERF_RECORD_SWITCH) message(FATAL_ERROR "lo2s requires support for perf context switch recording. Make sure that you are running on a kernel that support context_switch with perf_event_open") endif() + # detect version of running kernel string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LINUX_VERSION ${CMAKE_SYSTEM_VERSION}) @@ -206,6 +207,7 @@ set(SOURCE_FILES src/config.cpp src/main.cpp src/monitor/process_monitor.cpp src/platform.cpp + src/process_info.cpp src/topology.cpp src/pipe.cpp src/dwarf_resolve.cpp src/util.cpp src/perf/util.cpp @@ -232,6 +234,14 @@ target_link_libraries(lo2s debuginfod ) +find_path(DEBUGINFOD_INCLUDE_DIRS elfutils/debuginfod.h + PATHS ENV C_INCLUDE_PATH ENV CPATH + PATH_SUFFIXES include) + +if(DEBUGINFOD_INCLUDE_DIRS) + target_compile_definitions(lo2s PUBLIC HAVE_DEBUGINFOD) +endif() + # old glibc versions require -lrt for clock_gettime() if(NOT CLOCK_GETTIME_FOUND) if(CLOCK_GETTIME_FOUND_WITH_RT) diff --git a/include/lo2s/config.hpp b/include/lo2s/config.hpp index 53fd6f36..94627d70 100644 --- a/include/lo2s/config.hpp +++ b/include/lo2s/config.hpp @@ -44,6 +44,13 @@ enum class MonitorType CPU_SET }; +enum class DwarfUsage +{ + NONE, + LOCAL, + FULL +}; + struct Config { // General @@ -102,6 +109,7 @@ struct Config bool use_nvidia; std::string cuda_injectionlib_path; uint64_t nvidia_ringbuf_size; + DwarfUsage dwarf; }; const Config& config(); diff --git a/include/lo2s/dwarf_resolve.hpp b/include/lo2s/dwarf_resolve.hpp new file mode 100644 index 00000000..cc56bdba --- /dev/null +++ b/include/lo2s/dwarf_resolve.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +#include +extern "C" +{ +#include +#include +#include +#include +} + +namespace lo2s +{ +class DwarfFunctionResolver : public FunctionResolver +{ +public: + DwarfFunctionResolver(std::string name); + + static FunctionResolver& cache(std::string name) + { + return StringCache::instance()[name]; + } + + ~DwarfFunctionResolver(); + virtual LineInfo lookup_line_info(Address addr) override; + + std::string name() + { + return name_; + } + +private: + std::map cache_; + Dwfl_Callbacks cb; + Dwfl* dwfl_ = nullptr; + Dwfl_Module* mod_ = nullptr; + std::string name_; +}; +} // namespace lo2s diff --git a/include/lo2s/function_resolver.hpp b/include/lo2s/function_resolver.hpp new file mode 100644 index 00000000..bd05256d --- /dev/null +++ b/include/lo2s/function_resolver.hpp @@ -0,0 +1,130 @@ +/* + * This file is part of the lo2s software. + * Linux OTF2 sampling + * + * Copyright (c) 2024, + * Technische Universitaet Dresden, Germany + * + * lo2s is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lo2s is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lo2s. If not, see . + */ + +#pragma once + +#include +#include + +#include +#include + +namespace lo2s +{ +class FunctionResolver +{ +public: + FunctionResolver(const std::string& name) : name_(name) + { + } + + static FunctionResolver& cache(const std::string& name) + { + return StringCache::instance()[name]; + } + + virtual LineInfo lookup_line_info(Address) + { + return LineInfo::for_binary(name_); + } + + std::string name() + { + return name_; + } + +protected: + std::string name_; +}; + +class Kallsyms : public FunctionResolver +{ +public: + Kallsyms() : FunctionResolver("[kernel]") + { + std::map entries; + std::ifstream ksyms_file("/proc/kallsyms"); + + std::regex ksym_regex("([0-9a-f]+) (?:t|T) ([^[:space:]]+)"); + std::smatch ksym_match; + + std::string line; + + // Emplacing into entries map takes care of sorting symbols by address + while (getline(ksyms_file, line)) + { + if (std::regex_match(line, ksym_match, ksym_regex)) + { + std::string sym_str = ksym_match[2]; + uint64_t sym_addr = stoull(ksym_match[1], nullptr, 16); + entries.emplace(std::piecewise_construct, std::forward_as_tuple(sym_addr), + std::forward_as_tuple(sym_str)); + } + } + + std::string sym_str = ""; + Address prev = 0; + + for (auto& entry : entries) + { + if (prev != 0 && prev != entry.first) + { + kallsyms_.emplace(std::piecewise_construct, + std::forward_as_tuple(prev, entry.first), + std::forward_as_tuple(sym_str)); + } + else + { + start_ = entry.first.value(); + } + sym_str = entry.second; + prev = entry.first; + } + + if (sym_str != "") + { + kallsyms_.emplace(std::piecewise_construct, + std::forward_as_tuple(prev, Address((uint64_t)-1)), + std::forward_as_tuple(sym_str)); + } + } + + static Kallsyms& cache() + { + static Kallsyms k; + return k; + } + + uint64_t start() + { + return start_; + } + + virtual LineInfo lookup_line_info(Address addr) override + { + return LineInfo::for_function("[kernel]", kallsyms_.at(addr + start_).c_str(), 1, ""); + } + +private: + std::map kallsyms_; + uint64_t start_; +}; +} diff --git a/include/lo2s/instruction_resolver.hpp b/include/lo2s/instruction_resolver.hpp new file mode 100644 index 00000000..c1d32971 --- /dev/null +++ b/include/lo2s/instruction_resolver.hpp @@ -0,0 +1,72 @@ +/* + * This file is part of the lo2s software. + * Linux OTF2 sampling + * + * Copyright (c) 2024, + * Technische Universitaet Dresden, Germany + * + * lo2s is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lo2s is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lo2s. If not, see . + */ + +#pragma once + +#include +#ifdef HAVE_RADARE +#include +#endif +#include + +#include +namespace lo2s +{ +class InstructionResolver +{ +public: + InstructionResolver() + { + } + + static InstructionResolver& cache() + { + static InstructionResolver ir; + return ir; + } + + virtual std::string lookup_instruction(Address) + { + return ""; + } +}; +#ifdef HAVE_RADARE +class RadareInstructionResolver : public InstructionResolver +{ +public: + RadareInstructionResolver(std::string name) : radare_(name) + { + } + + static RadareInstructionResolver& cache(const std::string& name) + { + return StringCache::instance()[name]; + } + + virtual std::string lookup_instruction(Address ip) + { + return radare_.instruction(ip); + } + + RadareResolver radare_; +}; +#endif // HAVE_RADARE +} // namespace lo2s diff --git a/include/lo2s/mmap.hpp b/include/lo2s/mmap.hpp deleted file mode 100644 index a8237e3f..00000000 --- a/include/lo2s/mmap.hpp +++ /dev/null @@ -1,292 +0,0 @@ -/* - * This file is part of the lo2s software. - * Linux OTF2 sampling - * - * Copyright (c) 2016, - * Technische Universitaet Dresden, Germany - * - * lo2s is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * lo2s is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with lo2s. If not, see . - */ - -#pragma once - -#include -#include -#ifdef HAVE_RADARE -#include -#endif -#include -#include - -#include -#include -#include -#include -#include -#include - -extern "C" -{ -#include -} - -namespace lo2s -{ - -class Binary -{ -protected: - Binary(const std::string& name) : name_(name) - { - } - -public: - virtual ~Binary() - { - } - - virtual std::string lookup_instruction(Address) - { - throw std::runtime_error("lookup_instruction not implemented."); - }; - - virtual LineInfo lookup_line_info(Address ip) = 0; - - const std::string& name() const - { - return name_; - }; - -private: - std::string name_; -}; - -class NamedBinary : public Binary -{ -public: - NamedBinary(const std::string& name) : Binary(name) - { - } - - static Binary& cache(const std::string& name) - { - return StringCache::instance()[name]; - } - - virtual std::string lookup_instruction(Address) override - { - throw std::domain_error("Unknown instruction."); - } - - virtual LineInfo lookup_line_info(Address) override - { - return LineInfo::for_binary(name()); - } -}; - -/** - * TODO Split this... it's ugly - */ -class BfdRadareBinary : public Binary -{ -public: - BfdRadareBinary(const std::string& name) - : Binary(name), bfd_(name) -#ifdef HAVE_RADARE - , - radare_(name) -#endif - { - } - - static Binary& cache(const std::string& name) - { - return StringCache::instance()[name]; - } - -#ifdef HAVE_RADARE - virtual std::string lookup_instruction(Address ip) override - { - return radare_.instruction(ip); - } -#endif - - virtual LineInfo lookup_line_info(Address ip) override - { - try - { - return bfd_.lookup(ip); - } - catch (bfdr::LookupError&) - { - return LineInfo::for_unknown_function_in_dso(name()); - } - } - -private: - bfdr::Lib bfd_; -#ifdef HAVE_RADARE - RadareResolver radare_; -#endif // HAVE_RADARE -}; - -class Kallsyms : public Binary -{ -public: - Kallsyms() : Binary("[kernel]") - { - std::map entries; - std::ifstream ksyms_file("/proc/kallsyms"); - - std::regex ksym_regex("([0-9a-f]+) (?:t|T) ([^[:space:]]+)"); - std::smatch ksym_match; - - std::string line; - - // Emplacing into entries map takes care of sorting symbols by address - while (getline(ksyms_file, line)) - { - if (std::regex_match(line, ksym_match, ksym_regex)) - { - std::string sym_str = ksym_match[2]; - uint64_t sym_addr = stoull(ksym_match[1], nullptr, 16); - entries.emplace(std::piecewise_construct, std::forward_as_tuple(sym_addr), - std::forward_as_tuple(sym_str)); - } - } - - std::string sym_str = ""; - Address prev = 0; - - for (auto& entry : entries) - { - if (prev != 0 && prev != entry.first) - { - kallsyms_.emplace(std::piecewise_construct, - std::forward_as_tuple(prev, entry.first), - std::forward_as_tuple(sym_str)); - } - else - { - start_ = entry.first.value(); - } - sym_str = entry.second; - prev = entry.first; - } - - if (sym_str != "") - { - kallsyms_.emplace(std::piecewise_construct, - std::forward_as_tuple(prev, Address((uint64_t)-1)), - std::forward_as_tuple(sym_str)); - } - } - - static Kallsyms& cache() - { - static Kallsyms k; - return k; - } - - uint64_t start() - { - return start_; - } - - virtual std::string lookup_instruction(Address) override - { - throw std::domain_error("Unknown instruction."); - } - - virtual LineInfo lookup_line_info(Address addr) override - { - return LineInfo::for_function("[kernel]", kallsyms_.at(addr + start_).c_str(), 1, ""); - } - -private: - std::map kallsyms_; - uint64_t start_; -}; - -struct RecordMmapType -{ - // BAD things happen if you try this - RecordMmapType() = delete; - RecordMmapType(const RecordMmapType&) = delete; - RecordMmapType& operator=(const RecordMmapType&) = delete; - RecordMmapType(RecordMmapType&&) = delete; - RecordMmapType& operator=(RecordMmapType&&) = delete; - - struct perf_event_header header; - uint32_t pid, tid; - uint64_t addr; - uint64_t len; - uint64_t pgoff; - // Note ISO C++ forbids zero-size array, but this struct is exclusively used as pointer - char filename[1]; - // struct sample_id sample_id; -}; - -struct RawMemoryMapEntry -{ - RawMemoryMapEntry(Address addr, Address end, Address pgoff, const std::string& filename) - : process(0), thread(0), addr(addr), end(end), pgoff(pgoff), filename(filename) - { - } - - RawMemoryMapEntry(const RecordMmapType* record) - : process(record->pid), thread(record->tid), addr(record->addr), - end(record->addr + record->len), pgoff(record->pgoff), filename(record->filename) - { - } - - Process process; - Thread thread; - Address addr; - Address end; - Address pgoff; - std::string filename; -}; - -using RawMemoryMapCache = std::deque; - -class MemoryMap -{ -public: - MemoryMap(); - MemoryMap(Process p, bool read_initial); - - void mmap(const RawMemoryMapEntry& entry); - - LineInfo lookup_line_info(Address ip) const; - - // Will throw alot - catch it if you can - std::string lookup_instruction(Address ip) const; - -private: - struct Mapping - { - Mapping(Address s, Address e, Address o, Binary& d) : start(s), end(e), pgoff(o), dso(d) - { - } - - Address start; - Address end; - Address pgoff; - Binary& dso; - }; - - std::map map_; -}; -} // namespace lo2s diff --git a/include/lo2s/perf/types.hpp b/include/lo2s/perf/types.hpp index 79188855..64468c99 100644 --- a/include/lo2s/perf/types.hpp +++ b/include/lo2s/perf/types.hpp @@ -85,14 +85,14 @@ struct RecordMmapType * \brief structure for PERF_RECORD_COMM events **/ -struct __attribute((__packed__)) sample_id +struct sample_id { uint32_t pid, tid; uint64_t time; uint32_t cpu, res; }; -struct __attribute((__packed__)) RecordCommType +struct RecordCommType { struct perf_event_header header; uint32_t pid; @@ -101,7 +101,7 @@ struct __attribute((__packed__)) RecordCommType // struct sample_id id; }; -struct __attribute((__packed__)) RecordMmap2Type +struct RecordMmap2Type { // BAD things happen if you try this RecordMmap2Type() = delete; diff --git a/include/lo2s/process_info.hpp b/include/lo2s/process_info.hpp index 14004af2..53d586f0 100644 --- a/include/lo2s/process_info.hpp +++ b/include/lo2s/process_info.hpp @@ -22,10 +22,19 @@ #pragma once #include +#include +#include +#include #include -#include +#include -#include +#include + +#include + +#include +#include +#include #include namespace lo2s @@ -33,31 +42,55 @@ namespace lo2s class ProcessInfo { public: - ProcessInfo(Process p, bool enable_on_exec) : process_(p), maps_(p, !enable_on_exec) - { - } - + ProcessInfo(Process p, bool read_initial); Process process() const { return process_; } - void mmap(const RawMemoryMapEntry& entry) + void mmap(const RecordMmap2Type& entry) { - std::lock_guard lock(mutex_); - maps_.mmap(entry); + mmap(entry.addr, entry.addr + entry.len, entry.pgoff, entry.filename); } - MemoryMap maps() const + void mmap(Address addr, Address end, Address pgoff, std::string filename); + + LineInfo lookup_line_info(Address ip); + std::string lookup_instruction(Address ip) const; + +private: + struct Mapping + { + Mapping(Address s, Address e, Address o, FunctionResolver& f, InstructionResolver& i) + : start(s), end(e), pgoff(o), fr(f), ir(i) + { + } + + Address start; + Address end; + Address pgoff; + FunctionResolver& fr; + InstructionResolver& ir; + }; + const Process process_; + mutable std::shared_mutex mutex_; + std::map map_; +}; + +class ProcessMap +{ +public: + ProcessMap() { - std::lock_guard lock(mutex_); - // Yes, this is correct as per 6.6.3 The Return Statement - return maps_; } + bool has(Process p, uint64_t timepoint); + + ProcessInfo& get(Process p, uint64_t timepoint); + + ProcessInfo& insert(Process p, uint64_t timepoint, bool read_initial); + private: - const Process process_; - mutable std::mutex mutex_; - MemoryMap maps_; + std::map> infos_; }; } // namespace lo2s diff --git a/src/config.cpp b/src/config.cpp index 55505876..5e31e296 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -228,6 +228,17 @@ void parse_program_options(int argc, const char** argv) general_options.toggle("list-knobs", "List available x86_adapt CPU knobs."); + general_options +#ifdef HAVE_DEBUGINFOD + .option("dwarf", + "Set DWARF resolve mode. none disables DWARF, local uses only locally cached " + "debuginfo, full uses debuginfod to download debug information on demand") + .default_value("full") +#else + .option("dwarf", "Set DWARF resolve mode. none disables DWARF, local enables DWARF support") + .default_value("local") +#endif + .metavar("DWARFMODE"); system_mode_options .toggle("all-cpus", "Start in system-monitoring mode for all CPUs. " "Monitor as long as COMMAND is running or until PID exits.") @@ -589,6 +600,30 @@ void parse_program_options(int argc, const char** argv) } } + std::string dwarf_mode = arguments.get("dwarf"); + + if (dwarf_mode == "full") + { +#ifdef HAVE_DEBUGINFOD + config.dwarf = DwarfUsage::FULL; +#else + Log::warn() << "No Debuginfod support available, downgrading to use only local files!"; + config.dwarf = DwarfUsage::LOCAL; +#endif + } + else if (dwarf_mode == "local") + { + config.dwarf = DwarfUsage::LOCAL; + } + else if (dwarf_mode == "none") + { + config.dwarf = DwarfUsage::NONE; + } + else + { + std::cerr << "Unknown DWARF mode: " << dwarf_mode << std::endl; + std::exit(EXIT_FAILURE); + } if (arguments.given("all-cpus") || arguments.given("all-cpus-sampling")) { config.monitor_type = lo2s::MonitorType::CPU_SET; diff --git a/src/dwarf_resolve.cpp b/src/dwarf_resolve.cpp new file mode 100644 index 00000000..5b922afd --- /dev/null +++ b/src/dwarf_resolve.cpp @@ -0,0 +1,141 @@ +#include +#include + +#ifdef HAVE_DEBUGINFOD +extern "C" +{ +#include +} +#endif + +namespace lo2s +{ + +struct getfuncs_arg +{ + Dwarf_Addr addr; + std::string name; +}; + +static int func_cb(Dwarf_Die* d, void* arg) +{ + struct getfuncs_arg* args = (struct getfuncs_arg*)arg; + if (dwarf_haspc(d, args->addr)) + { + const char* name = dwarf_diename(d); + if (name != nullptr) + { + args->name = name; + } + return DWARF_CB_ABORT; + } + return DWARF_CB_OK; +} + +static int dwfl_dummy_find_debuginfo(Dwfl_Module*, void**, const char*, GElf_Addr, const char*, + const char*, GElf_Word, char**) +{ + return -1; +} + +DwarfFunctionResolver::DwarfFunctionResolver(std::string name) : FunctionResolver(name) +{ + if (config().dwarf == DwarfUsage::FULL) + { + cb.find_debuginfo = dwfl_standard_find_debuginfo; + } + else if (config().dwarf == DwarfUsage::LOCAL) + { + cb.find_debuginfo = dwfl_build_id_find_debuginfo; + } + else + { + cb.find_debuginfo = dwfl_dummy_find_debuginfo; + } + cb.find_debuginfo = dwfl_build_id_find_debuginfo; + cb.debuginfo_path = nullptr; + cb.find_elf = dwfl_linux_proc_find_elf; + cb.section_address = dwfl_offline_section_address; + + dwfl_ = dwfl_begin(&cb); + if (dwfl_ == nullptr) + { + Log::error() << "Could not open dwfl session: " << dwfl_errmsg(dwfl_errno()); + throw std::runtime_error(dwfl_errmsg(dwfl_errno())); + } + +#ifdef HAVE_DEBUGINFOD + debuginfod_client* debug_c = dwfl_get_debuginfod_client(dwfl_); + if (debug_c != 0) + { + debuginfod_set_verbose_fd(debug_c, STDERR_FILENO); + } +#endif + + dwfl_report_begin(dwfl_); + + mod_ = dwfl_report_offline(dwfl_, name.c_str(), name.c_str(), -1); + if (mod_ == nullptr) + { + const char* errmsg = dwfl_errmsg(dwfl_errno()); + Log::error() << "Could not add " << name << " " << errmsg; + } + dwfl_report_end(dwfl_, NULL, NULL); +} + +DwarfFunctionResolver::~DwarfFunctionResolver() +{ + dwfl_end(dwfl_); +} + +LineInfo DwarfFunctionResolver::lookup_line_info(Address addr) +{ + if (cache_.count(addr)) + { + return cache_.at(addr); + } + const char* module_name = + dwfl_module_info(mod_, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + + if (config().dwarf != DwarfUsage::NONE) + { + Dwarf_Die* cudie = nullptr; + Dwarf_Addr bias; + while ((cudie = dwfl_module_nextcu(mod_, cudie, &bias)) != nullptr) + { + if (dwarf_haspc(cudie, addr.value())) + { + Dwarf_Line* line = dwarf_getsrc_die(cudie, addr.value()); + int lineno; + dwarf_lineno(line, &lineno); + const char* srcname = dwarf_linesrc(line, nullptr, nullptr); + + struct getfuncs_arg arg; + arg.addr = addr.value(); + arg.name = ""; + dwarf_getfuncs(cudie, func_cb, &arg, 0); + + return cache_ + .emplace(addr, + LineInfo::for_function(srcname, arg.name.c_str(), lineno, module_name)) + .first->second; + } + } + } + + GElf_Addr offset; + GElf_Sym sym; + const char* name = + dwfl_module_addrinfo(mod_, addr.value(), &offset, &sym, nullptr, nullptr, nullptr); + + if (name != nullptr) + { + return cache_ + .emplace(Range(sym.st_value, sym.st_value + sym.st_size), + LineInfo::for_function(module_name, name, 1, module_name)) + .first->second; + } + + return cache_.emplace(addr, LineInfo::for_binary(module_name)).first->second; +} +} // namespace lo2s diff --git a/src/mmap.cpp b/src/mmap.cpp deleted file mode 100644 index 72481452..00000000 --- a/src/mmap.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This file is part of the lo2s software. - * Linux OTF2 sampling - * - * Copyright (c) 2016, - * Technische Universitaet Dresden, Germany - * - * lo2s is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * lo2s is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with lo2s. If not, see . - */ - -#include -#include -#include - -#include - -#include - -#include -#include -#include - -namespace lo2s -{ - -MemoryMap::MemoryMap() -{ -} - -MemoryMap::MemoryMap(Process process, bool read_initial) -{ - Binary* kall = &Kallsyms::cache(); - map_.emplace(std::piecewise_construct, - std::forward_as_tuple(Kallsyms::cache().start(), (uint64_t)-1), - std::forward_as_tuple(Kallsyms::cache().start(), (uint64_t)-1, 0, *kall)); - - if (!read_initial) - { - return; - } - // Supposedly this one is faster than /proc/%d/maps for processes with many threads - auto filename = fmt::format("/proc/{}/task/{}/maps", process.as_pid_t(), process.as_pid_t()); - - std::ifstream mapstream(filename); - if (mapstream.fail()) - { - Log::error() << "could not open maps file " << filename; - // Gracefully return an initially empty map that always fails. - return; - } - std::string line; - - // start - end prot offset - // device inode dso - // NOTE: we only look at executable entries here ↓ - std::regex regex("([0-9a-f]+)\\-([0-9a-f]+)\\s+[r-][w-]x.?\\s+([0-9a-z]+)" - "\\s+\\S+\\s+\\d+\\s+(.*)"); - Log::debug() << "opening " << filename; - while (getline(mapstream, line)) - { - Log::trace() << "map entry: " << line; - std::smatch match; - if (std::regex_match(line, match, regex)) - { - RawMemoryMapEntry entry(Address(match.str(1)), Address(match.str(2)), - Address(match.str(3)), match.str(4)); - mmap(entry); - } - } -} - -void MemoryMap::mmap(const RawMemoryMapEntry& entry) -{ - Log::debug() << "mmap: " << entry.addr << "-" << entry.end << " " << entry.pgoff << ": " - << entry.filename; - - if (entry.filename.empty() || std::string("//anon") == entry.filename || - std::string("/dev/zero") == entry.filename || - std::string("/anon_hugepage") == entry.filename || - nitro::lang::starts_with(entry.filename, "/SYSV")) - { - Log::debug() << "mmap: skipping dso: " << entry.filename << " (known non-library)"; - return; - } - - bool is_non_file_dso = (entry.filename[0] == '['); - - Binary* lb; - if (is_non_file_dso) - { - lb = &NamedBinary::cache(entry.filename); - } - else - { - try - { - - lb = &BfdRadareBinary::cache(entry.filename); - } - catch (bfdr::InitError& e) - { - Log::warn() << "could not initialize bfd: " << e.what(); - lb = &NamedBinary::cache(entry.filename); - } - catch (bfdr::InvalidFileError& e) - { - Log::debug() << "dso is not a valid file: " << e.what(); - lb = &NamedBinary::cache(entry.filename); - } - } - - auto ex_it = map_.find(entry.addr); - if (ex_it != map_.end()) - { - // Overlapping with existing entry - auto ex_range = ex_it->first; - auto ex_elem = std::move(ex_it->second); - map_.erase(ex_it); - if (ex_range.start != entry.addr) - { - // Truncate entry - assert(ex_range.start < entry.addr); - ex_range.end = entry.addr; - Log::debug() << "truncating map entry at " << ex_range.start << " to " << ex_range.end; - [[maybe_unused]] auto r = map_.emplace(ex_range, std::move(ex_elem)); - assert(r.second); - } - } - - try - { - auto r = - map_.emplace(std::piecewise_construct, std::forward_as_tuple(entry.addr, entry.end), - std::forward_as_tuple(entry.addr, entry.end, entry.pgoff, *lb)); - if (!r.second) - { - // very common, so only debug - // TODO handle better - Log::debug() << "duplicate memory range from mmap event. new: " << entry.addr << "-" - << entry.end << "%" << entry.pgoff << " " << entry.filename << "\n" - << "OLD: " << r.first->second.start << "-" << r.first->second.end << "%" - << r.first->second.pgoff << " " << r.first->second.dso.name(); - } - } - catch (Range::Error& e) - { - // Very common, can't warn here - // TODO consider handling this somehow... - Log::debug() << "invalid address range in /proc/.../maps: " << e.what(); - } -} - -LineInfo MemoryMap::lookup_line_info(Address ip) const -{ - try - { - auto& mapping = map_.at(ip); - return mapping.dso.lookup_line_info(ip - mapping.start + mapping.pgoff); - } - catch (std::out_of_range&) - { - // This will just happen a lot in practice - Log::trace() << "no mapping found for address " << ip; - // Graceful fallback - return LineInfo::for_unknown_function(); - } -} - -std::string MemoryMap::lookup_instruction(Address ip) const -{ - auto& mapping = map_.at(ip); - return mapping.dso.lookup_instruction(ip - mapping.start + mapping.pgoff); -} -} // namespace lo2s diff --git a/src/process_info.cpp b/src/process_info.cpp new file mode 100644 index 00000000..400bfb50 --- /dev/null +++ b/src/process_info.cpp @@ -0,0 +1,231 @@ +/* + * This file is part of the lo2s software. + * Linux OTF2 sampling + * + * Copyright (c) 2024, + * Technische Universitaet Dresden, Germany + * + * lo2s is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lo2s is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lo2s. If not, see . + */ + +#include + +namespace lo2s +{ + +ProcessInfo::ProcessInfo(Process p, bool read_initial) : process_(p) +{ + FunctionResolver& kall = Kallsyms::cache(); + InstructionResolver& kernel_ir = InstructionResolver::cache(); + map_.emplace( + std::piecewise_construct, std::forward_as_tuple(Kallsyms::cache().start(), (uint64_t)-1), + std::forward_as_tuple(Kallsyms::cache().start(), (uint64_t)-1, 0, kall, kernel_ir)); + if (!read_initial) + { + return; + } + // Supposedly this one is faster than /proc/%d/maps for processes with many threads + auto filename = fmt::format("/proc/{}/task/{}/maps", p.as_pid_t(), p.as_pid_t()); + + std::ifstream mapstream(filename); + if (mapstream.fail()) + { + Log::error() << "could not open maps file " << filename; + // Gracefully return an initially empty map that always fails. + return; + } + + std::string line; + + // start - end prot offset + // device inode fr + // NOTE: we only look at executable entries here ↓ + std::regex regex("([0-9a-f]+)\\-([0-9a-f]+)\\s+[r-][w-][x-].?\\s+([0-9a-z]+)" + "\\s+\\S+\\s+\\d+\\s+(.*)"); + Log::debug() << "opening " << filename; + while (getline(mapstream, line)) + { + Log::trace() << "map entry: " << line; + std::smatch match; + if (std::regex_match(line, match, regex)) + { + mmap(Address(match.str(1)), Address(match.str(2)), Address(match.str(3)), match.str(4)); + } + } +} + +void ProcessInfo::mmap(Address addr, Address end, Address pgoff, std::string filename) +{ + + std::lock_guard lock(mutex_); + + Log::debug() << "mmap: " << addr << "-" << end << " " << pgoff << ": " << filename; + + if (filename.empty() || std::string("//anon") == filename || + std::string("/dev/zero") == filename || std::string("/anon_hugepage") == filename || + nitro::lang::starts_with(filename, "/memfd") || + nitro::lang::starts_with(filename, "/SYSV") || nitro::lang::starts_with(filename, "/dev")) + { + Log::debug() << "mmap: skipping fr: " << filename << " (known non-library)"; + return; + } + + FunctionResolver* fr; + InstructionResolver* ir; + + try + { + if (nitro::lang::starts_with(filename, "[")) + { + fr = &FunctionResolver::cache(filename); + } + else + { + fr = &DwarfFunctionResolver::cache(filename); + } + } + catch (...) + { + fr = &FunctionResolver::cache(filename); + } + +#ifdef HAVE_RADARE + try + { + ir = &RadareInstructionResolver::cache(filename); + } + catch (...) + { + ir = &InstructionResolver::cache(); + } +#else + ir = &InstructionResolver::cache(); +#endif + auto ex_it = map_.find(addr); + if (ex_it != map_.end()) + { + // Overlapping with existing entry + auto ex_range = ex_it->first; + auto ex_elem = std::move(ex_it->second); + map_.erase(ex_it); + if (ex_range.start != addr) + { + // Truncate entry + assert(ex_range.start < addr); + ex_range.end = addr; + Log::debug() << "truncating map entry at " << ex_range.start << " to " << ex_range.end; + [[maybe_unused]] auto r = map_.emplace(ex_range, std::move(ex_elem)); + assert(r.second); + } + } + + try + { + auto r = map_.emplace(std::piecewise_construct, std::forward_as_tuple(addr, end), + std::forward_as_tuple(addr, end, pgoff, *fr, *ir)); + if (!r.second) + { + // very common, so only debug + // TODO handle better + Log::debug() << "duplicate memory range from mmap event. new: " << addr << "-" << end + << "%" << pgoff << " " << filename << "\n" + << "OLD: " << r.first->second.start << "-" << r.first->second.end << "%" + << r.first->second.pgoff << " " << r.first->second.fr.name(); + } + } + catch (Range::Error& e) + { + // Very common, can't warn here + // TODO consider handling this somehow... + Log::debug() << "invalid address range in /proc/.../maps: " << e.what(); + } +} + +LineInfo ProcessInfo::lookup_line_info(Address ip) +{ + try + { + std::shared_lock lock(mutex_); + return map_.at(ip).fr.lookup_line_info(ip - map_.at(ip).start + map_.at(ip).pgoff); + } + catch (std::exception& e) + { + Log::error() << process_ << ": Could not find mapping for ip: " << ip << e.what(); + return LineInfo::for_unknown_function(); + } +} + +std::string ProcessInfo::lookup_instruction(Address ip) const +{ + return map_.at(ip).ir.lookup_instruction(ip - map_.at(ip).start + map_.at(ip).pgoff); +} + +bool ProcessMap::has(Process p, uint64_t timepoint) +{ + auto it = infos_.find(p); + if (it == infos_.end()) + { + return false; + } + for (auto& elem : it->second) + { + if (timepoint >= elem.first) + { + return true; + } + } + return false; +} + +ProcessInfo& ProcessMap::get(Process p, uint64_t timepoint) +{ + auto& infos = infos_.at(p); + + auto it = std::find_if(infos.begin(), infos.end(), + [&timepoint](auto& arg) { return arg.first > timepoint; }); + + if (it == infos.begin()) + { + if (timepoint >= it->first) + { + return it->second; + } + else + { + throw std::out_of_range("no matching timepoint"); + } + } + else + { + it--; + if (timepoint >= it->first) + { + return it->second; + } + else + { + throw std::out_of_range("no matching timepoint it--"); + } + } + + return infos_.at(p).at(timepoint); +} + +ProcessInfo& ProcessMap::insert(Process p, uint64_t timepoint, bool read_initial) +{ + auto it = infos_[p].emplace(std::piecewise_construct, std::forward_as_tuple(timepoint), + std::forward_as_tuple(p, read_initial)); + return it.first->second; +} +} // namespace lo2s diff --git a/src/trace/trace.cpp b/src/trace/trace.cpp index dffe96d3..534b9fed 100644 --- a/src/trace/trace.cpp +++ b/src/trace/trace.cpp @@ -892,7 +892,7 @@ void Trace::merge_calling_contexts(ProcessMap& process_infos) { for (auto& local_cctx : local_cctx_maps_) { - assert(local_cctx.writer != nullptr); + assert(local_cctx.writer() != nullptr); if (local_cctx.num_cctx() > 0) { const auto& mapping = merge_calling_contexts(local_cctx, process_infos); From 66f9ff81b5450c0aa1600f57266f12849c140845 Mon Sep 17 00:00:00 2001 From: Christian von Elm Date: Thu, 7 Nov 2024 14:43:45 +0100 Subject: [PATCH 6/6] Make debuginfod configurable --- CMakeLists.txt | 25 +++++++++++------ cmake/FindDebuginfod.cmake | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 cmake/FindDebuginfod.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 85b9134f..5589c4e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ IfUpdatedUnsetAll(lo2s_USE_STATIC_LIBS X86Adapt_STATIC x86_energy_STATIC CUDA_USE_STATIC_LIBS + Debuginfod_USE_STATIC_LIBS ) if(lo2s_USE_STATIC_LIBS STREQUAL "OFF") @@ -47,6 +48,7 @@ if(lo2s_USE_STATIC_LIBS STREQUAL "OFF") set(Sensors_USE_STATIC_LIBS OFF CACHE BOOL "") set(Libpfm_USE_STATIC_LIBS OFF CACHE BOOL "") set(CUDA_USE_STATIC_LIBS OFF CACHE BOOL "") + set(Debuginfod_USE_STATIC_LIBS OFF CACHE BOOL "") endif() if(lo2s_USE_STATIC_LIBS STREQUAL "MOSTLY") @@ -59,6 +61,7 @@ if(lo2s_USE_STATIC_LIBS STREQUAL "MOSTLY") set(Sensors_USE_STATIC_LIBS ON CACHE BOOL "") set(Libpfm_USE_STATIC_LIBS ON CACHE BOOL "") set(CUDA_USE_STATIC_LIBS ON CACHE BOOL "") + set(Debuginfod_USE_STATIC_LIBS ON CACHE BOOL "") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc") endif() @@ -72,6 +75,7 @@ if(lo2s_USE_STATIC_LIBS STREQUAL "ALL") set(Sensors_USE_STATIC_LIBS ON CACHE BOOL "") set(Libpfm_USE_STATIC_LIBS ON CACHE BOOL "") set(CUDA_USE_STATIC_LIBS ON CACHE BOOL "") + set(Debuginfod_USE_STATIC_LIBS ON CACHE BOOL "") # Doesn't seem to work with clang, even though it should, # but at least it doesn't complain about it either @@ -112,6 +116,7 @@ find_package(Libpfm) find_package(PkgConfig) find_package(CUDAToolkit) find_package(LibElf REQUIRED) +find_package(Debuginfod) if(PkgConfig_FOUND) pkg_check_modules(Audit audit) @@ -136,6 +141,8 @@ CMAKE_DEPENDENT_OPTION(USE_VEOSINFO "Use libveosinfo to sample NEC SX-Aurora Tsu add_feature_info("USE_VEOSINFO" USE_VEOSINFO "Use libveosinfo to sample NEC SX-Aurora Tsubasa cards.") CMAKE_DEPENDENT_OPTION(USE_CUPTI "Use CUPTI to record CUDA activity." ON "CUDAToolkit_FOUND" OFF) add_feature_info("USE_CUPTI" USE_CUPTI "Use CUPTI to record CUDA activity.") +CMAKE_DEPENDENT_OPTION(USE_DEBUGINFOD "Use Debuginfod to download debug information on-demand." ON "Debuginfod_FOUND" OFF) +add_feature_info("USE_DEBUGINFOD" USE_DEBUGINFOD "Use Debuginfod to download debug information on-demand.") # system configuration checks CHECK_INCLUDE_FILES(linux/hw_breakpoint.h HAVE_HW_BREAKPOINT_H) CHECK_STRUCT_HAS_MEMBER("struct perf_event_attr" clockid linux/perf_event.h HAVE_PERF_EVENT_ATTR_CLOCKID) @@ -231,17 +238,8 @@ target_link_libraries(lo2s std::filesystem LibElf::LibElf LibElf::LibDw - debuginfod ) -find_path(DEBUGINFOD_INCLUDE_DIRS elfutils/debuginfod.h - PATHS ENV C_INCLUDE_PATH ENV CPATH - PATH_SUFFIXES include) - -if(DEBUGINFOD_INCLUDE_DIRS) - target_compile_definitions(lo2s PUBLIC HAVE_DEBUGINFOD) -endif() - # old glibc versions require -lrt for clock_gettime() if(NOT CLOCK_GETTIME_FOUND) if(CLOCK_GETTIME_FOUND_WITH_RT) @@ -321,6 +319,15 @@ if (USE_VEOSINFO) endif() endif() +if (USE_DEBUGINFOD) + if (Debuginfod_FOUND) + target_compile_definitions(lo2s PUBLIC HAVE_DEBUGINFOD) + target_link_libraries(lo2s PRIVATE Debuginfod::Debuginfod) + else() + message(SEND_ERROR "Debuginfod not found but requested") + endif() +endif() + if (USE_LIBPFM) if (Libpfm_FOUND) target_compile_definitions(lo2s PUBLIC HAVE_LIBPFM) diff --git a/cmake/FindDebuginfod.cmake b/cmake/FindDebuginfod.cmake new file mode 100644 index 00000000..cb39e068 --- /dev/null +++ b/cmake/FindDebuginfod.cmake @@ -0,0 +1,56 @@ +# Copyright (c) 2022, Technische Universität Dresden, Germany +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions +# and the following disclaimer in the documentation and/or other materials provided with the +# distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse +# or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(${CMAKE_CURRENT_LIST_DIR}/UnsetIfUpdated.cmake) + +# Linking libelf isn't a great default because it produces warnings +option(Debuginfod_USE_STATIC_LIBS "Link debuginfod statically." OFF) + +UnsetIfUpdated(Debuginfod_LIBRARY Debuginfod_USE_STATIC_LIBS) + +find_path(Debuginfod_INCLUDE_DIRS libelf.h + PATHS ENV C_INCLUDE_PATH ENV CPATH + PATH_SUFFIXES include) + +if(Debuginfod_USE_STATIC_LIBS) + find_library(Debuginfod_LIBRARY NAMES libdebuginfod.a + HINTS ENV LIBRARY_PATH) +else() + find_library(Debuginfod_LIBRARY NAMES libdebuginfod.so + HINTS ENV LIBRARY_PATH LD_LIBRARY_PATH) +endif() + +include (FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Debuginfod DEFAULT_MSG + Debuginfod_LIBRARY + Debuginfod_INCLUDE_DIRS) + +if(Debuginfod_FOUND) + add_library(Debuginfod::Debuginfod UNKNOWN IMPORTED) + set_property(TARGET Debuginfod::Debuginfod PROPERTY IMPORTED_LOCATION ${Debuginfod_LIBRARY}) + target_include_directories(Debuginfod::Debuginfod INTERFACE ${Debuginfod_INCLUDE_DIRS}) +endif() + +mark_as_advanced(Debuginfod_LIBRARY Debuginfod_INCLUDE_DIRS)