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:
+5
-4
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
+9
@@ -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
|
||||
+556
-591
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;
|
||||
}
|
||||
Reference in New Issue
Block a user