Files
TheRock/rocm-systems/projects/rocprofiler-sdk/tests/rocattach/attach_tree.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

286 lines
11 KiB
C++

// MIT License
//
// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Tests rocattach_attach_tree(), which attaches to a process and all of its
// descendant processes. The parent forks a child which executes the test app;
// rocattach_attach_tree() is called on the parent PID and must also attach to
// the child without the caller enumerating it explicitly.
#include <rocprofiler-sdk-rocattach/defines.h>
#include <rocprofiler-sdk-rocattach/rocattach.h>
#include <rocprofiler-sdk-rocattach/types.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
static bool
wait_for_attach_ready(pid_t pid)
{
constexpr int max_wait_sec = 30;
auto task_dir = "/proc/" + std::to_string(pid) + "/task";
std::cout << "Waiting for rocp-bg-attach thread in PID " << pid << "...\n";
for(int elapsed = 0; elapsed < max_wait_sec; ++elapsed)
{
std::error_code ec;
for(const auto& entry : std::filesystem::directory_iterator(task_dir, ec))
{
if(!entry.is_directory()) continue;
std::ifstream comm(entry.path() / "comm");
std::string name;
if(std::getline(comm, name) && name == "rocp-bg-attach")
{
std::cout << "Attachment ready (" << elapsed << "s elapsed)\n";
return true;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Timed out after " << max_wait_sec << "s waiting for rocp-bg-attach thread\n";
return false;
}
#define ROCATTACH_CALL(func) \
{ \
std::cout << "starting call to " #func << std::endl; \
rocattach_status_t status = func; \
if(status != ROCATTACH_STATUS_SUCCESS) \
{ \
std::cout << "error: call to " #func " returned non zero status " << status \
<< std::endl; \
return 1; \
} \
else \
{ \
std::cout << "call to " #func " successful" << std::endl; \
} \
}
// Check whether a shared library appears in /proc/<pid>/maps, which confirms
// it was loaded into the target process by the attachment machinery.
bool
is_library_loaded(pid_t pid, const std::string& library_path)
{
std::ifstream maps("/proc/" + std::to_string(pid) + "/maps");
std::string line;
while(std::getline(maps, line))
{
if(line.find(library_path) != std::string::npos) return true;
}
return false;
}
int
main(int argc, char** argv)
{
if(argc < 3)
{
std::cout << "error: wrong number of arguments\n";
return 1;
}
// Build a two-level process tree: parent → child → grandchild.
// Both child and grandchild exec the test application.
// rocattach_attach_tree() is called with only child_pid; it must discover
// the grandchild automatically via /proc to exercise tree traversal.
//
// A pipe is used to communicate the grandchild's PID from child to parent
// so the parent can verify the tool library was loaded in both processes.
int pipefd[2];
if(pipe(pipefd) != 0)
{
std::cout << "error: pipe failed\n";
return 1;
}
pid_t child_pid = fork();
if(child_pid < 0)
{
std::cout << "error: fork failed\n";
return 1;
}
if(child_pid == 0)
{
// Child: spawn a grandchild before replacing itself with the test app.
// The grandchild inherits child's PID as its PPID, which persists
// across the child's exec, so collect_process_tree will find it.
pid_t grandchild_pid = fork();
if(grandchild_pid < 0)
{
std::cout << "error: grandchild fork failed\n";
return 1;
}
if(grandchild_pid == 0)
{
// Grandchild: close both pipe ends — it doesn't use the pipe.
close(pipefd[0]);
close(pipefd[1]);
// Grandchild: exec the test application.
std::cout << "grandchild executing " << argv[1] << std::endl;
execl(argv[1], argv[1], nullptr);
std::cout << "error in grandchild execl(), errno=" << errno << std::endl;
return 1;
}
// Child: send the grandchild PID to the parent via the pipe, then exec.
close(pipefd[0]);
if(write(pipefd[1], &grandchild_pid, sizeof(grandchild_pid)) !=
static_cast<ssize_t>(sizeof(grandchild_pid)))
{
std::cout << "error: failed to write grandchild PID to pipe\n";
return 1;
}
close(pipefd[1]);
// Child: also exec the test application. The grandchild's PPID
// remains this PID across the exec.
std::cout << "child executing " << argv[1] << std::endl;
execl(argv[1], argv[1], nullptr);
std::cout << "error in child execl(), errno=" << errno << std::endl;
return 1;
}
// Parent: close write end and read grandchild PID from pipe.
close(pipefd[1]);
pid_t grandchild_pid = -1;
if(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)) !=
static_cast<ssize_t>(sizeof(grandchild_pid)))
{
std::cout << "error: failed to read grandchild PID from pipe\n";
return 1;
}
close(pipefd[0]);
// Parent process: wait for the child and grandchild to be ready for attachment.
auto kill_children = [&]() {
kill(grandchild_pid, SIGKILL);
kill(child_pid, SIGKILL);
waitpid(grandchild_pid, nullptr, 0);
waitpid(child_pid, nullptr, 0);
};
if(!wait_for_attach_ready(child_pid))
{
kill_children();
return 1;
}
if(!wait_for_attach_ready(grandchild_pid))
{
kill_children();
return 1;
}
setenv("ROCPROF_ATTACH_TOOL_LIBRARY", argv[2], true);
// Attach to the child and any processes it may have spawned. Passing the
// child PID to rocattach_attach_tree lets it discover the full subtree via
// /proc without the caller enumerating descendants explicitly.
{
std::cout << "starting call to rocattach_attach_tree(child_pid)" << std::endl;
rocattach_status_t status = rocattach_attach_tree(child_pid);
if(status != ROCATTACH_STATUS_SUCCESS)
{
std::cout << "error: call to rocattach_attach_tree(child_pid) returned non zero status "
<< status << std::endl;
kill_children();
return 1;
}
std::cout << "call to rocattach_attach_tree(child_pid) successful" << std::endl;
}
// Verify that the tool library was loaded into both the child and the
// grandchild by inspecting /proc/<pid>/maps. setup() calls
// rocprofiler_register_attach() synchronously via ptrace and waits for
// it to return before returning itself, so the library is fully loaded
// in each target process by the time rocattach_attach_tree() returns.
const std::string tool_library = argv[2];
if(!is_library_loaded(child_pid, tool_library))
{
std::cout << "error: tool library not found in child pid " << child_pid << " maps\n";
kill_children();
return 1;
}
std::cout << "verified: child pid " << child_pid << " loaded the tool library\n";
if(!is_library_loaded(grandchild_pid, tool_library))
{
std::cout << "error: tool library not found in grandchild pid " << grandchild_pid
<< " maps\n";
kill_children();
return 1;
}
std::cout << "verified: grandchild pid " << grandchild_pid << " loaded the tool library\n";
// Let profiling run for a few seconds.
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Detach from the process tree symmetrically with how we attached.
{
std::cout << "starting call to rocattach_detach_tree(child_pid)" << std::endl;
rocattach_status_t status = rocattach_detach_tree(child_pid);
if(status != ROCATTACH_STATUS_SUCCESS)
{
std::cout << "error: call to rocattach_detach_tree(child_pid) returned non zero status "
<< status << std::endl;
kill_children();
return 1;
}
std::cout << "call to rocattach_detach_tree(child_pid) successful" << std::endl;
}
int grandchild_status = 0;
kill(grandchild_pid, SIGINT);
waitpid(grandchild_pid, &grandchild_status, 0);
int child_status = 0;
kill(child_pid, SIGINT);
waitpid(child_pid, &child_status, 0);
if(grandchild_status != 0)
{
std::cout << "error: grandchild process returned non-zero status: " << grandchild_status
<< std::endl;
return 1;
}
if(child_status != 0)
{
std::cout << "error: child process returned non-zero status: " << child_status << std::endl;
return 1;
}
return 0;
}