From aeb76dc902895043a6ca8b19e0944da605081db5 Mon Sep 17 00:00:00 2001 From: Sahil Faizal Date: Sat, 3 Oct 2026 10:45:02 -0700 Subject: [PATCH 1/3] Fix shared ASan runtime discovery and component loader paths --- .../github_actions/configure_asan_env.py | 23 ++++++++++++-- .../test_executable_scripts/test_hiptests.py | 13 ++------ .../test_rocprofiler_sdk.py | 27 ++-------------- .../tests/configure_asan_env_test.py | 31 ++++++++++++++++++- .../tests/rocprofiler_sdk_dashboard_test.py | 16 ++++++++++ 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/build_tools/github_actions/configure_asan_env.py b/build_tools/github_actions/configure_asan_env.py index bc024fed32d..765704e99f4 100644 --- a/build_tools/github_actions/configure_asan_env.py +++ b/build_tools/github_actions/configure_asan_env.py @@ -114,9 +114,28 @@ def _resolve_symbolizer(artifacts_dir: Path) -> tuple[Optional[Path], Optional[s return symbolizer.resolve(), None -def _resolve_library_path(artifacts_dir: Path) -> str: +def get_asan_runtime_path(artifacts_dir: Path) -> Path: + """Returns the configured runtime, or discovers it for standalone launchers.""" + configured = os.environ.get("ASAN_RUNTIME_PATH") + if configured: + runtime = Path(configured) + if not runtime.is_file(): + raise FileNotFoundError(f"ASAN_RUNTIME_PATH does not exist: {runtime}") + return runtime.resolve() + + runtime, warning = _resolve_asan_runtime(artifacts_dir) + if runtime is None: + raise FileNotFoundError(warning) + return runtime + + +def _resolve_library_path(artifacts_dir: Path, runtime: Path | None = None) -> str: lib_dir = (artifacts_dir / "lib").resolve() parts = [str(lib_dir), str(lib_dir / "rocm_sysdeps" / "lib")] + # -shared-libsan binaries and Python extensions need the clang resource + # directory as well as ROCm/lib, even when they do not preload ASan. + if runtime: + parts.append(str(runtime.parent)) existing = os.environ.get("LD_LIBRARY_PATH") if existing: parts.append(existing) @@ -140,7 +159,7 @@ def resolve_asan_env(artifacts_dir: Path) -> tuple[dict[str, str], list[str]]: if warning: warnings.append(warning) - env["LD_LIBRARY_PATH"] = _resolve_library_path(artifacts_dir) + env["LD_LIBRARY_PATH"] = _resolve_library_path(artifacts_dir, runtime) return env, warnings diff --git a/build_tools/github_actions/test_executable_scripts/test_hiptests.py b/build_tools/github_actions/test_executable_scripts/test_hiptests.py index 1f5d91faa53..6d3897b6b07 100644 --- a/build_tools/github_actions/test_executable_scripts/test_hiptests.py +++ b/build_tools/github_actions/test_executable_scripts/test_hiptests.py @@ -27,6 +27,7 @@ # Importing is_asan from amdgpu_family_matrix.py sys.path.append(str(THEROCK_DIR / "build_tools" / "github_actions")) from amdgpu_family_matrix import is_asan +from configure_asan_env import get_asan_runtime_path env = os.environ.copy() @@ -107,17 +108,7 @@ def get_asan_lib_path(): - arch = platform.machine() - CLANG_PATH = str(Path(THEROCK_BIN_DIR).parent / "lib" / "llvm" / "bin" / "clang++") - cmd = [f"{CLANG_PATH}", f"--print-file-name=libclang_rt.asan-{arch}.so"] - logging.info(f"++ Exec [{CLANG_PATH}]$ {shlex.join(cmd)}") - result = subprocess.run( - cmd, - check=True, - text=True, - capture_output=True, - ) - return result.stdout.strip() + return str(get_asan_runtime_path(THEROCK_BIN_DIR.resolve().parent)) def copy_dlls_exe_path(): diff --git a/build_tools/github_actions/test_executable_scripts/test_rocprofiler_sdk.py b/build_tools/github_actions/test_executable_scripts/test_rocprofiler_sdk.py index bb94de8d0ff..348acc74a0a 100644 --- a/build_tools/github_actions/test_executable_scripts/test_rocprofiler_sdk.py +++ b/build_tools/github_actions/test_executable_scripts/test_rocprofiler_sdk.py @@ -25,6 +25,7 @@ THEROCK_DIR = SCRIPT_DIR.parent.parent.parent sys.path.append(str(THEROCK_DIR / "build_tools" / "github_actions")) from amdgpu_family_matrix import is_asan +from configure_asan_env import get_asan_runtime_path # Base Paths THEROCK_BIN_DIR = os.getenv("THEROCK_BIN_DIR") @@ -97,29 +98,7 @@ def get_asan_runtime_library(): """Return the clang AddressSanitizer runtime path.""" - machine = platform.machine() - if machine in ("x86_64", "AMD64"): - arch = "x86_64" - elif machine == "aarch64": - arch = "aarch64" - else: - raise RuntimeError(f"Unsupported ASan runtime architecture: {machine}") - - asan_lib = f"libclang_rt.asan-{arch}.so" - result = subprocess.run( - [str(THEROCK_CLANG_PLUS_PATH), f"-print-file-name={asan_lib}"], - check=True, - capture_output=True, - text=True, - env=environ_vars, - ) - resolved = result.stdout.strip() - if not resolved or resolved == asan_lib or not Path(resolved).is_file(): - raise FileNotFoundError( - f"Could not locate ASan runtime '{asan_lib}' via {THEROCK_CLANG_PLUS_PATH} " - f"(got: '{resolved}')" - ) - return str(Path(resolved).resolve()) + return str(get_asan_runtime_path(THEROCK_PATH)) def setup_env(): @@ -134,7 +113,7 @@ def setup_env(): if is_asan(): # Installed test binaries are built with -shared-libsan, so the clang - # resource dir holding libclang_rt.asan-.so must be on the loader + # resource dir holding the ASan runtime must be on the loader # search path. Match rocprofiler-sdk sanitizer defaults for launchers. ld_lib_paths.append(str(Path(get_asan_runtime_library()).parent)) diff --git a/build_tools/github_actions/tests/configure_asan_env_test.py b/build_tools/github_actions/tests/configure_asan_env_test.py index 47493d7d994..18e69794e83 100644 --- a/build_tools/github_actions/tests/configure_asan_env_test.py +++ b/build_tools/github_actions/tests/configure_asan_env_test.py @@ -11,10 +11,16 @@ import tempfile import unittest from pathlib import Path +from unittest.mock import patch sys.path.insert(0, str(Path(__file__).parent.parent)) -from configure_asan_env import STATIC_ASAN_ENV, main, resolve_asan_env +from configure_asan_env import ( + STATIC_ASAN_ENV, + get_asan_runtime_path, + main, + resolve_asan_env, +) # The runtime is located by executing the artifact tree's clang, which the tests # stub with a shell script. Windows has no equivalent shebang mechanism. @@ -56,6 +62,24 @@ def test_resolves_runtime_and_symbolizer(self): self.assertEqual(warnings, []) self.assertTrue(env["ASAN_RUNTIME_PATH"].endswith("asan.so")) self.assertTrue(env["ASAN_SYMBOLIZER_PATH"].endswith("llvm-symbolizer")) + self.assertIn( + str(Path(env["ASAN_RUNTIME_PATH"]).parent), + env["LD_LIBRARY_PATH"].split(":"), + ) + + def test_launcher_uses_configured_runtime_without_clang(self): + with tempfile.TemporaryDirectory() as tmp: + runtime = Path(tmp) / "libclang_rt.asan.so" + runtime.touch() + with patch.dict(os.environ, {"ASAN_RUNTIME_PATH": str(runtime)}): + self.assertEqual(get_asan_runtime_path(Path(tmp)), runtime.resolve()) + + def test_launcher_rejects_missing_configured_runtime(self): + with tempfile.TemporaryDirectory() as tmp: + runtime = Path(tmp) / "missing.so" + with patch.dict(os.environ, {"ASAN_RUNTIME_PATH": str(runtime)}): + with self.assertRaisesRegex(FileNotFoundError, "ASAN_RUNTIME_PATH"): + get_asan_runtime_path(Path(tmp)) def test_static_values_are_always_exported(self): with tempfile.TemporaryDirectory() as tmp: @@ -118,6 +142,11 @@ def test_resolves_the_legacy_arch_suffixed_runtime(self): self.assertEqual(warnings, []) self.assertEqual(env["ASAN_RUNTIME_PATH"], str(runtime_path.resolve())) + with patch.dict(os.environ): + os.environ.pop("ASAN_RUNTIME_PATH", None) + self.assertEqual( + get_asan_runtime_path(artifacts), runtime_path.resolve() + ) @requires_posix def test_missing_runtime_file_warns(self): diff --git a/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py b/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py index 6aa6fe64786..26b44d61941 100644 --- a/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py +++ b/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py @@ -100,6 +100,22 @@ def test_asan_dashboard_preserves_mainline_configuration_and_exclusions(self): dashboard, ) + def test_asan_configure_uses_resolved_runtime(self): + # Per-target LLVM layouts use the unsuffixed runtime name. The runner + # must consume discovery rather than query an obsolete basename again. + with tempfile.TemporaryDirectory() as tmp: + runtime = Path(tmp) / "libclang_rt.asan.so" + runtime.touch() + with ( + patch.dict(os.environ, {"ASAN_RUNTIME_PATH": str(runtime)}), + patch.object(test_rocprofiler_sdk, "is_asan", return_value=True), + ): + command = test_rocprofiler_sdk.get_cmake_config_cmd() + self.assertIn( + f"-DROCPROFILER_MEMCHECK_PRELOAD_ENV=LD_PRELOAD={runtime.resolve()}", + command, + ) + def test_strict_submission_is_encoded_in_dashboard(self): with patch.object(test_rocprofiler_sdk, "is_asan", return_value=False): dashboard = self._generate(require_submission=True) From 6c7999dea22aaf88194bbe28eda3067be05a4104 Mon Sep 17 00:00:00 2001 From: Sahil Faizal Date: Wed, 7 Oct 2026 21:29:06 -0700 Subject: [PATCH 2/3] Reuse existing ASan coverage without adding test cases --- .../tests/configure_asan_env_test.py | 27 +------------------ .../tests/rocprofiler_sdk_dashboard_test.py | 24 +++++------------ 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/build_tools/github_actions/tests/configure_asan_env_test.py b/build_tools/github_actions/tests/configure_asan_env_test.py index 18e69794e83..796dcf429b6 100644 --- a/build_tools/github_actions/tests/configure_asan_env_test.py +++ b/build_tools/github_actions/tests/configure_asan_env_test.py @@ -11,16 +11,10 @@ import tempfile import unittest from pathlib import Path -from unittest.mock import patch sys.path.insert(0, str(Path(__file__).parent.parent)) -from configure_asan_env import ( - STATIC_ASAN_ENV, - get_asan_runtime_path, - main, - resolve_asan_env, -) +from configure_asan_env import STATIC_ASAN_ENV, main, resolve_asan_env # The runtime is located by executing the artifact tree's clang, which the tests # stub with a shell script. Windows has no equivalent shebang mechanism. @@ -67,20 +61,6 @@ def test_resolves_runtime_and_symbolizer(self): env["LD_LIBRARY_PATH"].split(":"), ) - def test_launcher_uses_configured_runtime_without_clang(self): - with tempfile.TemporaryDirectory() as tmp: - runtime = Path(tmp) / "libclang_rt.asan.so" - runtime.touch() - with patch.dict(os.environ, {"ASAN_RUNTIME_PATH": str(runtime)}): - self.assertEqual(get_asan_runtime_path(Path(tmp)), runtime.resolve()) - - def test_launcher_rejects_missing_configured_runtime(self): - with tempfile.TemporaryDirectory() as tmp: - runtime = Path(tmp) / "missing.so" - with patch.dict(os.environ, {"ASAN_RUNTIME_PATH": str(runtime)}): - with self.assertRaisesRegex(FileNotFoundError, "ASAN_RUNTIME_PATH"): - get_asan_runtime_path(Path(tmp)) - def test_static_values_are_always_exported(self): with tempfile.TemporaryDirectory() as tmp: env, _ = resolve_asan_env(Path(tmp) / "missing") @@ -142,11 +122,6 @@ def test_resolves_the_legacy_arch_suffixed_runtime(self): self.assertEqual(warnings, []) self.assertEqual(env["ASAN_RUNTIME_PATH"], str(runtime_path.resolve())) - with patch.dict(os.environ): - os.environ.pop("ASAN_RUNTIME_PATH", None) - self.assertEqual( - get_asan_runtime_path(artifacts), runtime_path.resolve() - ) @requires_posix def test_missing_runtime_file_warns(self): diff --git a/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py b/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py index 26b44d61941..ef88933fb8d 100644 --- a/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py +++ b/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py @@ -88,34 +88,22 @@ def test_asan_dashboard_preserves_mainline_configuration_and_exclusions(self): patch.object(test_rocprofiler_sdk, "is_asan", return_value=True), patch.object( test_rocprofiler_sdk, - "get_asan_runtime_library", - return_value="/tmp/libclang_rt.asan-x86_64.so", + "get_asan_runtime_path", + return_value=Path("/tmp/libclang_rt.asan.so"), ), ): dashboard = self._generate() self.assertIn("-DROCPROFILER_MEMCHECK=AddressSanitizer", dashboard) + self.assertIn( + "-DROCPROFILER_MEMCHECK_PRELOAD_ENV=LD_PRELOAD=/tmp/libclang_rt.asan.so", + dashboard, + ) self.assertIn( 'EXCLUDE "rocprofiler_sdk.unit.spm_core.check_packet_generation|', dashboard, ) - def test_asan_configure_uses_resolved_runtime(self): - # Per-target LLVM layouts use the unsuffixed runtime name. The runner - # must consume discovery rather than query an obsolete basename again. - with tempfile.TemporaryDirectory() as tmp: - runtime = Path(tmp) / "libclang_rt.asan.so" - runtime.touch() - with ( - patch.dict(os.environ, {"ASAN_RUNTIME_PATH": str(runtime)}), - patch.object(test_rocprofiler_sdk, "is_asan", return_value=True), - ): - command = test_rocprofiler_sdk.get_cmake_config_cmd() - self.assertIn( - f"-DROCPROFILER_MEMCHECK_PRELOAD_ENV=LD_PRELOAD={runtime.resolve()}", - command, - ) - def test_strict_submission_is_encoded_in_dashboard(self): with patch.object(test_rocprofiler_sdk, "is_asan", return_value=False): dashboard = self._generate(require_submission=True) From 4c3d1437336985f1384b78f8a9744e36fb875715 Mon Sep 17 00:00:00 2001 From: Sahil Faizal Date: Wed, 7 Oct 2026 21:47:41 -0700 Subject: [PATCH 3/3] Make reused ASan preload assertion portable --- .../github_actions/tests/rocprofiler_sdk_dashboard_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py b/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py index ef88933fb8d..aa101f0665a 100644 --- a/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py +++ b/build_tools/github_actions/tests/rocprofiler_sdk_dashboard_test.py @@ -93,11 +93,12 @@ def test_asan_dashboard_preserves_mainline_configuration_and_exclusions(self): ), ): dashboard = self._generate() + command = test_rocprofiler_sdk.get_cmake_config_cmd() self.assertIn("-DROCPROFILER_MEMCHECK=AddressSanitizer", dashboard) self.assertIn( - "-DROCPROFILER_MEMCHECK_PRELOAD_ENV=LD_PRELOAD=/tmp/libclang_rt.asan.so", - dashboard, + f"-DROCPROFILER_MEMCHECK_PRELOAD_ENV=LD_PRELOAD={Path('/tmp/libclang_rt.asan.so')}", + command, ) self.assertIn( 'EXCLUDE "rocprofiler_sdk.unit.spm_core.check_packet_generation|',