diff --git a/cmake/Findelfutils.cmake b/cmake/Findelfutils.cmake index b454fb793..6175cdde3 100644 --- a/cmake/Findelfutils.cmake +++ b/cmake/Findelfutils.cmake @@ -18,10 +18,10 @@ find_package(zstd) set(CMAKE_FIND_LIBRARY_SUFFIXES ${ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) set(SHA256_ELF - "616099beae24aba11f9b63d86ca6cc8d566d968b802391334c91df54eab416b4" + "09e2ff033d39baa8b388a2d7fbc5390bfde99ae3b7c67c7daaf7433fbcf0f01e" CACHE STRING "SHA256 of the elfutils tar") set(VER_ELF - "0.192" + "0.194" CACHE STRING "elfutils version") set(ELFUTILS_PATH ${VENDOR_PATH}/elfutils-${VER_ELF}) diff --git a/include/dso_hdr.hpp b/include/dso_hdr.hpp index 391c172b0..1b8183746 100644 --- a/include/dso_hdr.hpp +++ b/include/dso_hdr.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -68,6 +69,32 @@ class DsoStats { uint64_t _backpopulate_count{0}; }; +/************** + * vDSO file * + **************/ + +class VdsoSnapshot { +public: + VdsoSnapshot(); + ~VdsoSnapshot(); + + VdsoSnapshot(const VdsoSnapshot &other) = delete; + VdsoSnapshot &operator=(const VdsoSnapshot &other) = delete; + VdsoSnapshot(VdsoSnapshot &&other) noexcept; + VdsoSnapshot &operator=(VdsoSnapshot &&other) noexcept; + + void set(FileInfo info); + + [[nodiscard]] bool ready() const; + [[nodiscard]] const FileInfo &info() const; + +private: + void reset(); + + FileInfo _info; + bool _ready{false}; +}; + /************** * DSO Header * **************/ @@ -112,6 +139,12 @@ class DsoHdr { /******* MAIN APIS **********/ explicit DsoHdr(std::string_view path_to_proc = "", int dd_profiling_fd = -1); + ~DsoHdr() = default; + + DsoHdr(const DsoHdr &other) = delete; + DsoHdr &operator=(const DsoHdr &other) = delete; + DsoHdr(DsoHdr &&other) noexcept = default; + DsoHdr &operator=(DsoHdr &&other) noexcept = default; // Add the element check for overlap and remove them DsoFindRes insert_erase_overlap(Dso &&dso); @@ -214,8 +247,14 @@ class DsoHdr { FileInfoId_t update_id_from_dso(const Dso &dso); FileInfoId_t update_id_dd_profiling(const Dso &dso); + FileInfoId_t update_id_vdso(const Dso &dso); FileInfoId_t update_id_from_path(const Dso &dso); + FileInfo find_vdso_file_info(); + bool ensure_vdso_snapshot(); + std::optional + remap_host_workspace_path(std::string_view original) const; + void init_workspace_remap(); // Unordered map (by pid) of sorted DSOs DsoPidMap _pid_map; @@ -224,10 +263,14 @@ class DsoHdr { FileInfoVector _file_info_vector; std::string _path_to_proc; // /proc files can be mounted at various places // (whole host profiling) + std::string _workspace_host_prefix; + std::string _workspace_container_root; int _dd_profiling_fd; // Assumption is that we have a single version of the dd_profiling library // across all PIDs. FileInfoId_t _dd_profiling_file_info = k_file_info_undef; + VdsoSnapshot _vdso_snapshot; + FileInfoId_t _vdso_file_info = k_file_info_undef; }; } // namespace ddprof diff --git a/include/unwind_helper.hpp b/include/unwind_helper.hpp index ed7442768..2625742ae 100644 --- a/include/unwind_helper.hpp +++ b/include/unwind_helper.hpp @@ -32,4 +32,22 @@ void add_virtual_base_frame(UnwindState *us); void add_error_frame(const Dso *dso, UnwindState *us, ProcessAddress_t pc, SymbolErrors error_case = SymbolErrors::unknown_mapping); +#if defined(__aarch64__) +inline uint64_t canonicalize_user_address(uint64_t addr) { + // Drop the top byte that may hold a pointer tag (TBI/MTE) before sign + // extension so we can match proc-maps entries. + constexpr unsigned k_top_byte_shift = 56; + constexpr uint64_t top_byte_mask = (uint64_t{1} << k_top_byte_shift) - 1; + addr &= top_byte_mask; + + constexpr unsigned canonical_bits = 48; + if (canonical_bits < k_top_byte_shift) { + addr &= (uint64_t{1} << canonical_bits) - 1; + } + return addr; +} +#else +inline uint64_t canonicalize_user_address(uint64_t addr) { return addr; } +#endif + } // namespace ddprof diff --git a/src/dso_hdr.cc b/src/dso_hdr.cc index 34be3801d..ed3b5fbb8 100644 --- a/src/dso_hdr.cc +++ b/src/dso_hdr.cc @@ -16,12 +16,17 @@ #include #include +#include +#include #include #include +#include #include #include #include +#include #include +#include namespace ddprof { @@ -62,8 +67,92 @@ bool is_intersection_allowed(const Dso &old_so, const Dso &new_dso) { return old_so.is_same_file(new_dso) || (old_so._type == DsoType::kStandard && new_dso._type == DsoType::kAnon); } + +struct VdsoBounds { + uintptr_t start; + size_t length; +}; + +std::optional find_self_vdso_bounds() { + const UniqueFile maps{fopen("/proc/self/maps", "r")}; + if (!maps) { + return std::nullopt; + } + char *line = nullptr; + size_t cap = 0; + auto cleanup = make_defer([&]() { free(line); }); + while (getline(&line, &cap, maps.get()) != -1) { + if (line != nullptr && strstr(line, "[vdso]") != nullptr) { + uintptr_t start = 0; + uintptr_t end = 0; + // NOLINTNEXTLINE(cert-err34-c) + if (sscanf(line, "%lx-%lx", &start, &end) == 2 && end > start) { + return VdsoBounds{start, static_cast(end - start)}; + } + break; + } + } + return std::nullopt; +} + +bool write_all(int fd, const uint8_t *data, size_t size) { + size_t written = 0; + while (written < size) { + const ssize_t rc = ::write(fd, data + written, size - written); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + return false; + } + if (rc == 0) { + return false; + } + written += static_cast(rc); + } + return true; +} } // namespace +VdsoSnapshot::VdsoSnapshot() = default; + +VdsoSnapshot::~VdsoSnapshot() { reset(); } + +VdsoSnapshot::VdsoSnapshot(VdsoSnapshot &&other) noexcept + : _info(std::move(other._info)), _ready(other._ready) { + other._ready = false; + other._info = {}; +} + +VdsoSnapshot &VdsoSnapshot::operator=(VdsoSnapshot &&other) noexcept { + if (this != &other) { + reset(); + _info = std::move(other._info); + _ready = other._ready; + other._ready = false; + other._info = {}; + } + return *this; +} + +void VdsoSnapshot::set(FileInfo info) { + reset(); + _info = std::move(info); + _ready = true; +} + +bool VdsoSnapshot::ready() const { return _ready; } + +const FileInfo &VdsoSnapshot::info() const { return _info; } + +void VdsoSnapshot::reset() { + if (_ready && !_info._path.empty()) { + ::unlink(_info._path.c_str()); + } + _ready = false; + _info = {}; +} + /***************/ /* STATS */ /***************/ @@ -109,6 +198,7 @@ DsoHdr::DsoHdr(std::string_view path_to_proc, int dd_profiling_fd) } else { _path_to_proc = path_to_proc; } + init_workspace_remap(); // 0 element is error element _file_info_vector.emplace_back(FileInfo(), 0); } @@ -307,6 +397,48 @@ FileInfoId_t DsoHdr::update_id_dd_profiling(const Dso &dso) { return _dd_profiling_file_info; } +FileInfoId_t DsoHdr::update_id_from_dso(const Dso &dso) { + if (dso._type == DsoType::kVdso) { + return update_id_vdso(dso); + } + + if (!has_relevant_path(dso._type)) { + dso._id = k_file_info_error; // no file associated + return dso._id; + } + + if (dso._type == DsoType::kDDProfiling) { + return update_id_dd_profiling(dso); + } + + return update_id_from_path(dso); +} + +FileInfoId_t DsoHdr::update_id_vdso(const Dso &dso) { + if (_vdso_file_info != k_file_info_undef) { + dso._id = _vdso_file_info; + return dso._id; + } + + FileInfo file_info = find_vdso_file_info(); + if (!file_info._inode) { + dso._id = k_file_info_error; + return dso._id; + } + + const FileInfoInodeKey key(file_info._inode, file_info._size); + auto [it, inserted] = + _file_info_inode_map.emplace(key, _file_info_vector.size()); + if (inserted) { + dso._id = it->second; + _file_info_vector.emplace_back(std::move(file_info), dso._id); + } else { + dso._id = it->second; + } + _vdso_file_info = dso._id; + return dso._id; +} + FileInfoId_t DsoHdr::update_id_from_path(const Dso &dso) { FileInfo file_info = find_file_info(dso); @@ -337,19 +469,6 @@ FileInfoId_t DsoHdr::update_id_from_path(const Dso &dso) { return dso._id; } -FileInfoId_t DsoHdr::update_id_from_dso(const Dso &dso) { - if (!has_relevant_path(dso._type)) { - dso._id = k_file_info_error; // no file associated - return dso._id; - } - - if (dso._type == DsoType::kDDProfiling) { - return update_id_dd_profiling(dso); - } - - return update_id_from_path(dso); -} - bool DsoHdr::maybe_insert_erase_overlap(Dso &&dso, PerfClock::time_point timestamp) { auto &pid_mapping = _pid_map[dso._pid]; @@ -528,10 +647,92 @@ Dso DsoHdr::dso_from_proc_line(int pid, const char *line) { DsoOrigin::kProcMaps}; } +void DsoHdr::init_workspace_remap() { + _workspace_host_prefix.clear(); + _workspace_container_root.clear(); + if (const char *workspace_env = std::getenv("DDPROF_WORKSPACE_ROOT"); + workspace_env && workspace_env[0] != '\0') { + const std::string mapping = workspace_env; + auto const sep = mapping.find('|'); + if (sep != std::string::npos) { + _workspace_host_prefix = mapping.substr(0, sep); + _workspace_container_root = mapping.substr(sep + 1); + } else { + LG_WRN("DDPROF_WORKSPACE_ROOT is expected to be " + "'|'"); + } + } + auto trim_trailing_slash = [](std::string &path) { + while (path.size() > 1 && !path.empty() && path.back() == '/') { + path.pop_back(); + } + }; + trim_trailing_slash(_workspace_host_prefix); + trim_trailing_slash(_workspace_container_root); + if (_workspace_host_prefix.empty() || _workspace_container_root.empty()) { + _workspace_host_prefix.clear(); + _workspace_container_root.clear(); + } else { + LG_NTC("DDPROF_WORKSPACE_ROOT is set to %s|%s", + _workspace_host_prefix.c_str(), _workspace_container_root.c_str()); + } +} + +// Best-effort remapping for local macOS development. When the environment +// variable DDPROF_WORKSPACE_ROOT is set to "|", +// file paths backed by the host (for example /run/host_virtiofs/...) can be +// converted to the in-container path so that libdwfl can locate the ELF. +// Example when using the launch_local_build.sh script: +// /run/host_virtiofs/Users/foo/dd/ddprof/build/... -> /app/build/... +std::optional +DsoHdr::remap_host_workspace_path(std::string_view original) const { + if (_workspace_host_prefix.empty() || _workspace_container_root.empty()) { + return std::nullopt; + } + + namespace fs = std::filesystem; + + fs::path prefix_path(_workspace_host_prefix); + fs::path container_path(_workspace_container_root); + fs::path original_path(original); + + prefix_path = prefix_path.lexically_normal(); + container_path = container_path.lexically_normal(); + original_path = original_path.lexically_normal(); + + if (prefix_path.empty() || container_path.empty()) { + return std::nullopt; + } + + auto it_orig = original_path.begin(); + auto it_prefix = prefix_path.begin(); + for (; it_prefix != prefix_path.end(); ++it_prefix, ++it_orig) { + if (it_orig == original_path.end() || *it_prefix != *it_orig) { + return std::nullopt; + } + } + + fs::path suffix; + for (; it_orig != original_path.end(); ++it_orig) { + suffix /= *it_orig; + } + + fs::path remapped = container_path; + if (!suffix.empty()) { + remapped /= suffix; + } + remapped = remapped.lexically_normal(); + return remapped.string(); +} + FileInfo DsoHdr::find_file_info(const Dso &dso) { int64_t size; inode_t inode; + if (dso._type == DsoType::kVdso) { + return find_vdso_file_info(); + } + // First, try to find matching file in profiler mount namespace since it will // still be accessible when process exits if (get_file_inode(dso._filename.c_str(), &inode, &size) && @@ -551,10 +752,25 @@ FileInfo DsoHdr::find_file_info(const Dso &dso) { return {proc_path, size, inode}; } + if (auto remapped = remap_host_workspace_path(dso._filename)) { + if (get_file_inode(remapped->c_str(), &inode, &size)) { + LG_DBG("[DSO] Remapped %s -> %s", dso._filename.c_str(), + remapped->c_str()); + return {*remapped, size, inode}; + } + } + LG_DBG("[DSO] Unable to find path to %s", dso._filename.c_str()); return {}; } +FileInfo DsoHdr::find_vdso_file_info() { + if (!ensure_vdso_snapshot()) { + return {}; + } + return _vdso_snapshot.info(); +} + int DsoHdr::get_nb_dso() const { unsigned total_nb_elts = 0; std::for_each(_pid_map.begin(), _pid_map.end(), @@ -634,4 +850,65 @@ int DsoHdr::clear_unvisited(const std::unordered_set &visited_pids) { } return pids_to_clear.size(); } + +bool DsoHdr::ensure_vdso_snapshot() { + if (_vdso_snapshot.ready()) { + return true; + } + + const auto bounds_opt = find_self_vdso_bounds(); + if (!bounds_opt) { + LG_WRN("[DSO] Unable to locate local vDSO mapping"); + return false; + } + + const UniqueFd mem_fd{::open("/proc/self/mem", O_RDONLY)}; + if (!mem_fd) { + LG_WRN("[DSO] Unable to open /proc/self/mem: %s", strerror(errno)); + return false; + } + + std::vector buffer(bounds_opt->length); + size_t copied = 0; + while (copied < buffer.size()) { + const ssize_t rd = + pread(mem_fd.get(), buffer.data() + copied, buffer.size() - copied, + static_cast(bounds_opt->start + copied)); + if (rd < 0) { + if (errno == EINTR) { + continue; + } + LG_WRN("[DSO] Failed to copy vDSO bytes: %s", strerror(errno)); + return false; + } + if (rd == 0) { + LG_WRN("[DSO] Failed to copy vDSO bytes: %s", strerror(errno)); + return false; + } + copied += static_cast(rd); + } + + char path_template[] = "/tmp/ddprof-vdsoXXXXXX"; + int fd = mkstemp(path_template); + if (fd == -1) { + LG_WRN("[DSO] Unable to create vDSO snapshot file: %s", strerror(errno)); + return false; + } + auto fd_cleanup = make_defer([&]() { ::close(fd); }); + if (!write_all(fd, buffer.data(), buffer.size())) { + LG_WRN("[DSO] Unable to write vDSO snapshot file: %s", strerror(errno)); + ::unlink(path_template); + return false; + } + + struct stat st{}; + if (fstat(fd, &st) != 0) { + LG_WRN("[DSO] Unable to stat vDSO snapshot file: %s", strerror(errno)); + ::unlink(path_template); + return false; + } + + _vdso_snapshot.set(FileInfo(path_template, st.st_size, st.st_ino)); + return true; +} } // namespace ddprof diff --git a/src/logger.cc b/src/logger.cc index a983786f6..576b3dd2a 100644 --- a/src/logger.cc +++ b/src/logger.cc @@ -49,7 +49,7 @@ struct LoggerContext { LogsAllowedCallback logs_allowed_function; }; -LoggerContext log_ctx{.fd = -1, .mode = LOG_STDERR, .level = LL_ERROR}; +LoggerContext log_ctx{}; } // namespace void LOG_setlevel(int lvl) { @@ -190,17 +190,13 @@ void vlprintfln(int lvl, int fac, const char *format, va_list args) { "<%d>%s.%06ld %s[%d]: ", lvl + (fac * LL_LENGTH), tm_str, d_us.count(), name, pid); } else { + // LL_EMERGENCY is 0 const char *levels[LL_LENGTH] = { - [LL_EMERGENCY] = "EMERGENCY", - [LL_ALERT] = "ALERT", - [LL_CRITICAL] = "CRITICAL", - [LL_ERROR] = "ERROR", - [LL_WARNING] = "WARNING", - [LL_NOTICE] = "NOTICE", - [LL_INFORMATIONAL] = "INFORMATIONAL", - [LL_DEBUG] = "DEBUG", + "EMERGENCY", "ALERT", "CRITICAL", "ERROR", + "WARNING", "NOTICE", "INFORMATIONAL", "DEBUG", }; - sz_h = snprintf(buf, LOG_MSG_CAP, "<%s>%s.%06lu %s[%d]: ", levels[lvl], + sz_h = snprintf(buf, LOG_MSG_CAP, "<%s>%s.%06lu %s[%d]: ", + (lvl >= 0 && lvl < LL_LENGTH ? levels[lvl] : "UNKNOWN"), tm_str, d_us.count(), name, pid); } diff --git a/src/unwind.cc b/src/unwind.cc index d599f8bdb..91c51718b 100644 --- a/src/unwind.cc +++ b/src/unwind.cc @@ -46,6 +46,36 @@ void add_exe_name(UnwindState *us) { us->symbol_hdr._base_frame_symbol_lookup.get_exe_name(us->pid); } +#if defined(__aarch64__) +void skip_vdso_frame_if_needed(UnwindState *us) { + uint64_t const pc = + canonicalize_user_address(us->initial_regs.regs[REGNAME(PC)]); + if (!pc) { + return; + } + DsoHdr::DsoFindRes const pc_res = us->dso_hdr.dso_find_closest(us->pid, pc); + if (!pc_res.second || pc_res.first->second._type != DsoType::kVdso) { + return; + } + + uint64_t const lr = us->initial_regs.regs[REGNAME(LR)]; + if (!lr) { + return; + } + + uint64_t const canonical_lr = canonicalize_user_address(lr); + DsoHdr::DsoFindRes const lr_res = + us->dso_hdr.dso_find_closest(us->pid, canonical_lr); + if (lr_res.second && lr_res.first->second._type == DsoType::kVdso) { + return; + } + + us->initial_regs.regs[REGNAME(PC)] = canonical_lr; + us->current_ip = canonical_lr; + LG_DBG("Skipping VDSO frame (pc=0x%lx -> lr=0x%lx)", pc, canonical_lr); +} +#endif + void add_thread_name(Process &process, UnwindState *us) { us->output.thread_name = process.get_or_insert_thread_name(us->output.tid); } @@ -59,7 +89,15 @@ void unwind_init_sample(UnwindState *us, const uint64_t *sample_regs, us->output.clear(); memcpy(&us->initial_regs.regs[0], sample_regs, k_nb_registers_to_unwind * sizeof(uint64_t)); + us->initial_regs.regs[REGNAME(PC)] = + canonicalize_user_address(us->initial_regs.regs[REGNAME(PC)]); us->current_ip = us->initial_regs.regs[REGNAME(PC)]; +#if defined(__aarch64__) + // this allows us to get out of the VDSO frame on aarch64 + // todo: we should keep this frame as part of the unwind output + // This would give us things like `__kernel_clock_gettime` + skip_vdso_frame_if_needed(us); +#endif us->pid = sample_pid; us->stack_sz = sample_size_stack; us->stack = sample_data_stack; diff --git a/src/unwind_dwfl.cc b/src/unwind_dwfl.cc index 87bd3c455..92c2e5cee 100644 --- a/src/unwind_dwfl.cc +++ b/src/unwind_dwfl.cc @@ -79,6 +79,7 @@ DDRes add_symbol(Dwfl_Frame *dwfl_frame, UnwindState *us) { add_error_frame(nullptr, us, pc, SymbolErrors::unwind_failure); return {}; // invalid pc : do not add frame } + pc = canonicalize_user_address(pc); us->current_ip = pc; DsoHdr &dsoHdr = us->dso_hdr; DsoHdr::PidMapping &pid_mapping = dsoHdr.get_pid_mapping(us->pid); @@ -168,6 +169,7 @@ DDRes add_symbol(Dwfl_Frame *dwfl_frame, UnwindState *us) { if (!is_activation) { --pc; } + pc = canonicalize_user_address(pc); us->current_ip = pc; // Now we register diff --git a/test/ddprof_stats-ut.cc b/test/ddprof_stats-ut.cc index 8c4a7ab3a..231f7671f 100644 --- a/test/ddprof_stats-ut.cc +++ b/test/ddprof_stats-ut.cc @@ -81,7 +81,7 @@ TEST(ddprof_statsTest, ConnectAndSet) { } TEST(ddprof_statsTest, Arithmetic) { - const char path_listen[] = UNIT_TEST_DATA "/my_statsd_listener.sock"; + const char path_listen[] = "/tmp/my_statsd_listener_arithmetic"; unlink(path_listen); // make sure node is available, OK if this fails // Initiate "server" diff --git a/test/dso-ut.cc b/test/dso-ut.cc index 14e36b605..375ddf842 100644 --- a/test/dso-ut.cc +++ b/test/dso-ut.cc @@ -5,10 +5,15 @@ #include "dso_hdr.hpp" +#include +#include +#include #include #include #include #include +#include +#include #include "defer.hpp" #include "loghandle.hpp" @@ -393,6 +398,41 @@ TEST(DSOTest, missing_dso) { EXPECT_FALSE(file_info._inode); } +TEST(DSOTest, WorkspaceRemapping) { + // Create temporary directory that will represent the in-container root. + std::string base_template = + std::filesystem::temp_directory_path() / "ddprof-workspace-remapXXXXXX"; + std::vector tmpl(base_template.begin(), base_template.end()); + tmpl.push_back('\0'); + char *tmp_dir = mkdtemp(tmpl.data()); + ASSERT_NE(tmp_dir, nullptr); + auto cleanup_dir = + make_defer([&]() { std::filesystem::remove_all(tmp_dir); }); + + std::string container_root = tmp_dir; + std::string relative_path = "/subdir/test_binary"; + std::filesystem::create_directories(container_root + "/subdir"); + + std::string container_file = container_root + relative_path; + { + std::ofstream ofs(container_file); + ofs << "test"; + } + + std::string host_prefix = "/run/host_virtiofs/Users/test/ddprof"; + std::string host_path = host_prefix + relative_path; + std::string env_value = host_prefix + "|" + container_root; + setenv("DDPROF_WORKSPACE_ROOT", env_value.c_str(), 1); + auto cleanup_env = make_defer([]() { unsetenv("DDPROF_WORKSPACE_ROOT"); }); + + DsoHdr dso_hdr; + Dso remap_dso(getpid(), 0x1000, 0x2000, 0, std::move(host_path)); + FileInfo file_info = dso_hdr.find_file_info(remap_dso); + + EXPECT_EQ(file_info._path, container_file); + EXPECT_NE(file_info._inode, 0); +} + // clang-format off // Assuming we get a big insertion // Dec 14 14:15:16 ddprof[725]: <0>(MAP)722: /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 (7f51f1d42000/389000/0) diff --git a/tools/elfutils.patch b/tools/elfutils.patch index 36bf69ac0..baff5f3f4 100644 --- a/tools/elfutils.patch +++ b/tools/elfutils.patch @@ -1,7 +1,7 @@ -diff -ur elfutils-0.186/libdwfl/frame_unwind.c elfutils-0.186_patched/libdwfl/frame_unwind.c ---- elfutils-0.186/libdwfl/frame_unwind.c 2021-11-10 22:21:41.000000000 +0000 -+++ elfutils-0.186_patched/libdwfl/frame_unwind.c 2022-07-08 14:22:12.760891930 +0000 -@@ -145,7 +145,7 @@ +diff -urN --exclude=GPG-KEY --exclude=tests/debuginfod-ima/koji/fedora-38-ima.pem elfutils-0.194/libdwfl/frame_unwind.c elfutils-0.194_patched/libdwfl/frame_unwind.c +--- elfutils-0.194/libdwfl/frame_unwind.c 2025-10-25 03:33:14 ++++ elfutils-0.194_patched/libdwfl/frame_unwind.c 2025-12-03 11:47:14 +@@ -134,7 +134,7 @@ static bool expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops, @@ -10,16 +10,16 @@ diff -ur elfutils-0.186/libdwfl/frame_unwind.c elfutils-0.186_patched/libdwfl/fr { Dwfl_Process *process = state->thread->process; if (nops == 0) -@@ -314,7 +314,7 @@ +@@ -303,7 +303,7 @@ } if (! pop (&val1) || ! process->callbacks->memory_read (process->dwfl, val1, &val1, - process->callbacks_arg)) -+ -1, process->callbacks_arg)) ++ regno, process->callbacks_arg)) { free (stack.addrs); return false; -@@ -467,7 +467,7 @@ +@@ -456,7 +456,7 @@ Dwarf_Addr cfa; if (frame == NULL || dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0 @@ -28,25 +28,25 @@ diff -ur elfutils-0.186/libdwfl/frame_unwind.c elfutils-0.186_patched/libdwfl/fr || ! push (cfa)) { __libdwfl_seterrno (DWFL_E_LIBDW); -@@ -498,7 +498,7 @@ - __libdwfl_seterrno (DWFL_E_INVALID_ARGUMENT); +@@ -488,6 +488,7 @@ return false; } -- if (! process->callbacks->memory_read (process->dwfl, *result, result, -+ if (! process->callbacks->memory_read (process->dwfl, *result, result, regno, + if (! process->callbacks->memory_read (process->dwfl, *result, result, ++ regno, process->callbacks_arg)) return false; } -@@ -600,7 +600,7 @@ +@@ -590,7 +591,8 @@ continue; } } - else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val, bias)) -+ else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val, bias, regno)) ++ else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val, bias, ++ regno)) { /* PPC32 vDSO has various invalid operations, ignore them. The register will look as unset causing an error later, if used. -@@ -709,7 +711,7 @@ +@@ -712,7 +714,7 @@ Dwfl_Frame *state = arg; Dwfl_Thread *thread = state->thread; Dwfl_Process *process = thread->process; @@ -55,23 +55,21 @@ diff -ur elfutils-0.186/libdwfl/frame_unwind.c elfutils-0.186_patched/libdwfl/fr process->callbacks_arg); } -diff -ur elfutils-0.186/libdwfl/libdwfl.h elfutils-0.186_patched/libdwfl/libdwfl.h ---- elfutils-0.186/libdwfl/libdwfl.h 2021-11-10 22:21:41.000000000 +0000 -+++ elfutils-0.186_patched/libdwfl/libdwfl.h 2022-07-08 14:20:27.340893360 +0000 -@@ -664,8 +664,8 @@ - successfully read *RESULT or false and sets dwfl_errno () on failure. +diff -urN --exclude=GPG-KEY --exclude=tests/debuginfod-ima/koji/fedora-38-ima.pem elfutils-0.194/libdwfl/libdwfl.h elfutils-0.194_patched/libdwfl/libdwfl.h +--- elfutils-0.194/libdwfl/libdwfl.h 2025-10-25 03:33:14 ++++ elfutils-0.194_patched/libdwfl/libdwfl.h 2025-12-03 11:45:56 +@@ -688,7 +688,7 @@ This method may be NULL - in such case dwfl_thread_getframes will return only the initial frame. */ -- bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, + bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, - void *dwfl_arg) -+ bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, + int regno, void *dwfl_arg) __nonnull_attribute__ (1, 3); /* Called on initial unwind to get the initial register state of the first -diff -ur elfutils-0.186/libdwfl/linux-core-attach.c elfutils-0.186_patched/libdwfl/linux-core-attach.c ---- elfutils-0.186/libdwfl/linux-core-attach.c 2021-11-10 22:21:41.000000000 +0000 -+++ elfutils-0.186_patched/libdwfl/linux-core-attach.c 2022-07-08 14:24:00.932890463 +0000 +diff -urN --exclude=GPG-KEY --exclude=tests/debuginfod-ima/koji/fedora-38-ima.pem elfutils-0.194/libdwfl/linux-core-attach.c elfutils-0.194_patched/libdwfl/linux-core-attach.c +--- elfutils-0.194/libdwfl/linux-core-attach.c 2025-10-25 03:33:14 ++++ elfutils-0.194_patched/libdwfl/linux-core-attach.c 2025-12-03 11:46:14 @@ -51,7 +51,7 @@ }; @@ -89,23 +87,66 @@ diff -ur elfutils-0.186/libdwfl/linux-core-attach.c elfutils-0.186_patched/libdw if (elf_getphdrnum (core, &phnum) < 0) { __libdwfl_seterrno (DWFL_E_LIBELF); -diff -ur elfutils-0.186/libdwfl/linux-pid-attach.c elfutils-0.186_patched/libdwfl/linux-pid-attach.c ---- elfutils-0.186/libdwfl/linux-pid-attach.c 2021-11-10 22:21:41.000000000 +0000 -+++ elfutils-0.186_patched/libdwfl/linux-pid-attach.c 2022-07-08 14:23:28.596890901 +0000 -@@ -191,13 +191,14 @@ +diff -urN --exclude=GPG-KEY --exclude=tests/debuginfod-ima/koji/fedora-38-ima.pem elfutils-0.194/libdwfl/linux-pid-attach.c elfutils-0.194_patched/libdwfl/linux-pid-attach.c +--- elfutils-0.194/libdwfl/linux-pid-attach.c 2025-10-25 03:33:14 ++++ elfutils-0.194_patched/libdwfl/linux-pid-attach.c 2025-12-03 11:46:25 +@@ -190,12 +190,14 @@ /* Note that the result word size depends on the architecture word size. That is sizeof long. */ static bool -pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) -+pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, int regno, void *arg) ++pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, int regno, ++ void *arg) { struct __libdwfl_pid_arg *pid_arg = arg; pid_t tid = pid_arg->tid_attached; Dwfl_Process *process = dwfl->process; assert (tid > 0); -- + (void) regno; -+ + #ifdef HAVE_PROCESS_VM_READV if (read_cached_memory (pid_arg, addr, result)) - { +diff -urN --exclude=GPG-KEY --exclude=tests/debuginfod-ima/koji/fedora-38-ima.pem elfutils-0.194/libdwfl_stacktrace/dwflst_sample_frame.c elfutils-0.194_patched/libdwfl_stacktrace/dwflst_sample_frame.c +--- elfutils-0.194/libdwfl_stacktrace/dwflst_sample_frame.c 2025-10-25 03:33:14 ++++ elfutils-0.194_patched/libdwfl_stacktrace/dwflst_sample_frame.c 2025-12-03 11:46:32 +@@ -126,8 +126,10 @@ + *(result) = 0; + + static bool +-elf_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) ++elf_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, int regno, ++ void *arg) + { ++ (void) regno; + struct sample_info *sample_arg = + (struct sample_info *)arg; + Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (dwfl, addr); +@@ -151,14 +153,15 @@ + } + + static bool +-sample_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) ++sample_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, int regno, ++ void *arg) + { + struct sample_info *sample_arg = + (struct sample_info *)arg; + /* Imitate read_cached_memory() with the stack sample data as the cache. */ + if (addr < sample_arg->base_addr || + addr - sample_arg->base_addr >= sample_arg->stack_size) +- return elf_memory_read(dwfl, addr, result, arg); ++ return elf_memory_read(dwfl, addr, result, regno, arg); + const uint8_t *d = &sample_arg->stack[addr - sample_arg->base_addr]; + copy_word(result, d, sample_arg->elfclass); + return true; +diff -urN --exclude=GPG-KEY --exclude=tests/debuginfod-ima/koji/fedora-38-ima.pem elfutils-0.194/tests/backtrace-data.c elfutils-0.194_patched/tests/backtrace-data.c +--- elfutils-0.194/tests/backtrace-data.c 2025-10-25 03:33:14 ++++ elfutils-0.194_patched/tests/backtrace-data.c 2025-12-03 11:47:02 +@@ -70,6 +70,7 @@ + + static bool + memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, ++ int regno __attribute__ ((unused)), + void *dwfl_arg __attribute__ ((unused))) + { + pid_t child = dwfl_pid (dwfl); diff --git a/tools/launch_local_build.sh b/tools/launch_local_build.sh index 6fd8e684b..e2e21867d 100755 --- a/tools/launch_local_build.sh +++ b/tools/launch_local_build.sh @@ -88,6 +88,15 @@ if [ -e "${CURRENTDIR}/.env" ]; then fi MOUNT_CMD="-v ${DEFAULT_DEV_WORKSPACE:-${CURRENTDIR}}:/app" +# avoid symlinks +HOST_SHARE_PATH=$(python3 - <<'PY' +import os, sys +path = os.environ.get("DEFAULT_DEV_WORKSPACE", os.environ.get("CURRENTDIR")) +if not path: + path = os.getcwd() +print(os.path.realpath(path), end="") +PY +) # Support docker sync : Improves compilation speed # Example of config (to be pasted in the docker-sync.yml file) @@ -102,6 +111,7 @@ if [ -e "${CURRENTDIR}/docker-sync.yml" ]; then VOLUME_SYNC=$(grep -A 1 "syncs:" "${CURRENTDIR}/docker-sync.yml" | tail -n 1 | awk -F ':' '{print $1}' | sed "s/ //g") echo "$VOLUME_SYNC" MOUNT_CMD="--mount source=${VOLUME_SYNC},target=/app" + HOST_SHARE_PATH="" if ! docker-sync list | grep -q "$VOLUME_SYNC"; then echo "Please generate a volume: $VOLUME_SYNC" echo "Suggested commands:" @@ -130,13 +140,15 @@ if [ $PERFORM_CLEAN -eq 1 ]; then fi # Check if base image exists -if [ ! ${CUSTOM_ID:-,,} == "yes" ] && ! docker images | awk '{print $1}'| grep -qE "^${DOCKER_NAME}$"; then - echo "Building image" - BUILD_CMD="docker build $CACHE_OPTION -t ${DOCKER_NAME} --build-arg COMPILER=$COMPILER --build-arg UBUNTU_VERSION=${UBUNTU_VERSION} -f $BASE_DOCKERFILE ." - #echo "${BUILD_CMD}" - eval "${BUILD_CMD}" -else - echo "Base image found, not rebuilding. Remove it to force rebuild." +if [ ! ${CUSTOM_ID:-,,} == "yes" ]; then + if ! docker image inspect "${DOCKER_NAME}" >/dev/null 2>&1; then + echo "Building image" + BUILD_CMD="docker build $CACHE_OPTION -t ${DOCKER_NAME} --build-arg COMPILER=$COMPILER --build-arg UBUNTU_VERSION=${UBUNTU_VERSION} -f $BASE_DOCKERFILE ." + #echo "${BUILD_CMD}" + eval "${BUILD_CMD}" + else + echo "Base image found, not rebuilding. Remove it to force rebuild." + fi fi if [[ $OSTYPE == darwin* ]]; then @@ -162,6 +174,15 @@ else MOUNT_TOOLS_DIR="" fi -CMD="docker run -it --rm -u $(id -u):$(id -g) --network=host -w /app ${MOUNT_SSH_AGENT} ${MOUNT_TOOLS_DIR} --cap-add CAP_SYS_PTRACE --cap-add SYS_ADMIN ${MOUNT_CMD} \"${DOCKER_NAME}${DOCKER_TAG}\" /bin/bash" +WORKSPACE_ENV="" +if [[ $OSTYPE == darwin* ]] && [ -n "${HOST_SHARE_PATH}" ]; then + HOST_PREFIX="/run/host_virtiofs${HOST_SHARE_PATH}" + CONTAINER_ROOT="/app" + WORKSPACE_ENV="-e DDPROF_WORKSPACE_ROOT=${HOST_PREFIX}|${CONTAINER_ROOT}" +fi -eval "$CMD" +# shellcheck disable=SC2086 +docker run -it --rm -u "$(id -u):$(id -g)" --network=host -w /app \ + ${MOUNT_SSH_AGENT} ${MOUNT_TOOLS_DIR} ${WORKSPACE_ENV} \ + --cap-add CAP_SYS_PTRACE --cap-add SYS_ADMIN ${MOUNT_CMD} \ + "${DOCKER_NAME}${DOCKER_TAG}" /bin/bash diff --git a/tools/style-check.sh b/tools/style-check.sh index 00f4297b6..45571ede2 100755 --- a/tools/style-check.sh +++ b/tools/style-check.sh @@ -33,5 +33,5 @@ declare -a arr_folders=("src" "test" "include") FILES_TO_FORMAT=".*[.]\(cpp\|cc\|c\|cxx\|h\|hpp\)" find "${arr_folders[@]}" -regex "${FILES_TO_FORMAT}" -print0 | xargs -0 "${CLANG_FORMAT}" ${CLANG_OPTION} -find . -maxdepth 1 -\( -name CMakeLists.txt -or -name '*.cmake' -\) -print0 | xargs -0 cmake-format ${CMAKE_FORMAT_OPTIONS} -find cmake src test -\( -name CMakeLists.txt -or -name '*.cmake' -\) -print0 | xargs -0 cmake-format ${CMAKE_FORMAT_OPTIONS} +find . -maxdepth 1 \( -name CMakeLists.txt -or -name '*.cmake' \) -print0 | xargs -0 cmake-format ${CMAKE_FORMAT_OPTIONS} +find cmake src test \( -name CMakeLists.txt -or -name '*.cmake' \) -print0 | xargs -0 cmake-format ${CMAKE_FORMAT_OPTIONS}