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>
This commit is contained in:
Mark Meserve
2026-01-15 14:32:14 -06:00
committed by GitHub
parent 2482bff0b7
commit 8760fb4976
26 changed files with 2426 additions and 795 deletions
@@ -80,19 +80,20 @@ class rocprofiler_sdk_profiler(RocProfCompute_Base):
})
if args.attach_pid:
# In attach mode, tools are provided using ROCP_TOOL_LIBRARIES
# In attach mode, tools are provided using ROCPROF_ATTACH_TOOL_LIBRARY
# instead of LD_PRELOAD.
options.update({
"ROCP_TOOL_LIBRARIES": ":".join(ld_preload),
"ROCPROF_ATTACH_TOOL_LIBRARY": ":".join(ld_preload),
})
options.pop("LD_PRELOAD", None)
rocprofiler_attach_tool_path = str(
rocprofiler_attach_library_path = str(
Path(args.rocprofiler_sdk_tool_path).parent.parent
/ "librocprofiler-sdk-rocattach.so"
)
options.update({
"ROCPROF_ATTACH_TOOL_LIBRARY": rocprofiler_attach_tool_path,
"ROCPROF_ATTACH_TOOL_LIBRARY": rocprofiler_sdk_tool_path,
"ROCPROF_ATTACH_LIBRARY": rocprofiler_attach_library_path,
"ROCPROF_ATTACH_PID": args.attach_pid,
})
@@ -803,7 +803,7 @@ def run_prof(
os.environ.update(original_env)
with temporary_env(new_env):
libname = options["ROCPROF_ATTACH_TOOL_LIBRARY"]
libname = options["ROCPROF_ATTACH_LIBRARY"]
c_lib = ctypes.CDLL(libname)
if c_lib is None:
console_error(f"Error opening {libname}")
@@ -829,7 +829,7 @@ def run_prof(
f"detach will happen in {duration} milliseconds...\033[0m"
)
time.sleep(int(duration) / 1000)
c_lib.detach()
c_lib.detach(int(pid))
else:
if app_cmd is None:
+1
View File
@@ -121,6 +121,7 @@ add_subdirectory(source)
include(rocprofiler_config_install)
include(rocprofiler_config_install_roctx)
include(rocprofiler_config_install_rocpd)
include(rocprofiler_config_install_rocattach)
include(rocprofiler_config_install_tests)
if(ROCPROFILER_BUILD_TESTS)
@@ -0,0 +1,9 @@
# Config file for @PACKAGE_NAME@ and its component libraries in the build tree
#
list(APPEND @PACKAGE_NAME@_INCLUDE_DIR @CMAKE_BINARY_DIR@/source/include)
foreach(COMP @PROJECT_BUILD_TREE_TARGETS@)
list(APPEND @PACKAGE_NAME@_LIBRARIES ${COMP})
target_link_libraries(@PACKAGE_NAME@::@PACKAGE_NAME@ INTERFACE ${COMP})
endforeach()
@@ -0,0 +1,73 @@
# - Config file for @PACKAGE_NAME@ and its component libraries
# It defines the following variables:
#
# @PACKAGE_NAME@_VERSION
# @PACKAGE_NAME@_INCLUDE_DIR
# @PACKAGE_NAME@_LIB_DIR
# @PACKAGE_NAME@_LIBRARIES
#
# It provides the following interface libraries:
#
# @PACKAGE_NAME@::@PACKAGE_NAME@
#
# prevent "target already exists" error
include_guard(DIRECTORY)
# compute paths
get_filename_component(@PACKAGE_NAME@_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
@PACKAGE_INIT@
set_and_check(@PACKAGE_NAME@_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
set_and_check(@PACKAGE_NAME@_LIB_DIR "@PACKAGE_LIB_INSTALL_DIR@")
get_filename_component(@PACKAGE_NAME@_ROOT_DIR ${@PACKAGE_NAME@_INCLUDE_DIR} PATH)
set_and_check(@PACKAGE_NAME@_ROOT "${@PACKAGE_NAME@_ROOT_DIR}")
# extra validation
foreach(_@PACKAGE_NAME@_SUBDIR @PROJECT_EXTRA_DIRS@)
set_and_check(_@PACKAGE_NAME@_SUBDIR_CHECK
"${PACKAGE_PREFIX_DIR}/${_@PACKAGE_NAME@_SUBDIR}")
unset(_@PACKAGE_NAME@_SUBDIR_CHECK)
endforeach()
set(@PACKAGE_NAME@_LIBRARIES)
# add interface library
add_library(@PACKAGE_NAME@::@PACKAGE_NAME@ INTERFACE IMPORTED)
if(@PACKAGE_NAME@_BUILD_TREE
AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/@PACKAGE_NAME@-build-config.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/@PACKAGE_NAME@-build-config.cmake")
else()
include("${@PACKAGE_NAME@_CMAKE_DIR}/@PACKAGE_NAME@-targets.cmake")
# Library dependencies
if(@PACKAGE_NAME@_FIND_COMPONENTS)
foreach(COMP ${@PACKAGE_NAME@_FIND_COMPONENTS})
set(TARG @PACKAGE_NAME@::@PACKAGE_NAME@-${COMP})
if(TARGET ${TARG})
set(@PACKAGE_NAME@_${COMP}_FOUND 1)
list(APPEND @PACKAGE_NAME@_LIBRARIES ${TARG})
target_link_libraries(@PACKAGE_NAME@::@PACKAGE_NAME@ INTERFACE ${TARG})
else()
set(@PACKAGE_NAME@_${COMP}_FOUND 0)
endif()
endforeach()
else()
foreach(TARG @PROJECT_BUILD_TARGETS@)
set(TARG @PACKAGE_NAME@::${TARG})
list(APPEND @PACKAGE_NAME@_LIBRARIES ${TARG})
target_link_libraries(@PACKAGE_NAME@::@PACKAGE_NAME@ INTERFACE ${TARG})
endforeach()
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
@PACKAGE_NAME@
FOUND_VAR @PACKAGE_NAME@_FOUND
VERSION_VAR @PACKAGE_NAME@_VERSION
REQUIRED_VARS @PACKAGE_NAME@_ROOT_DIR @PACKAGE_NAME@_INCLUDE_DIR
@PACKAGE_NAME@_LIB_DIR @PACKAGE_NAME@_LIBRARIES @PACKAGE_NAME@_VERSION
HANDLE_COMPONENTS HANDLE_VERSION_RANGE)
@@ -0,0 +1,106 @@
# include guard
include_guard(GLOBAL)
include(CMakePackageConfigHelpers)
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME rocattach)
set(SDK_PACKAGE_NAME "${PROJECT_NAME}")
set(PACKAGE_NAME "${PROJECT_NAME}-rocattach")
install(
EXPORT ${PACKAGE_NAME}-targets
FILE ${PACKAGE_NAME}-targets.cmake
NAMESPACE ${SDK_PACKAGE_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}
COMPONENT rocattach)
rocprofiler_install_env_setup_files(
NAME ${PACKAGE_NAME}
VERSION ${PROJECT_VERSION}
INSTALL_DIR ${CMAKE_INSTALL_DATAROOTDIR}
COMPONENT rocattach)
# ------------------------------------------------------------------------------#
# install tree
#
set(PROJECT_INSTALL_DIR ${CMAKE_INSTALL_PREFIX})
set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
set(LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR})
set(PROJECT_BUILD_TARGETS ${PACKAGE_NAME}-shared-library)
set(PROJECT_EXTRA_DIRS "${CMAKE_INSTALL_INCLUDEDIR}/${PACKAGE_NAME}")
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/Templates/${PACKAGE_NAME}/config.cmake.in
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}/${PACKAGE_NAME}-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}
INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}
PATH_VARS PROJECT_INSTALL_DIR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR)
write_basic_package_version_file(
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}/${PACKAGE_NAME}-config-version.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
install(
FILES
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}/${PACKAGE_NAME}-config.cmake
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}/${PACKAGE_NAME}-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}
COMPONENT rocattach)
export(PACKAGE ${PACKAGE_NAME})
# ------------------------------------------------------------------------------#
# build tree
#
set(${PACKAGE_NAME}_BUILD_TREE
ON
CACHE BOOL "" FORCE)
set(PROJECT_BUILD_TREE_TARGETS ${SDK_PACKAGE_NAME}::${PACKAGE_NAME}-shared-library
${SDK_PACKAGE_NAME}::${SDK_PACKAGE_NAME}-stack-protector)
configure_file(
${PROJECT_SOURCE_DIR}/cmake/Templates/${PACKAGE_NAME}/build-config.cmake.in
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}/${PACKAGE_NAME}-build-config.cmake
@ONLY)
file(RELATIVE_PATH rocp_bin2src_rel_path ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR})
string(REPLACE "//" "/" rocp_inc_rel_path "${rocp_bin2src_rel_path}/source/include")
set(_BUILDTREE_EXPORT_DIR
"${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}")
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${rocp_inc_rel_path}
${PROJECT_BINARY_DIR}/include WORKING_DIRECTORY ${PROJECT_BINARY_DIR})
if(NOT EXISTS "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
endif()
if(NOT EXISTS "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/${PACKAGE_NAME}")
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/${PACKAGE_NAME}")
endif()
if(NOT EXISTS "${_BUILDTREE_EXPORT_DIR}")
file(MAKE_DIRECTORY "${_BUILDTREE_EXPORT_DIR}")
endif()
if(NOT EXISTS "${_BUILDTREE_EXPORT_DIR}/${PACKAGE_NAME}-targets.cmake")
file(TOUCH "${_BUILDTREE_EXPORT_DIR}/${PACKAGE_NAME}-targets.cmake")
endif()
export(
EXPORT ${PACKAGE_NAME}-targets
NAMESPACE ${PACKAGE_NAME}::
FILE "${_BUILDTREE_EXPORT_DIR}/${PACKAGE_NAME}-targets.cmake")
set(${PACKAGE_NAME}_DIR
"${_BUILDTREE_EXPORT_DIR}"
CACHE PATH "${PACKAGE_NAME} build tree install" FORCE)
install(
FILES ${PROJECT_SOURCE_DIR}/LICENSE.md
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${PACKAGE_NAME}
COMPONENT rocattach)
@@ -22,6 +22,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import argparse
import ctypes
import os
import signal
@@ -30,27 +31,110 @@ import time
ROCPROF_ATTACH_DIR = os.path.dirname(os.path.realpath(__file__))
ROCM_DIR = os.path.dirname(ROCPROF_ATTACH_DIR)
ROCPROF_ATTACH_TOOL_LIBRARY = f"{ROCM_DIR}/lib/rocprofiler-sdk-rocattach.so"
ROCPROF_ATTACH_LIBRARY = f"{ROCM_DIR}/lib/librocprofiler-sdk-rocattach.so"
def main(
pid=os.environ.get("ROCPROF_ATTACH_PID", None),
attach_library=os.environ.get(
"ROCPROF_ATTACH_TOOL_LIBRARY", ROCPROF_ATTACH_TOOL_LIBRARY
),
duration=os.environ.get("ROCPROF_ATTACH_DURATION", None),
def parse_arguments(args=None):
def format_help(formatter, w=120, h=40):
"""Return a wider HelpFormatter, if possible."""
try:
kwargs = {"width": w, "max_help_position": h}
formatter(None, **kwargs)
return lambda prog: formatter(prog, **kwargs)
except TypeError:
return formatter
usage_examples = """
%(prog)s, e.g.
$ rocprof-attach -p <pid> -t <tool library> [-a <attach tool library> -d <msec duration>]
$ rocprof-attach -p 12345 -t path/to/your-tool-library.so -d 5000
"""
parser = argparse.ArgumentParser(
description="rocprofiler-sdk attachment profiler for custom tool libraries ",
usage="%(prog)s [options] ",
epilog=usage_examples,
formatter_class=format_help(argparse.RawTextHelpFormatter),
)
parser.add_argument(
"-p",
"--pid",
"--attach",
help="""Attachment target's process identifier (PID).
Can also be specified in environment variable ROCPROF_ATTACH_PID. This option overrides the environment variable if both are set.""",
type=int,
required=False,
default=os.environ.get("ROCPROF_ATTACH_PID", None),
)
parser.add_argument(
"-t",
"--attach-tool-library",
help="""Comma delimited list of tool libraries to use during attachment.
Can also be specified in environment variable ROCPROF_ATTACH_TOOL_LIBRARY. This option overrides the environment variable if both are set.""",
type=str,
required=False,
default=os.environ.get("ROCPROF_ATTACH_TOOL_LIBRARY", None),
)
parser.add_argument(
"-d",
"--attach-duration-msec",
help="""Sets the amount of time in milliseconds the profiler will be attached before detaching. When unset, the profiler will wait until Enter is pressed or SIGINT (Ctrl+C) to detach.
Can also be specified in environment variable ROCPROF_ATTACH_DURATION. This option overrides the environment variable if both are set.""",
type=int,
required=False,
default=os.environ.get("ROCPROF_ATTACH_DURATION", None),
)
advanced_options = parser.add_argument_group("Advanced options")
advanced_options.add_argument(
"--attach-library",
help="""Library used to attach and detach from the target process. Default will work for nearly all configurations.
Defaults to rocprofiler-sdk-rocattach.so from this ROCm install, i.e. <ROCmdirectory>/lib/rocprofiler-sdk-rocattach.so
Can also be specified in environment variable ROCPROF_ATTACH_LIBRARY. This option overrides the environment variable if both are set.""",
type=str,
required=False,
default=os.environ.get("ROCPROF_ATTACH_LIBRARY", ROCPROF_ATTACH_LIBRARY),
)
return parser.parse_args(args)
def attach(
pid,
attach_tool_library,
attach_duration_msec,
attach_library=ROCPROF_ATTACH_LIBRARY,
):
if pid is None:
raise RuntimeError("rocprof_attach called with no PID specified")
raise RuntimeError("rocprof-attach called with no PID specified")
if attach_tool_library is None:
raise RuntimeError("rocprof-attach called with no tool libraries specified")
tool_libraries_tokens = attach_tool_library.split(":")
for lib in tool_libraries_tokens:
if not os.path.exists(lib):
raise RuntimeError(f"rocprof-attach could not find tool library {lib}")
# Program option overrides environment variable. This is consumed by rocprofiler-sdk on the target program side.
os.environ["ROCPROF_ATTACH_TOOL_LIBRARY"] = attach_tool_library
print(f"Attaching to PID {pid} using library {attach_library}")
# Load the shared library into ctypes and attach
try:
c_lib = ctypes.CDLL(attach_library)
c_lib.attach.restype = ctypes.c_int
c_lib.attach.argtypes = [ctypes.c_uint]
attach_status = c_lib.attach(int(pid))
c_lib.rocattach_attach.restype = ctypes.c_int
c_lib.rocattach_attach.argtypes = [ctypes.c_int]
c_lib.rocattach_detach.restype = ctypes.c_int
c_lib.rocattach_detach.argtypes = [ctypes.c_int]
attach_status = c_lib.rocattach_attach(pid)
except Exception as e:
raise RuntimeError(f"Exception during library load and attachment: {e}")
@@ -65,7 +149,7 @@ def main(
print("Detaching. Please wait, this can take up to 1-2 minutes")
sys.stdout.flush()
try:
detach_status = c_lib.detach()
detach_status = c_lib.rocattach_detach(int(pid))
except Exception as e:
print(f"Exception during detachment: {e}")
@@ -83,17 +167,30 @@ def main(
signal.signal(signal.SIGINT, signal_handler)
if duration is None:
if attach_duration_msec is None:
sys.stdout.write("Press Enter to detach...")
sys.stdout.flush() # Force the prompt to appear immediately
input() # Now wait for input
else:
print(f"Attaching for {duration} msec...\n")
print(f"Attaching for {attach_duration_msec} msec...\n")
sys.stdout.flush()
time.sleep(int(duration) / 1000)
time.sleep(int(attach_duration_msec) / 1000)
detach()
def main(cmd_args=None):
args = parse_arguments(cmd_args)
attach(
pid=args.pid,
attach_tool_library=args.attach_tool_library,
attach_duration_msec=args.attach_duration_msec,
attach_library=args.attach_library,
)
return 0
if __name__ == "__main__":
main()
ec = main(sys.argv[1:])
sys.exit(ec)
@@ -1158,7 +1158,9 @@ def run(app_args, args, **kwargs):
ROCPROF_LIST_AVAIL_TOOL_LIBRARY = (
f"{ROCM_DIR}/lib/rocprofiler-sdk/librocprofv3-list-avail.so"
)
ROCPROF_ATTACH_TOOL_LIBRARY = f"{ROCM_DIR}/lib/librocprofiler-sdk-rocattach.so"
ROCPROF_ATTACH_TOOL_LIBRARY = (
f"{ROCM_DIR}/lib/rocprofiler-sdk/librocprofiler-sdk-tool.so"
)
ROCPROF_TOOL_LIBRARY = resolve_library_path(ROCPROF_TOOL_LIBRARY, args)
ROCPROF_SDK_LIBRARY = resolve_library_path(ROCPROF_SDK_LIBRARY, args)
@@ -13,35 +13,51 @@ Overview
This document provides the technical details needed to implement a process attachment tool similar to ``rocprofv3 --attach``. Process attachment allows profiling tools to dynamically attach to running GPU applications without requiring application restart. The implementation can use either the provided python or exported C functions.
Direct Python Execution
===================================
The python file ``rocprof-attach`` can be called directly to attach to a specific PID and use custom tools within the attachment target.
.. code-block:: bash
$ rocprof-attach -p 12345 -t path/to/your-tool-library.so -d 5000
In this example, the process with PID 12345 will be attached to and the library path/to/your-tool-library.so will be loaded by rocprofiler-sdk from within that process. After 5000 milliseconds have passed, detach will be called and ``rocprof-attach`` will exit when detachment is complete.
More information can be found by invoking ``rocprof-attach -h``
Python Functions
===================================
The python file ``rocprof-attach`` defines a main function that can be used for attachment:
The python file ``rocprof-attach`` defines an attach function that can be used for attachment:
.. code-block:: python
def main(
pid=os.environ.get("ROCPROF_ATTACH_PID", None),
attach_library=os.environ.get(
"ROCPROF_ATTACH_TOOL_LIBRARY", ROCPROF_ATTACH_TOOL_LIBRARY
),
duration=os.environ.get("ROCPROF_ATTACH_DURATION", None),
)
def attach(
pid,
attach_tool_library,
attach_duration_msec,
attach_library=ROCPROF_ATTACH_LIBRARY,
):
**Function Details**
The main function performs the entire attachment process, including attaching and detaching, and provides the ability to use custom tools. It also has simple control flow intended for direct calling from python. For more complex control, it is recommended to instead use the explicit attach and detach functions provided by the ``librocprofiler-sdk-rocattach.so`` binary.
The attach function performs the entire attachment process, including attaching and detaching, and provides the ability to use custom tools via the tool_libraries parameter. It also has simple control flow intended for direct calling from python. For more complex control, it is recommended to instead use the explicit attach and detach functions provided by the ``librocprofiler-sdk-rocattach.so`` binary.
**Parameters**
- **pid**: Required - PID of process to attach to
- Defaults to environment variable ROCPROF_ATTACH_PID
- **attach_library**: Optional - Colon delimited list of tool libraries to use
- **attach_tool_library**: Colon delimited list of tool libraries to use
- Defaults to environment variable ROCPROF_ATTACH_TOOL_LIBRARY
- If unspecified, defaults to the absolute path of librocprofiler-sdk-rocattach.so
- **duration**: Optional - Length of time in milliseconds to profile for
- **attach_duration_msec**: Optional - Length of time in milliseconds to profile for
- Defaults to environment variable ROCPROF_ATTACH_DURATION
- If unspecified, attachment will run until Enter is pressed or SIGINT (Ctrl+C) is received
- **attach_tool_library**: Optional - Tool library to use for attachment and detachment
- Default will work for nearly all applications
- Defaults to environment variable ROCPROF_ATTACH_LIBRARY
- If unspecified, defaults to the absolute path of librocprofiler-sdk-rocattach.so
C Functions
===================================
@@ -55,7 +71,7 @@ The C library ``librocprofiler-sdk-rocattach.so`` defines an attach and detach f
void attach(uint32_t pid) ROCPROFILER_EXPORT;
// Detach from target process and cleanup
void detach() ROCPROFILER_EXPORT;
void detach(uint32_t pid) ROCPROFILER_EXPORT;
}
**Function Details:**
@@ -65,8 +81,10 @@ The C library ``librocprofiler-sdk-rocattach.so`` defines an attach and detach f
- Initiates ptrace-based attachment sequence
- Custom tool libraries can be specified in a colon delimited list with the environment variable ROCPROF_ATTACH_TOOL_LIBRARY
- **detach()**: Entry point for detaching from the target process
- **detach(uint32_t pid)**: Entry point for detaching from the target process
- Takes the target process ID as a parameter
- Cleans up attachment resources and terminates profiling
- A PID of 0 can be specified to detach from all processes
Function Call Sequence
======================
@@ -4,5 +4,6 @@
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "development")
add_subdirectory(rocprofiler-sdk)
add_subdirectory(rocprofiler-sdk-rocattach)
add_subdirectory(rocprofiler-sdk-roctx)
add_subdirectory(rocprofiler-sdk-rocpd)
@@ -0,0 +1,18 @@
#
#
# Installation of public headers
#
#
configure_file(${CMAKE_CURRENT_LIST_DIR}/version.h.in
${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY)
set(ROCATTACH_HEADER_FILES
# core headers
rocattach.h
# secondary headers
defines.h types.h ${CMAKE_CURRENT_BINARY_DIR}/version.h)
install(
FILES ${ROCATTACH_HEADER_FILES}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rocprofiler-sdk-rocattach
COMPONENT rocattach)
@@ -0,0 +1,116 @@
// MIT License
//
// Copyright (c) 2025 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.
#pragma once
/**
* @defgroup SYMBOL_VERSIONING_GROUP Symbol Versions
*
* @brief The names used for the shared library versioned symbols.
*
* Every function is annotated with one of the version macros defined in this
* section. Each macro specifies a corresponding symbol version string. After
* dynamically loading the shared library with @p dlopen, the address of each
* function can be obtained using @p dlsym with the name of the function and
* its corresponding symbol version string. An error will be reported by @p
* dlvsym if the installed library does not support the version for the
* function specified in this version of the interface.
*
* @{
*/
/**
* @brief The function was introduced in version 0.0 of the interface and has the
* symbol version string of ``"ROCPROFILER_SDK_ROCATTACH_0.0"``.
*/
#define ROCPROFILER_SDK_ROCATTACH_VERSION_0_0
/** @} */
#if !defined(ROCATTACH_ATTRIBUTE)
# if defined(_MSC_VER)
# define ROCATTACH_ATTRIBUTE(...) __declspec(__VA_ARGS__)
# else
# define ROCATTACH_ATTRIBUTE(...) __attribute__((__VA_ARGS__))
# endif
#endif
#if !defined(ROCATTACH_PUBLIC_API)
# if defined(_MSC_VER)
# define ROCATTACH_PUBLIC_API ROCATTACH_ATTRIBUTE(dllexport)
# else
# define ROCATTACH_PUBLIC_API ROCATTACH_ATTRIBUTE(visibility("default"))
# endif
#endif
#if !defined(ROCATTACH_HIDDEN_API)
# if defined(_MSC_VER)
# define ROCATTACH_HIDDEN_API
# else
# define ROCATTACH_HIDDEN_API ROCATTACH_ATTRIBUTE(visibility("hidden"))
# endif
#endif
#if !defined(ROCATTACH_EXPORT_DECORATOR)
# define ROCATTACH_EXPORT_DECORATOR ROCATTACH_PUBLIC_API
#endif
#if !defined(ROCATTACH_IMPORT_DECORATOR)
# if defined(_MSC_VER)
# define ROCATTACH_IMPORT_DECORATOR ROCATTACH_ATTRIBUTE(dllimport)
# else
# define ROCATTACH_IMPORT_DECORATOR
# endif
#endif
#define ROCATTACH_EXPORT ROCATTACH_EXPORT_DECORATOR
#define ROCATTACH_IMPORT ROCATTACH_IMPORT_DECORATOR
#if !defined(ROCATTACH_API)
# if defined(ROCATTACH_EXPORTS)
# define ROCATTACH_API ROCATTACH_EXPORT
# else
# define ROCATTACH_API ROCATTACH_IMPORT
# endif
#endif
#if defined(__has_attribute)
# if __has_attribute(nonnull)
# define ROCATTACH_NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
# else
# define ROCATTACH_NONNULL(...)
# endif
#else
# if defined(__GNUC__)
# define ROCATTACH_NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
# else
# define ROCATTACH_NONNULL(...)
# endif
#endif
#ifdef __cplusplus
# define ROCATTACH_EXTERN_C_INIT extern "C" {
# define ROCATTACH_EXTERN_C_FINI }
#else
# define ROCATTACH_EXTERN_C_INIT
# define ROCATTACH_EXTERN_C_FINI
#endif
@@ -0,0 +1,136 @@
// MIT License
//
// Copyright (c) 2025 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.
#pragma once
/**
* @file rocattach.h
* @brief rocAttach API interface for AMD profiling data analysis
*
* @mainpage rocAttach API Specification
*
*/
#include "rocprofiler-sdk-rocattach/defines.h"
#include "rocprofiler-sdk-rocattach/types.h"
/**
* @defgroup VERSIONING_GROUP Library Versioning
* @brief Version information about the interface and the associated installed library.
*
* The semantic version of the interface following semver.org rules. A context
* that uses this interface is only compatible with the installed library if
* the major version numbers match and the interface minor version number is
* less than or equal to the installed library minor version number.
*
* @{
*/
#include "rocprofiler-sdk-rocattach/version.h"
ROCATTACH_EXTERN_C_INIT
/**
* @fn rocattach_status_t rocattach_get_version(uint32_t* major, uint32_t* minor, uint32_t*
* patch)
* @brief Query the version of the installed library.
*
* Returns the version of the rocprofiler-sdk library loaded at runtime. This can be used to check
* if the runtime version is equal to or compatible with the version of rocprofiler-sdk used during
* compilation time. This function can be invoked before tool initialization.
*
* @param [out] major The major version number is stored if non-NULL.
* @param [out] minor The minor version number is stored if non-NULL.
* @param [out] patch The patch version number is stored if non-NULL.
* @return ::rocattach_status_t
* @retval ::ROCATTACH_STATUS_SUCCESS Always returned
*/
rocattach_status_t
rocattach_get_version(uint32_t* major, uint32_t* minor, uint32_t* patch) ROCATTACH_API;
/**
* @brief Simplified alternative to ::rocattach_get_version
*
* Returns the version of the rocprofiler-sdk library loaded at runtime. This can be used to check
* if the runtime version is equal to or compatible with the version of rocprofiler-sdk used during
* compilation time. This function can be invoked before tool initialization.
*
* @param [out] info Pointer to version triplet struct which will be populated by the function call.
* @return ::rocattach_status_t
* @retval ::ROCATTACH_STATUS_SUCCESS Always returned
*/
rocattach_status_t
rocattach_get_version_triplet(rocattach_version_triplet_t* info) ROCATTACH_API ROCATTACH_NONNULL(1);
/**
* @brief Attach to a process ID
*
* Attempts to attach to a rocm process at the given process identifier (PID). If successful, the
* target process will then load rocprofiler-sdk, which will subsequently load any tool libraries
* given in the environment variable ROCP_TOOL_LIBRARIES.
*
* @param [in] pid Process ID to attach to
* @return ::rocattach_status_t
* @retval ::ROCATTACH_STATUS_SUCCESS Attachment successful
*/
rocattach_status_t
rocattach_attach(int pid) ROCATTACH_API;
/**
* @brief Detach a previous attachment
*
* Detaches from a previous
*
* @param [in] pid Process ID to detach from
* @return ::rocattach_status_t
* @retval ::ROCATTACH_STATUS_SUCCESS Attachment successful
*/
rocattach_status_t
rocattach_detach(int pid) ROCATTACH_API;
/**
* @defgroup MISCELLANEOUS_GROUP Miscellaneous Utility Functions
* @brief utility functions for library
* @{
*/
/**
* @fn const char* rocattach_get_status_name(rocattach_status_t status)
* @brief Return the string encoding of ::rocattach_status_t value
* @param [in] status error code value
* @return Will return a nullptr if invalid/unsupported ::rocattach_status_t value is provided.
*/
const char*
rocattach_get_status_name(rocattach_status_t status) ROCATTACH_API;
/**
* @fn const char* rocattach_get_status_string(rocattach_status_t status)
* @brief Return the message associated with ::rocattach_status_t value
* @param [in] status error code value
* @return Will return a nullptr if invalid/unsupported ::rocattach_status_t value is provided.
*/
const char*
rocattach_get_status_string(rocattach_status_t status) ROCATTACH_API;
/** @} */
ROCATTACH_EXTERN_C_FINI
@@ -0,0 +1,81 @@
// MIT License
//
// Copyright (c) 2025 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.
#pragma once
#include "rocprofiler-sdk-rocattach/defines.h"
#include <stdint.h>
/** @defgroup DATA_TYPE rocAttach Data types
*
* Data types defined or aliased by rocAttach
*
* @{
*/
//--------------------------------------------------------------------------------------//
//
// ENUMERATIONS
//
//--------------------------------------------------------------------------------------//
/**
* @defgroup BASIC_DATA_TYPES Basic data types
* @brief Basic data types and typedefs
*
* @{
*/
/**
* @brief Status codes.
*/
typedef enum rocattach_status_t // NOLINT(performance-enum-size)
{
ROCATTACH_STATUS_SUCCESS = 0, ///< No error occurred
ROCATTACH_STATUS_ERROR, ///< Generalized error
ROCATTACH_STATUS_ERROR_INVALID_ARGUMENT, ///< Invalid function argument
ROCATTACH_STATUS_ERROR_NOT_SUPPORTED, ///< Attachment is not supported on this platform
ROCATTACH_STATUS_ERROR_PTRACE_ERROR, ///< General ptrace error
ROCATTACH_STATUS_ERROR_PTRACE_OPERATION_NOT_PERMITTED, ///< ptrace returned EPERM, operation
///< not permitted
ROCATTACH_STATUS_ERROR_PTRACE_PROCESS_NOT_FOUND, ///< ptrace returned ESRCH, no such process
ROCATTACH_STATUS_LAST,
} rocattach_status_t;
//--------------------------------------------------------------------------------------//
//
// STRUCTS
//
//--------------------------------------------------------------------------------------//
/**
* @brief Versioning info.
*/
typedef struct rocattach_version_triplet_t
{
uint32_t major;
uint32_t minor;
uint32_t patch;
} rocattach_version_triplet_t;
/** @} */
@@ -0,0 +1,115 @@
// MIT License
//
// Copyright (c) 2025 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.
#pragma once
/**
* @def ROCATTACH_IS_ROCPROFILER_SDK
* @brief Preprocessor define indicating the rocattach header is a rocprofiler-sdk project
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_VERSION_MAJOR
* @brief The major version of the interface as a macro so it can be used
* by the preprocessor.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_VERSION_MINOR
* @brief The minor version of the interface as a macro so it can be used
* by the preprocessor.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_VERSION_PATCH
* @brief The patch version of the interface as a macro so it can be used
* by the preprocessor.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_VERSION
* @brief Numerically increasing version number encoding major, minor, and patch via
computing `((10000 * <MAJOR>) + (100 * <MINOR>) + <PATCH>)`.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_SOVERSION
* @brief Shared object versioning value whose value is at least `(10000 * <MAJOR>)`.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_VERSION_STRING
* @brief Version string in form: `<MAJOR>.<MINOR>.<PATCH>`.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_GIT_DESCRIBE
* @brief String encoding of `git describe --tags` when rocprofiler was built.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_GIT_REVISION
* @brief String encoding of `git rev-parse HEAD` when rocprofiler was built.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_LIBRARY_ARCH
* @brief Architecture triplet of rocprofiler build.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_SYSTEM_NAME
* @brief Target operating system for rocprofiler build, e.g. Linux.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_SYSTEM_PROCESSOR
* @brief Target architecture for rocprofiler build.
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_SYSTEM_VERSION
* @brief Version of the operating system which built rocprofiler
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_COMPILER_ID
* @brief C++ compiler identifier which built rocprofiler, e.g., GNU
* @addtogroup VERSIONING_GROUP
*
* @def ROCATTACH_COMPILER_VERSION
* @brief C++ compiler version which built rocprofiler
* @addtogroup VERSIONING_GROUP
*/
#define ROCATTACH_IS_ROCPROFILER_SDK 1
// clang-format off
#define ROCATTACH_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define ROCATTACH_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define ROCATTACH_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define ROCATTACH_SOVERSION (10000 * @PROJECT_VERSION_MAJOR@)
#define ROCATTACH_VERSION_STRING "@FULL_VERSION_STRING@"
#define ROCATTACH_GIT_DESCRIBE "@ROCPROFILER_SDK_GIT_DESCRIBE@"
#define ROCATTACH_GIT_REVISION "@ROCPROFILER_SDK_GIT_REVISION@"
// system info during compilation
#define ROCATTACH_LIBRARY_ARCH "@CMAKE_LIBRARY_ARCHITECTURE@"
#define ROCATTACH_SYSTEM_NAME "@CMAKE_SYSTEM_NAME@"
#define ROCATTACH_SYSTEM_PROCESSOR "@CMAKE_SYSTEM_PROCESSOR@"
#define ROCATTACH_SYSTEM_VERSION "@CMAKE_SYSTEM_VERSION@"
// compiler information
#define ROCATTACH_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@"
#define ROCATTACH_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@"
// clang-format on
#define ROCATTACH_VERSION \
((10000 * ROCATTACH_VERSION_MAJOR) + (100 * ROCATTACH_VERSION_MINOR) + ROCATTACH_VERSION_PATCH)
@@ -23,8 +23,15 @@
rocprofiler_activate_clang_tidy()
add_library(rocprofiler-sdk-rocattach-shared-library SHARED)
target_sources(rocprofiler-sdk-rocattach-shared-library PRIVATE rocattach.cpp
ptrace_session.cpp)
add_library(rocprofiler-sdk::rocprofiler-sdk-rocattach-shared-library ALIAS
rocprofiler-sdk-rocattach-shared-library)
target_sources(rocprofiler-sdk-rocattach-shared-library
PRIVATE rocattach.cpp auxv.cpp ptrace_session.cpp symbol_lookup.cpp)
target_include_directories(
rocprofiler-sdk-rocattach-shared-library
INTERFACE $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/source/include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/source/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(
rocprofiler-sdk-rocattach-shared-library
@@ -40,8 +47,10 @@ set_target_properties(
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
SOVERSION ${PROJECT_VERSION_MAJOR}
VERSION ${PROJECT_VERSION}
BUILD_RPATH "\$ORIGIN:\$ORIGIN/.."
INSTALL_RPATH "\$ORIGIN:\$ORIGIN/..")
SKIP_BUILD_RPATH OFF
BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN"
DEFINE_SYMBOL ROCATTACH_EXPORTS)
install(
TARGETS rocprofiler-sdk-rocattach-shared-library
@@ -0,0 +1,88 @@
// MIT License
//
// Copyright (c) 2025 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.
#include "lib/common/filesystem.hpp"
#include "lib/common/logging.hpp"
#include "auxv.hpp"
#include <vector>
namespace fs = rocprofiler::common::filesystem;
namespace rocprofiler
{
namespace rocattach
{
// When injecting assembly into a process, existing instructions are partially or completely
// overwritten. Other threads will continue running and may execute our injected code, resulting in
// illegal instructions, segmentation faults, or worse. A common technique to avoid this is to
// inject code at the program entry address, as this is extremely unlikely to be called again in a
// multithreaded process. To determine this address, we inspect the auxv file for the target
// process.
// Each entry in the auxv file will match this format.
typedef struct
{
uint64_t type;
void* value;
} auxv_pair_t;
rocattach_status_t
get_auxv_entry(int pid, void*& entry_addr)
{
constexpr int AT_ENTRY = 9; // Type number for program entry point
constexpr int auxv_pair_size = sizeof(auxv_pair_t);
auto filename = fs::path{"/proc"} / std::to_string(pid) / "auxv";
std::ifstream auxv(filename, std::ios::in | std::ios::binary);
if(!auxv.is_open())
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Unable to open auxv file " << filename;
return ROCATTACH_STATUS_ERROR;
}
std::vector<char> auxv_buffer(auxv_pair_size);
entry_addr = nullptr;
while(auxv.read(auxv_buffer.data(), auxv_pair_size) && entry_addr == nullptr)
{
auxv_pair_t* const aux = reinterpret_cast<auxv_pair_t*>(auxv_buffer.data());
if(aux->type == AT_ENTRY)
{
entry_addr = aux->value;
}
}
if(entry_addr == nullptr)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Unexpected mising AT_ENTRY for " << filename;
return ROCATTACH_STATUS_ERROR;
}
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Entry address found to be " << entry_addr << " from "
<< filename;
return ROCATTACH_STATUS_SUCCESS;
}
} // namespace rocattach
} // namespace rocprofiler
@@ -0,0 +1,35 @@
// MIT License
//
// Copyright (c) 2025 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.
#pragma once
#include <rocprofiler-sdk-rocattach/types.h>
namespace rocprofiler
{
namespace rocattach
{
rocattach_status_t
get_auxv_entry(int pid, void*& entry_addr);
}
} // namespace rocprofiler
File diff suppressed because it is too large Load Diff
@@ -22,6 +22,7 @@
#pragma once
#include <rocprofiler-sdk-rocattach/types.h>
#include <rocprofiler-sdk/rocprofiler.h>
#include <atomic>
@@ -35,7 +36,7 @@
namespace rocprofiler
{
namespace attach
namespace rocattach
{
class PTraceSession
{
@@ -43,45 +44,110 @@ public:
explicit PTraceSession(int);
~PTraceSession();
bool attach();
bool detach();
bool simple_mmap(void*& addr, size_t length) const;
bool simple_munmap(void*& addr, size_t length) const;
static bool is_supported();
bool write(size_t addr, const std::vector<uint8_t>& data, size_t size) const;
bool read(size_t addr, std::vector<uint8_t>& data, size_t size) const;
bool swap(size_t addr,
const std::vector<uint8_t>& in_data,
std::vector<uint8_t>& out_data,
size_t size) const;
rocattach_status_t attach();
rocattach_status_t detach();
rocattach_status_t simple_mmap(void*& addr, size_t length);
rocattach_status_t simple_munmap(void*& addr, size_t length);
rocattach_status_t write(size_t addr, const std::vector<uint8_t>& data, size_t size);
rocattach_status_t read(size_t addr, std::vector<uint8_t>& data, size_t size);
rocattach_status_t swap(size_t addr,
const std::vector<uint8_t>& in_data,
std::vector<uint8_t>& out_data,
size_t size);
int get_pid() const { return m_pid; }
bool call_function(const std::string& library, const std::string& symbol);
bool call_function(const std::string& library, const std::string& symbol, void* first);
bool call_function(const std::string& library,
const std::string& symbol,
void* first,
void* second);
bool stop() const;
bool cont() const;
bool handle_signals() const;
void detach_ptrace_session();
std::atomic<rocprofiler_status_t> m_setup_status = ROCPROFILER_STATUS_SUCCESS;
rocattach_status_t call_function(const std::string& library,
const std::string& symbol,
uint64_t& ret_value);
rocattach_status_t call_function(const std::string& library,
const std::string& symbol,
uint64_t& ret_value,
void* first);
rocattach_status_t call_function(const std::string& library,
const std::string& symbol,
uint64_t& ret_value,
void* first,
void* second);
private:
static bool find_library(void*& addr, int inpid, const std::string& library);
bool find_symbol(void*& addr, const std::string& library, const std::string& symbol);
rocattach_status_t start_signal_handler();
rocattach_status_t stop_signal_handler();
rocattach_status_t wait_for_breakpoint();
rocattach_status_t wait_for_stop();
std::unordered_map<std::string, void*> m_target_library_addrs = {};
std::unordered_map<std::string, void*> m_target_symbol_addrs = {};
rocattach_status_t stop();
rocattach_status_t cont();
rocattach_status_t write_internal(size_t addr,
const std::vector<uint8_t>& data,
size_t size) const;
rocattach_status_t read_internal(size_t addr, std::vector<uint8_t>& data, size_t size) const;
rocattach_status_t swap_internal(size_t addr,
const std::vector<uint8_t>& in_data,
std::vector<uint8_t>& out_data,
size_t size) const;
const int m_pid = -1;
bool m_attached = false;
std::atomic<bool> m_detaching_ptrace_session = false;
enum ptrace_session_state_t
{
// When the session class is created, this is the initial state
// Can transition to attached by calling attach()
PTRACE_SESSION_STATE_INITIAL = 0,
// State after attach()
// Most functions can be run in this state
// Can transition to stopped by calling stop()
// Can transition to detached by calling detach()
PTRACE_SESSION_STATE_RUNNING,
// State after stop()
// Required state for some internal functions, like write_internal
// Can transition to running by calling cont()
PTRACE_SESSION_STATE_STOPPED,
// State after detach()
// Class is left to be cleaned up
PTRACE_SESSION_STATE_DETACHED
};
// This is the state of the signal handler thread. This thread intercepts signals that the
// attachee is sent. Based on the current state, these signals are either consumed by the
// attacher or forwarded to the attachee. Typically, the main thread updates this state. The
// main thread can update this state only if the program is stopped (i.e. stop() has been
// called). The main thread must also verify the state is not FINAL using a check-and-set
// operation. The signal handler will initially transition the state from INITIAL to ATTACHED.
// Otherwise, the signal handler update thread will only update this state to FINAL. This occurs
// when the target program ends, an unexpected error occurs, or if a stop has been requested by
// the main program (by transitioning this state to DETACHING). The main thread can check the
// error state for more information.
enum ptrace_session_signal_handler_state_t
{
PTRACE_SIGNAL_HANDLER_STATE_INITIAL = 0,
// The signal handler is running normally. All signals are forwarded to the attachee.
PTRACE_SIGNAL_HANDLER_STATE_ATTACHED,
// The signal handler is running normally. A WUNTRACED stop will transition the state to
// ATTACHED. All other signals are forwarded to the attachee.
PTRACE_SIGNAL_HANDLER_STATE_WAITING_FOR_BREAKPOINT,
// The signal handler will end operation. State is transitioned to FINAL when complete.
PTRACE_SIGNAL_HANDLER_STATE_DETACHING,
// The signal handler is not running, either by request or because some error has occurred.
// If an error occurred, more information will be in m_signal_handler_error
PTRACE_SIGNAL_HANDLER_STATE_FINAL
};
static void ptrace_signal_handler_func(
int pid,
std::atomic<ptrace_session_signal_handler_state_t>& state,
std::atomic<rocattach_status_t>& error);
ptrace_session_state_t m_state = PTRACE_SESSION_STATE_INITIAL;
std::atomic<ptrace_session_signal_handler_state_t> m_ptrace_signal_handler_state =
PTRACE_SIGNAL_HANDLER_STATE_INITIAL;
std::atomic<rocattach_status_t> m_ptrace_signal_handler_error = ROCATTACH_STATUS_SUCCESS;
std::thread m_ptrace_signal_handler_thread;
const int m_pid = -1;
};
} // namespace attach
} // namespace rocattach
} // namespace rocprofiler
@@ -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
@@ -0,0 +1,260 @@
// MIT License
//
// Copyright (c) 2025 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.
#include "symbol_lookup.hpp"
#include "lib/common/filesystem.hpp"
#include "lib/common/logging.hpp"
#include <dlfcn.h>
#include <link.h>
#include <optional>
#include <unordered_map>
#include <vector>
namespace fs = rocprofiler::common::filesystem;
namespace rocprofiler
{
namespace rocattach
{
namespace
{
constexpr char ROCATTACH_LIBRARY_NAME[] = "librocprofiler-sdk-rocattach.so.1";
std::unordered_map<std::string, void*> m_target_library_addrs = {};
std::unordered_map<std::string, void*> m_target_symbol_addrs = {};
using open_modes_vec_t = std::vector<int>;
std::optional<std::string>
get_linked_path(std::string_view _name, open_modes_vec_t&& _open_modes)
{
const open_modes_vec_t default_link_open_modes = {(RTLD_LAZY | RTLD_NOLOAD)};
if(_name.empty()) return fs::current_path().string();
if(_open_modes.empty()) _open_modes = default_link_open_modes;
void* _handle = nullptr;
bool _noload = false;
for(auto _mode : _open_modes)
{
_handle = dlopen(_name.data(), _mode);
_noload = (_mode & RTLD_NOLOAD) == RTLD_NOLOAD;
if(_handle) break;
}
if(_handle)
{
struct link_map* _link_map = nullptr;
dlinfo(_handle, RTLD_DI_LINKMAP, &_link_map);
if(_link_map != nullptr && !std::string_view{_link_map->l_name}.empty())
{
return fs::absolute(fs::path{_link_map->l_name}).string();
}
if(_noload == false) dlclose(_handle);
}
return std::nullopt;
}
auto
get_this_library_path()
{
auto _this_lib_path = get_linked_path(ROCATTACH_LIBRARY_NAME, {RTLD_NOLOAD | RTLD_LAZY});
LOG_IF(FATAL, !_this_lib_path) << "[rocprofiler-sdk-rocattach] " << ROCATTACH_LIBRARY_NAME
<< " could not locate itself in the list of loaded libraries";
return fs::path{*_this_lib_path}.parent_path().string();
}
void*
get_library_handle(std::string_view _lib_name)
{
void* _lib_handle = nullptr;
if(_lib_name.empty()) return nullptr;
auto _lib_path = fs::path{_lib_name};
auto _lib_path_fname = _lib_path.filename();
auto _lib_path_abs =
(_lib_path.is_absolute()) ? _lib_path : (fs::path{get_this_library_path()} / _lib_path);
// check to see if the rocprofiler library is already loaded
_lib_handle = dlopen(_lib_path.c_str(), RTLD_NOLOAD | RTLD_LAZY);
if(_lib_handle)
{
LOG(INFO) << "[rocprofiler-sdk-rocattach] loaded " << _lib_name << " library at "
<< _lib_path.string() << " (handle=" << _lib_handle
<< ") via RTLD_NOLOAD | RTLD_LAZY";
}
// try to load with the given path
if(!_lib_handle)
{
_lib_handle = dlopen(_lib_path.c_str(), RTLD_GLOBAL | RTLD_LAZY);
if(_lib_handle)
{
LOG(INFO) << "[rocprofiler-sdk-rocattach] loaded " << _lib_name << " library at "
<< _lib_path.string() << " (handle=" << _lib_handle
<< ") via RTLD_GLOBAL | RTLD_LAZY";
}
}
// try to load with the absoulte path
if(!_lib_handle)
{
_lib_path = _lib_path_abs;
_lib_handle = dlopen(_lib_path.c_str(), RTLD_GLOBAL | RTLD_LAZY);
}
// try to load with the basename path
if(!_lib_handle)
{
_lib_path = _lib_path_fname;
_lib_handle = dlopen(_lib_path.c_str(), RTLD_GLOBAL | RTLD_LAZY);
}
LOG(INFO) << "[rocprofiler-sdk-rocattach] loaded " << _lib_name << " library at "
<< _lib_path.string() << " (handle=" << _lib_handle << ")";
LOG_IF(WARNING, _lib_handle == nullptr) << _lib_name << " failed to load\n";
return _lib_handle;
}
} // namespace
bool
find_library(void*& addr, int inpid, const std::string& library)
{
std::stringstream searchname;
searchname << inpid << "::" << library;
// TODO: add this back
// if (target_library_addrs.find(searchname.str()) != target_library_addrs.end())
//{
// return target_library_addrs[searchname.str()];
//}
// uses "maps" file to find where library has been loaded in target process
// does not require this process to be attached
std::stringstream filename;
filename << "/proc/" << inpid << "/maps";
std::ifstream maps(filename.str().c_str());
if(!maps)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Couldn't open " << filename.str();
return false;
}
std::string line;
while(std::getline(maps, line))
{
if(line.find(library) != std::string::npos)
{
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Entry in pid " << inpid
<< " maps file is: " << line;
break;
}
}
if(!maps)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Couldn't find library " << library << " in "
<< filename.str();
return false;
}
// NOLINTNEXTLINE(performance-no-int-to-ptr)
addr = reinterpret_cast<void*>(std::stoull(line, nullptr, 16));
// target_library_addrs[searchname.str()] = addr;
return true;
}
bool
find_symbol(int target_pid, void*& addr, const std::string& library, const std::string& symbol)
{
auto searchname = std::stringstream{};
searchname << target_pid << "::" << library << "::" << symbol;
if(auto itr = m_target_symbol_addrs.find(searchname.str()); itr != m_target_symbol_addrs.end())
{
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Found symbol for " << searchname.str() << " at "
<< itr->second;
return itr->second != nullptr;
}
void* libraryaddr = nullptr;
void* symboladdr = nullptr;
// Load the library in our process to determine the offset of the requested symbol from the
// start address of the library
addr = nullptr;
libraryaddr = get_library_handle(library);
if(!libraryaddr)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Host couldn't dlopen " << library;
return false;
}
symboladdr = dlsym(libraryaddr, symbol.c_str());
if(!symboladdr)
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Host couldn't dlsym " << symbol;
return false;
}
// Find the start address of the library in our process
void* hostlibraryaddr;
if(!find_library(hostlibraryaddr, getpid(), library))
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Couldn't determine where " << library
<< " was loaded for host";
return false;
}
// Caluclate the offset
size_t offset =
reinterpret_cast<size_t>(symboladdr) - reinterpret_cast<size_t>(hostlibraryaddr);
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Offset of " << symbol << " into " << library
<< " calculated as " << offset;
// Find the start address of the library in the target process
void* targetlibraryaddr;
if(!find_library(targetlibraryaddr, target_pid, library))
{
ROCP_ERROR << "[rocprofiler-sdk-rocattach] Couldn't determine where " << library
<< " was loaded for target";
return false;
}
// Calculate address of symbol in the target process using the offset
// NOLINTNEXTLINE(performance-no-int-to-ptr)
addr = reinterpret_cast<void*>(reinterpret_cast<size_t>(targetlibraryaddr) + offset);
m_target_symbol_addrs[searchname.str()] = addr;
ROCP_TRACE << "[rocprofiler-sdk-rocattach] Found symbol for " << searchname.str() << " at "
<< addr;
return true;
}
} // namespace rocattach
} // namespace rocprofiler
@@ -0,0 +1,38 @@
// MIT License
//
// Copyright (c) 2025 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.
#pragma once
#include <string>
namespace rocprofiler
{
namespace rocattach
{
bool
find_library(void*& addr, int inpid, const std::string& library);
bool
find_symbol(int target_pid, void*& addr, const std::string& library, const std::string& symbol);
} // namespace rocattach
} // namespace rocprofiler
@@ -92,6 +92,9 @@ add_subdirectory(code-object-multi-threaded)
# rocpd validation tests
add_subdirectory(rocpd)
# rocattach validation tests
add_subdirectory(rocattach)
# rocprofv3 validation tests
add_subdirectory(rocprofv3)
@@ -0,0 +1,68 @@
#
# rocprofv3 tool test
#
cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
project(
rocprofiler-sdk-tests-rocattach
LANGUAGES CXX
VERSION 0.0.0)
find_package(rocprofiler-sdk REQUIRED)
find_package(rocprofiler-sdk-rocattach REQUIRED)
# TODO: sanitizer tests do not cooperate with attachment code injection
set(ROCPROFILER_MEMCHECK_TYPES "AddressSanitizer" "UndefinedBehaviorSanitizer"
"ThreadSanitizer")
if(ROCPROFILER_MEMCHECK AND ROCPROFILER_MEMCHECK IN_LIST ROCPROFILER_MEMCHECK_TYPES)
set(IS_DISABLED ON)
else()
set(IS_DISABLED OFF)
endif()
if(ROCPROFILER_MEMCHECK STREQUAL "LeakSanitizer")
set(LOG_LEVEL "warning") # info produces memory leak
else()
set(LOG_LEVEL "trace")
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(rocattach-parallel-attach-test)
target_sources(rocattach-parallel-attach-test PRIVATE main.cpp)
target_link_libraries(
rocattach-parallel-attach-test
PRIVATE rocprofiler-sdk::rocprofiler-sdk rocprofiler-sdk::tests-build-flags
rocprofiler-sdk::tests-common-library
rocprofiler-sdk::rocprofiler-sdk-rocattach-shared-library)
add_test(NAME rocattach-parallel-attach-test-execute
COMMAND $<TARGET_FILE:rocattach-parallel-attach-test>
$<TARGET_FILE:attachment-test> $<TARGET_FILE:rocprofiler-sdk-c-tool>)
set(rocattach-test-env
"${ROCPROFILER_MEMCHECK_PRELOAD_ENV}"
"ROCP_TOOL_ATTACH=1"
"ROCPROFILER_REGISTER_LOG_LEVEL=${LOG_LEVEL}"
"ROCPROFILER_LOG_LEVEL=${LOG_LEVEL}"
"ROCATTACH_LOG_LEVEL=${LOG_LEVEL}"
"LD_LIBRARY_PATH=$<TARGET_FILE_DIR:rocprofiler-sdk::rocprofiler-sdk-shared-library>:$ENV{LD_LIBRARY_PATH}"
)
set_tests_properties(
rocattach-parallel-attach-test-execute
PROPERTIES TIMEOUT
45
LABELS
"integration-tests"
ENVIRONMENT
"${rocattach-test-env}"
PASS_REGULAR_EXPRESSION
"Test C tool \\(priority=0\\) is using rocprofiler-sdk v"
FAIL_REGULAR_EXPRESSION
"${ROCPROFILER_DEFAULT_FAIL_REGEX}"
DISABLED
"${IS_DISABLED}")
@@ -0,0 +1,124 @@
// MIT License
//
// Copyright (c) 2025 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.
#include <rocprofiler-sdk-rocattach/defines.h>
#include <rocprofiler-sdk-rocattach/rocattach.h>
#include <rocprofiler-sdk-rocattach/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <iostream>
#include <sstream>
#include <thread>
#define ROCATTACH_CALL(func) \
{ \
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; \
} \
}
int
main(int argc, char** argv)
{
if(argc != 3)
{
std::cout << "error: wrong number of arguments\n";
return 1;
}
pid_t pid1 = fork();
if(pid1 < 0)
{
std::cout << "error: Fork 1 failed.\n";
return 1;
}
pid_t pid2 = 0;
if(pid1 > 0)
{
// Parent process, will fork again to spawn 2 processes
pid2 = fork();
}
if(pid2 < 0)
{
std::cout << "error: Fork 2 failed.\n";
return 1;
}
if(pid1 == 0 || pid2 == 0)
{
// Child process
std::cout << "child executing " << argv[1] << std::endl;
int ret = execl(argv[1], argv[1], nullptr);
if(ret == -1)
{
std::cout << "error in execl(), errno=" << errno << std::endl;
return 1;
}
}
else
{
// Wait a small amount of time for child processes to start executing
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
setenv("ROCPROF_ATTACH_TOOL_LIBRARY", argv[2], true);
ROCATTACH_CALL(rocattach_attach(pid1));
ROCATTACH_CALL(rocattach_attach(pid2));
// Wait a small amount of time for child processes to continue executing
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
ROCATTACH_CALL(rocattach_detach(pid1));
ROCATTACH_CALL(rocattach_detach(pid2));
int pid1status = 0;
waitpid(pid1, &pid1status, 0);
int pid2status = 0;
waitpid(pid2, &pid2status, 0);
if(pid1status != 0)
{
std::cout << "error in pid1, returned non-zero status: " << pid1status;
return 1;
}
if(pid2status != 0)
{
std::cout << "error in pid2, returned non-zero status: " << pid2status;
return 1;
}
}
return 0;
}