diff --git a/include/dso_hdr.hpp b/include/dso_hdr.hpp index 391c172b..d9f0b9f6 100644 --- a/include/dso_hdr.hpp +++ b/include/dso_hdr.hpp @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include @@ -216,6 +218,9 @@ class DsoHdr { FileInfoId_t update_id_dd_profiling(const Dso &dso); FileInfoId_t update_id_from_path(const Dso &dso); + 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,6 +229,8 @@ 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. diff --git a/src/dso_hdr.cc b/src/dso_hdr.cc index 34be3801..ab1b861c 100644 --- a/src/dso_hdr.cc +++ b/src/dso_hdr.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -109,6 +110,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); } @@ -528,6 +530,57 @@ 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.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()); + } +} + +// On macOS with Docker / virtiofs, /proc/pid/maps exposes host-backed paths +// (e.g. /run/host_virtiofs/Users/foo/ddprof/build/...) instead of the +// in-container path (/app/build/...). When DDPROF_WORKSPACE_ROOT is set, +// strip the host prefix and replace it with the container root so that +// libdwfl can locate the ELF file. +std::optional +DsoHdr::remap_host_workspace_path(std::string_view original) const { + if (_workspace_host_prefix.empty()) { + return std::nullopt; + } + if (!original.starts_with(_workspace_host_prefix)) { + return std::nullopt; + } + auto suffix = original.substr(_workspace_host_prefix.size()); + if (!suffix.empty() && suffix[0] != '/') { + return std::nullopt; + } + return _workspace_container_root + std::string(suffix); +} + FileInfo DsoHdr::find_file_info(const Dso &dso) { int64_t size; inode_t inode; @@ -551,6 +604,14 @@ 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 {}; } diff --git a/test/ddprof_stats-ut.cc b/test/ddprof_stats-ut.cc index 8c4a7ab3..231f7671 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 14e36b60..c28908b2 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,40 @@ TEST(DSOTest, missing_dso) { EXPECT_FALSE(file_info._inode); } +TEST(DSOTest, WorkspaceRemapping) { + 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/launch_local_build.sh b/tools/launch_local_build.sh index 6fd8e684..e2e21867 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