attach: Formalize ROCAttach API (#1653)

* attach: Formalize ROCAttach API

- Make ROCAttach public with public headers
- Change detach to take a PID
  - attach and detach are now reentrant
- Cleanup of states and signal handling in ptrace session
- Fixes mixed up definition of ROCPROF_ATTACH_TOOL_LIBRARY
  - ROCPROF_ATTACH_TOOL_LIBRARY now always means the tool library loaded by the attachment target
  - ROCPROF_ATTACH_LIBRARY refers to the library used to perform attachment
- Add direct call of rocprof-attach
- Fix python library call of rocprof-attach
  - Function now named attach(), changed from main()

* attach: rocprof-compute ROCAttach updates

- Update to new library names
- Correct usage of C lib detach

* attach: add test for rocattach

- Disable ASan, TSan, and UBSan for the new parallel-attach test
- Lower log level for LSan tests, existing behavior from other tests

---------

Co-authored-by: Ammar ELWazir <aelwazir@amd.com>
Αυτή η υποβολή περιλαμβάνεται σε:
Mark Meserve
2026-01-15 14:32:14 -06:00
υποβλήθηκε από GitHub
γονέας 2482bff0b7
υποβολή 8760fb4976
26 αρχεία άλλαξαν με 2426 προσθήκες και 795 διαγραφές
@@ -26,77 +26,121 @@
#include "lib/common/logging.hpp"
#include "lib/common/static_object.hpp"
#include <rocprofiler-sdk/defines.h>
#include <rocprofiler-sdk-rocattach/defines.h>
#include <rocprofiler-sdk-rocattach/rocattach.h>
#include <rocprofiler-sdk-rocattach/types.h>
#include <atomic>
#include <thread>
#include <map>
#include <mutex>
#include <unordered_map>
extern char** environ;
namespace common = ::rocprofiler::common;
namespace rocprofiler
{
namespace rocattach
{
namespace
{
std::unique_ptr<rocprofiler::attach::PTraceSession> ptrace_session;
std::thread ptrace_thread;
std::atomic<bool> finished_setup(false);
} // namespace
using session_t = rocprofiler::rocattach::PTraceSession;
using session_list_t = std::map<int, session_t>;
ROCPROFILER_EXTERN_C_INIT
int
attach(uint32_t pid) ROCPROFILER_EXPORT;
#define ROCATTACH_STATUS_STRING(CODE, MSG) \
template <> \
struct status_string<CODE> \
{ \
static constexpr auto name = #CODE; \
static constexpr auto value = MSG; \
};
int
detach() ROCPROFILER_EXPORT;
ROCPROFILER_EXTERN_C_FINI
template <size_t Idx>
struct status_string;
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_SUCCESS, "Success")
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_ERROR, "General error")
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_ERROR_INVALID_ARGUMENT, "Invalid function argument")
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_ERROR_NOT_SUPPORTED,
"Attachment not supported on this platform")
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_ERROR_PTRACE_ERROR, "General ptrace error")
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_ERROR_PTRACE_OPERATION_NOT_PERMITTED,
"ptrace returned EPERM, operation not permitted")
ROCATTACH_STATUS_STRING(ROCATTACH_STATUS_ERROR_PTRACE_PROCESS_NOT_FOUND,
"ptrace returned ESRCH, no such process")
template <size_t Idx, size_t... Tail>
const char*
get_status_name(rocattach_status_t status, std::index_sequence<Idx, Tail...>)
{
if(status == Idx) return status_string<Idx>::name;
// recursion until tail empty
if constexpr(sizeof...(Tail) > 0)
return get_status_name(status, std::index_sequence<Tail...>{});
return nullptr;
}
template <size_t Idx, size_t... Tail>
const char*
get_status_string(rocattach_status_t status, std::index_sequence<Idx, Tail...>)
{
if(status == Idx) return status_string<Idx>::value;
// recursion until tail empty
if constexpr(sizeof...(Tail) > 0)
return get_status_string(status, std::index_sequence<Tail...>{});
return nullptr;
}
void
initialize_logging()
{
auto logging_cfg = rocprofiler::common::logging_config{.install_failure_handler = true};
common::init_logging("ROCPROF", logging_cfg);
common::init_logging("ROCATTACH", logging_cfg);
FLAGS_colorlogtostderr = true;
}
namespace
session_list_t*
get_sessions()
{
static auto*& session_list = rocprofiler::common::static_object<session_list_t>::construct();
return session_list;
}
std::lock_guard<std::mutex>
get_sessions_lock_guard()
{
static auto*& m = rocprofiler::common::static_object<std::mutex>::construct();
return std::lock_guard(*CHECK_NOTNULL(m));
}
// Helper function to allocate memory in target process and write data
bool
write_data_to_target(const std::string& description,
rocattach_status_t
write_data_to_target(session_t& session,
const std::string& description,
const std::vector<uint8_t>& data,
void*& allocated_addr)
{
// Allocate memory in target process
if(!ptrace_session->simple_mmap(allocated_addr, data.size()))
auto status = ROCATTACH_STATUS_SUCCESS;
status = session.simple_mmap(allocated_addr, data.size());
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "Failed to allocate memory for " << description << " in target process";
return false;
}
ROCP_TRACE << "Allocated memory for " << description << " at " << allocated_addr;
// Stop target process for writing
if(!ptrace_session->stop())
{
ROCP_ERROR << "Failed to stop target process for " << description << " writing";
return false;
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Failed to allocate memory for " << description
<< " in target process pid " << session.get_pid();
return status;
}
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Allocated memory for " << description << " at "
<< allocated_addr << " in target process pid " << session.get_pid();
// Write data to target process memory
if(!ptrace_session->write(reinterpret_cast<size_t>(allocated_addr), data, data.size()))
status = session.write(reinterpret_cast<size_t>(allocated_addr), data, data.size());
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "Failed to write " << description << " to target process";
return false;
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Failed to write " << description
<< " to target process pid " << session.get_pid();
return status;
}
// Continue target process
if(!ptrace_session->cont())
{
ROCP_ERROR << "Failed to continue target process after " << description << " writing";
return false;
}
ROCP_TRACE << "Wrote " << description << " to target process";
return true;
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Wrote " << description << " to target process pid "
<< session.get_pid();
return status;
}
// Helper function to build environment buffer
@@ -112,11 +156,12 @@ build_environment_buffer()
const char* var = *invars;
if(strncmp("ROCP", var, 4) != 0)
{
// only take envvars starting with ROCP
continue;
}
var_count++;
ROCP_TRACE << "Adding to environment buffer: " << var;
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Adding to environment buffer: " << var;
// Add variable name
while(*var != '=')
@@ -140,119 +185,275 @@ build_environment_buffer()
return environment_buffer;
}
} // anonymous namespace
ROCPROFILER_EXTERN_C_INIT
void
handle_ptrace_operations(uint32_t pid)
rocattach_status_t
setup(int pid)
{
// Setup attachement for rocprofiler
ROCP_TRACE << "Attachment library called for pid " << pid;
ptrace_session = std::make_unique<rocprofiler::attach::PTraceSession>(pid);
ROCP_TRACE << "Attempting attachment to pid " << pid;
if(!ptrace_session->attach())
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Attachment library rocattach_attach function called "
"for pid "
<< pid;
auto* sessions = CHECK_NOTNULL(get_sessions());
session_t* session;
{
ROCP_ERROR << "Attachment failed to pid " << pid;
ptrace_session->m_setup_status.store(ROCPROFILER_STATUS_ERROR_INVALID_ARGUMENT);
finished_setup.store(true);
return;
auto lg = get_sessions_lock_guard();
if(sessions->count(pid) > 0)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] rocattach_attach called for pid " << pid
<< ", which already has an active "
"attachment session.";
return ROCATTACH_STATUS_ERROR_INVALID_ARGUMENT;
}
sessions->emplace(pid, pid);
session = &(sessions->at(pid));
}
ROCP_TRACE << "Attachment success to pid " << pid;
auto status = ROCATTACH_STATUS_SUCCESS;
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Attempting attachment to pid " << pid;
status = session->attach();
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Attachment failed to pid " << pid
<< " with status code " << status;
return status;
}
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Attachment success to pid " << pid;
// Build and write environment buffer to target process
auto environment_buffer = build_environment_buffer();
void* environment_buffer_addr = nullptr;
if(!write_data_to_target("environment buffer", environment_buffer, environment_buffer_addr))
status = write_data_to_target(
*session, "environment buffer", environment_buffer, environment_buffer_addr);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ptrace_session->m_setup_status.store(ROCPROFILER_STATUS_ERROR);
finished_setup.store(true);
return;
return status;
}
// Build and write tool library path to target process
auto tool_lib_path_env =
rocprofiler::common::get_env("ROCPROF_ATTACH_TOOL_LIBRARY", "librocprofiler-sdk-tool.so");
const char* tool_lib_path = tool_lib_path_env.c_str();
ROCP_TRACE << "Tool library path: " << tool_lib_path;
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Tool library path: " << tool_lib_path;
size_t tool_lib_path_len = strlen(tool_lib_path) + 1;
std::vector<uint8_t> tool_lib_buffer(tool_lib_path, tool_lib_path + tool_lib_path_len);
void* tool_lib_path_addr = nullptr;
if(!write_data_to_target("tool library path", tool_lib_buffer, tool_lib_path_addr))
status =
write_data_to_target(*session, "tool library path", tool_lib_buffer, tool_lib_path_addr);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ptrace_session->m_setup_status.store(ROCPROFILER_STATUS_ERROR);
finished_setup.store(true);
return;
}
// Execute the attach function with both parameters
if(!ptrace_session->call_function("librocprofiler-register.so",
"rocprofiler_register_attach",
environment_buffer_addr,
tool_lib_path_addr))
{
ROCP_ERROR << "Failed to call attach function in target process " << pid;
ptrace_session->m_setup_status.store(ROCPROFILER_STATUS_ERROR);
finished_setup.store(true);
return;
}
// Clean up - free the tool library path memory in target process
if(!ptrace_session->simple_munmap(tool_lib_path_addr, tool_lib_path_len))
{
ROCP_ERROR << "Failed to free tool library path memory in target process";
// Continue anyway since the main operation succeeded
}
ROCP_TRACE << "Cleaned up tool library path memory in target process";
// Allow main thread to continue
finished_setup.store(true);
if(!ptrace_session->handle_signals())
{
ROCP_ERROR << "Signal handling loop terminated unexepectedly for pid " << pid;
// don't return, try to detach anyways
}
// Detach rocprofiler
ROCP_TRACE << "Detaching rocprofiler from pid " << pid;
if(!ptrace_session->call_function("librocprofiler-register.so", "rocprofiler_register_detach"))
{
ROCP_ERROR << "Failed to call detach function in target process";
// don't return, try to detach anyways
}
ptrace_session->stop();
ptrace_session->detach();
ptrace_session.reset();
}
int
attach(uint32_t pid)
{
initialize_logging();
ptrace_thread = std::thread(handle_ptrace_operations, pid);
// Wait for ptrace thread to finish setting up
while(!finished_setup.load())
std::this_thread::yield();
auto status = ptrace_session->m_setup_status.load();
if(status != ROCPROFILER_STATUS_SUCCESS)
{
ROCP_ERROR << "ptrace session failed with error code " << ptrace_session->m_setup_status;
ptrace_thread.join();
finished_setup.store(false);
return status;
}
return ROCPROFILER_STATUS_SUCCESS;
uint64_t retval = 0;
// Execute the attach function with both parameters
status = session->call_function("librocprofiler-register.so",
"rocprofiler_register_attach",
retval,
environment_buffer_addr,
tool_lib_path_addr);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR
<< "[rocprofiler-sdk-rocattach] Failed to call "
"rocprofiler-register::rocprofiler_register_attach function in target process "
<< pid << ". status: " << status;
return status;
}
else if(retval != 0)
{
ROCP_ERROR
<< "[rocprofiler-sdk-rocattach] rocprofiler-register::rocprofiler_register_attach "
"function returned non-zero status in target process "
<< pid << ". return: " << retval;
return ROCATTACH_STATUS_ERROR;
}
// Clean up - free the environment buffer and tool library path memory in target process
status = session->simple_munmap(environment_buffer_addr, environment_buffer.size());
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Failed to free environment buffer memory in "
"target process "
<< pid << ", continuing...";
// Continue anyway since the main operation succeeded
}
ROCP_TRACE
<< "[rocprofiler-sdk-rocattach] Cleaned up tool environment memory in target process "
<< pid;
status = session->simple_munmap(tool_lib_path_addr, tool_lib_path_len);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Failed to free tool library path memory in "
"target process "
<< pid << ", continuing...";
// Continue anyway since the main operation succeeded
}
ROCP_TRACE
<< "[rocprofiler-sdk-rocattach] Cleaned up tool library path memory in target process "
<< pid;
return ROCATTACH_STATUS_SUCCESS;
}
int
detach()
rocattach_status_t
teardown(int pid)
{
ptrace_session->detach_ptrace_session();
ptrace_thread.join();
finished_setup.store(false);
return ROCPROFILER_STATUS_SUCCESS;
// Setup attachement for rocprofiler
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Attachment library rocattach_detach function called "
"for pid "
<< pid;
auto* sessions = CHECK_NOTNULL(get_sessions());
session_t* session;
{
auto lg = get_sessions_lock_guard();
if(sessions->count(pid) == 0)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] rocattach_detach called for pid " << pid
<< ", which has no active "
"attachment session.";
return ROCATTACH_STATUS_ERROR_INVALID_ARGUMENT;
}
session = &(sessions->at(pid));
}
auto status = ROCATTACH_STATUS_SUCCESS;
uint64_t retval = 0;
// Execute the attach function with both parameters
status =
session->call_function("librocprofiler-register.so", "rocprofiler_register_detach", retval);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR
<< "[rocprofiler-sdk-rocattach] Failed to call "
"rocprofiler-register::rocprofiler_register_detach function in target process "
<< pid << ". status: " << status;
// continue to detach anyways
}
else if(retval != 0)
{
ROCP_ERROR
<< "[rocprofiler-sdk-rocattach] rocprofiler-register::rocprofiler_register_detach "
"function returned non-zero status in target process "
<< pid << ". return: " << retval;
// continue to detach anyways
}
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Attempting detachment to pid " << pid;
status = session->detach();
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Detachment failed from pid " << pid;
return status;
}
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Detachment success from pid " << pid;
{
auto lg = get_sessions_lock_guard();
sessions->erase(pid);
}
return ROCATTACH_STATUS_SUCCESS;
}
ROCPROFILER_EXTERN_C_FINI
} // namespace
} // namespace rocattach
} // namespace rocprofiler
ROCATTACH_EXTERN_C_INIT
rocattach_status_t
rocattach_attach(int pid)
{
rocprofiler::rocattach::initialize_logging();
if(!rocprofiler::rocattach::PTraceSession::is_supported())
{
ROCP_ERROR << "[rocprofiler-sdk-attach] rocattach is not supported on this platform.";
return ROCATTACH_STATUS_ERROR_NOT_SUPPORTED;
}
auto status = rocprofiler::rocattach::setup(pid);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] rocattach_attach failed with error code "
<< status;
return status;
}
return ROCATTACH_STATUS_SUCCESS;
}
rocattach_status_t
rocattach_detach(int pid)
{
rocprofiler::rocattach::initialize_logging();
if(pid != 0)
{
auto status = rocprofiler::rocattach::teardown(pid);
if(status != ROCATTACH_STATUS_SUCCESS)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] rocattach_detach failed with error code "
<< status;
return status;
}
return ROCATTACH_STATUS_SUCCESS;
}
else
{
ROCP_INFO << "[rocprofiler-sdk-rocattach] rocattach_detach received pid=0, detaching from "
"ALL sessions";
std::vector<int> pids;
{
auto lg = rocprofiler::rocattach::get_sessions_lock_guard();
for(auto& pair_itr : *(CHECK_NOTNULL(rocprofiler::rocattach::get_sessions())))
{
pids.emplace_back(pair_itr.first);
}
}
for(int pid_itr : pids)
{
rocprofiler::rocattach::teardown(pid_itr);
}
return ROCATTACH_STATUS_SUCCESS;
}
}
rocattach_status_t
rocattach_get_version(uint32_t* major, uint32_t* minor, uint32_t* patch)
{
*CHECK_NOTNULL(major) = ROCATTACH_VERSION_MAJOR;
*CHECK_NOTNULL(minor) = ROCATTACH_VERSION_MINOR;
*CHECK_NOTNULL(patch) = ROCATTACH_VERSION_PATCH;
return ROCATTACH_STATUS_SUCCESS;
}
rocattach_status_t
rocattach_get_version_triplet(rocattach_version_triplet_t* info)
{
*CHECK_NOTNULL(info) = {.major = ROCATTACH_VERSION_MAJOR,
.minor = ROCATTACH_VERSION_MINOR,
.patch = ROCATTACH_VERSION_PATCH};
return ROCATTACH_STATUS_SUCCESS;
}
const char*
rocattach_get_status_name(rocattach_status_t status)
{
return rocprofiler::rocattach::get_status_name(
status, std::make_index_sequence<ROCATTACH_STATUS_LAST>{});
}
const char*
rocattach_get_status_string(rocattach_status_t status)
{
return rocprofiler::rocattach::get_status_string(
status, std::make_index_sequence<ROCATTACH_STATUS_LAST>{});
}
ROCATTACH_EXTERN_C_FINI