From e4e590834dbd6f63361660dc14ac8120d2034236 Mon Sep 17 00:00:00 2001 From: "Balcer, Piotr" Date: Sun, 16 Aug 2026 13:54:44 +0200 Subject: [PATCH] [UR] Make the device sanitizer layer a shared library The loader can be statically linked into more than one library of a process - an OpenMP offload application using SYCL interop loads both libomptarget and libsycl, each with its own copy of libur_loader.a. The layer state is hidden, so the linker cannot merge those copies, and the sanitizers ended up with two independent states over one device. Both reserved device shadow memory, and the second reservation failed with UR_RESULT_ERROR_OUT_OF_DEVICE_MEMORY. Move the sanitizer layer into libur_sanitizer_layer.so, loaded on demand from the same locations an adapter is loaded from, so all loader instances of a process share one instance of it. Every loader still initializes the layer with its own ddi table; the layer reference counts those and hooks up the extra tables via context_t::interceptDdiTable instead of setting up a second state. The library exports nothing but urLoaderLayerGetInterface, which also makes the ld -r/objcopy pre-link that hid the LLVM symbolizer symbols inside the static loader unnecessary - they are now private to this library by construction. Enabling a layer whose library cannot be loaded now fails urLoaderInit rather than silently running unsanitized. The generic half of this lives in ur_loader::SharedLayer so that other layers can be moved out of the loader the same way. --- sycl/cmake/modules/BuildUnifiedRuntime.cmake | 9 ++ unified-runtime/source/loader/CMakeLists.txt | 92 +------------- .../loader/layers/sanitizer/CMakeLists.txt | 119 ++++++++++++++++++ .../loader/layers/sanitizer/asan/asan_ddi.cpp | 3 - .../loader/layers/sanitizer/msan/msan_ddi.cpp | 2 - .../layers/sanitizer/sanitizer_layer.map | 6 + .../loader/layers/sanitizer/tsan/tsan_ddi.cpp | 2 - .../loader/layers/sanitizer/ur_sanddi.cpp | 15 +++ .../layers/sanitizer/ur_sanitizer_layer.hpp | 3 + .../sanitizer/ur_sanitizer_layer_entry.cpp | 118 +++++++++++++++++ .../sanitizer/ur_sanitizer_layer_proxy.hpp | 36 ++++++ .../source/loader/layers/ur_layer_interface.h | 56 +++++++++ .../source/loader/layers/ur_shared_layer.cpp | 116 +++++++++++++++++ .../source/loader/layers/ur_shared_layer.hpp | 55 ++++++++ unified-runtime/source/loader/ur_lib.cpp | 16 ++- unified-runtime/source/loader/ur_lib.hpp | 10 +- .../test/layers/sanitizer/CMakeLists.txt | 6 + .../test/layers/sanitizer/shared_layer.cpp | 105 ++++++++++++++++ 18 files changed, 665 insertions(+), 104 deletions(-) create mode 100644 unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt create mode 100644 unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map create mode 100644 unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp create mode 100644 unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp create mode 100644 unified-runtime/source/loader/layers/ur_layer_interface.h create mode 100644 unified-runtime/source/loader/layers/ur_shared_layer.cpp create mode 100644 unified-runtime/source/loader/layers/ur_shared_layer.hpp create mode 100644 unified-runtime/test/layers/sanitizer/shared_layer.cpp diff --git a/sycl/cmake/modules/BuildUnifiedRuntime.cmake b/sycl/cmake/modules/BuildUnifiedRuntime.cmake index 5cdf378bbe958..1160d21d716e8 100644 --- a/sycl/cmake/modules/BuildUnifiedRuntime.cmake +++ b/sycl/cmake/modules/BuildUnifiedRuntime.cmake @@ -132,6 +132,15 @@ if(TARGET UnifiedRuntimeLoader) ARCHIVE DESTINATION "lib${LLVM_LIBDIR_SUFFIX}" COMPONENT unified-runtime-loader RUNTIME DESTINATION "bin" COMPONENT unified-runtime-loader ) + + # The loader dlopen()s its shared layers from its own directory, so they have + # to be installed alongside it. + if(TARGET ur_sanitizer_layer) + install(TARGETS ur_sanitizer_layer + LIBRARY DESTINATION "lib${LLVM_LIBDIR_SUFFIX}" COMPONENT unified-runtime-loader + RUNTIME DESTINATION "bin" COMPONENT unified-runtime-loader + ) + endif() endif() add_custom_target(UnifiedRuntimeAdapters) diff --git a/unified-runtime/source/loader/CMakeLists.txt b/unified-runtime/source/loader/CMakeLists.txt index eb59c85686fb6..7bf82ddd7cd87 100644 --- a/unified-runtime/source/loader/CMakeLists.txt +++ b/unified-runtime/source/loader/CMakeLists.txt @@ -163,98 +163,14 @@ if(UR_ENABLE_TRACING) endif() if(UR_ENABLE_SANITIZER) + # The sanitizer layer is a shared library of its own that the loader loads on + # demand through a proxy - see layers/ur_shared_layer.hpp. target_sources(ur_loader PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../ur/ur.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_allocator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_allocator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_buffer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_buffer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_ddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_ddi.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_interceptor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_interceptor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_quarantine.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_quarantine.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_report.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_report.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_shadow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_shadow.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_statistics.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_statistics.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_validator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_validator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_allocator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_allocator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_buffer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_buffer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_ddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_ddi.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_interceptor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_interceptor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_origin.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_report.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_report.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_shadow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_shadow.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_buffer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_buffer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_ddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_ddi.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_interceptor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_interceptor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_report.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_report.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_shadow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_shadow.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/linux/backtrace.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/linux/sanitizer_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_allocator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_allocator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_common.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_options.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_options.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stackdepot.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stackdepot.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stacktrace.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stacktrace.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/ur_sanddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/ur_sanitizer_layer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/ur_sanitizer_layer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/ur_shared_layer.cpp ) - if(UR_ENABLE_SYMBOLIZER) - set(symbolizer_sources - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/linux/symbolizer.cpp - ) - target_sources(ur_loader - PRIVATE ${symbolizer_sources} - ) - target_include_directories(ur_loader PRIVATE ${LLVM_INCLUDE_DIRS}) - target_link_libraries(ur_loader PRIVATE LLVMSupport LLVMSymbolize) - target_compile_definitions(ur_loader PRIVATE UR_HAVE_SYMBOLIZER) - # In in-tree build, if LLVM is built with libc++, we also need to build - # symbolizer.cpp with libc++ abi and link libc++ in. - if(NOT UR_STANDALONE_BUILD AND LLVM_LIBCXX_USED) - set_property(SOURCE - ${symbolizer_sources} - APPEND_STRING PROPERTY COMPILE_FLAGS - " -stdlib=libc++ ") - # Link with gcc_s fisrt to avoid some symbols resolve to libc++/libc++abi/libunwind's one - target_link_libraries(ur_loader PRIVATE gcc_s ${LIBCXX_PATH} ${LIBCXX_ABI_PATH}) - endif() - endif() - - target_include_directories(ur_loader PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer" - "${CMAKE_CURRENT_SOURCE_DIR}/../" - ) + add_subdirectory(layers/sanitizer) endif() if(WIN32) diff --git a/unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt b/unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt new file mode 100644 index 0000000000000..63829b848c09f --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt @@ -0,0 +1,119 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# A shared library rather than a part of the loader, so that all loader +# instances of a process share one instance of it - see ur_shared_layer.hpp. +# Loaded on demand from the same locations an adapter is, and exports nothing +# but urLoaderLayerGetInterface, which keeps the LLVM symbolizer private too. + +add_ur_library(ur_sanitizer_layer SHARED + ${PROJECT_SOURCE_DIR}/source/ur/ur.cpp + asan/asan_allocator.cpp + asan/asan_buffer.cpp + asan/asan_ddi.cpp + asan/asan_interceptor.cpp + asan/asan_quarantine.cpp + asan/asan_report.cpp + asan/asan_shadow.cpp + asan/asan_statistics.cpp + asan/asan_validator.cpp + msan/msan_allocator.cpp + msan/msan_buffer.cpp + msan/msan_ddi.cpp + msan/msan_interceptor.cpp + msan/msan_report.cpp + msan/msan_shadow.cpp + tsan/tsan_buffer.cpp + tsan/tsan_ddi.cpp + tsan/tsan_interceptor.cpp + tsan/tsan_report.cpp + tsan/tsan_shadow.cpp + sanitizer_common/linux/backtrace.cpp + sanitizer_common/linux/sanitizer_utils.cpp + sanitizer_common/sanitizer_allocator.cpp + sanitizer_common/sanitizer_options.cpp + sanitizer_common/sanitizer_stackdepot.cpp + sanitizer_common/sanitizer_stacktrace.cpp + sanitizer_common/sanitizer_utils.cpp + ur_sanddi.cpp + ur_sanitizer_layer.cpp + ur_sanitizer_layer_entry.cpp +) +install_ur_library(ur_sanitizer_layer) + +target_include_directories(ur_sanitizer_layer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/source + ${PROJECT_SOURCE_DIR}/source/loader + ${PROJECT_SOURCE_DIR}/source/loader/layers +) + +target_link_libraries(ur_sanitizer_layer PRIVATE + ${PROJECT_NAME}::common + ${PROJECT_NAME}::headers +) + +set_target_properties(ur_sanitizer_layer PROPERTIES + VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" + SOVERSION "${PROJECT_VERSION_MAJOR}" + # BUILD_WITH_INSTALL_RPATH is intentionally not set, see + # source/adapters/CMakeLists.txt. + INSTALL_RPATH "\$ORIGIN" +) + +target_link_options(ur_sanitizer_layer PRIVATE + "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer_layer.map" +) + +if(UR_ENABLE_SYMBOLIZER) + target_sources(ur_sanitizer_layer PRIVATE + sanitizer_common/linux/symbolizer.cpp + ) + target_compile_definitions(ur_sanitizer_layer PRIVATE UR_HAVE_SYMBOLIZER) + target_include_directories(ur_sanitizer_layer PRIVATE ${LLVM_INCLUDE_DIRS}) + target_link_libraries(ur_sanitizer_layer PRIVATE LLVMSupport LLVMSymbolize) + + # The symbolizer dependencies may emit calls to + # _intel_fast_memcpy/_intel_fast_memset, normally provided by libirc, which + # this library doesn't link. The stubs forward to plain memcpy/memset. + if(CMAKE_C_COMPILER_ID STREQUAL "IntelLLVM") + if(MSVC) + set(_no_intel_lib_flag -Qno-intel-lib=libirc) + else() + set(_no_intel_lib_flag -no-intel-lib=libirc) + endif() + set_source_files_properties( + ${PROJECT_SOURCE_DIR}/source/loader/intel_fast_mem_stub.c + PROPERTIES COMPILE_OPTIONS "${_no_intel_lib_flag}") + target_sources(ur_sanitizer_layer PRIVATE + ${PROJECT_SOURCE_DIR}/source/loader/intel_fast_mem_stub.c + ) + endif() + + # In an in-tree build, if LLVM is built with libc++, symbolizer.cpp has to + # be built against libc++ as well and libc++ linked in. + if(NOT UR_STANDALONE_BUILD AND LLVM_LIBCXX_USED) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libc++.a + OUTPUT_VARIABLE LIBCXX_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libc++abi.a + OUTPUT_VARIABLE LIBCXX_ABI_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT EXISTS ${LIBCXX_PATH} OR NOT EXISTS ${LIBCXX_ABI_PATH}) + message(FATAL_ERROR "libc++ is required but can't find the libraries") + endif() + set_property(SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/sanitizer_common/linux/symbolizer.cpp + APPEND_STRING PROPERTY COMPILE_FLAGS " -stdlib=libc++ ") + # Link with gcc_s first to avoid some symbols resolving to + # libc++/libc++abi/libunwind's one + target_link_libraries(ur_sanitizer_layer PRIVATE + gcc_s ${LIBCXX_PATH} ${LIBCXX_ABI_PATH}) + endif() +endif() + +# Loaded at runtime, so there is no link time dependency to pull it in. +add_dependencies(ur_loader ur_sanitizer_layer) diff --git a/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp b/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp index 04f637fabdbca..7b3c282ae8ce7 100644 --- a/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp @@ -2108,9 +2108,6 @@ ur_result_t urCheckVersion(ur_api_version_t version) { } // namespace asan ur_result_t initAsanDDITable(ur_dditable_t *dditable) { - - UR_LOG_L(getContext()->logger, QUIET, "==== DeviceSanitizer: ASAN"); - ur_result_t result = ur_sanitizer_layer::asan::urCheckVersion(UR_API_VERSION_CURRENT); diff --git a/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp b/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp index 5e83d1e6e3476..f5271ac1fe786 100644 --- a/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp @@ -2085,8 +2085,6 @@ ur_result_t urCheckVersion(ur_api_version_t version) { ur_result_t initMsanDDITable(ur_dditable_t *dditable) { ur_result_t result = UR_RESULT_SUCCESS; - UR_LOG_L(getContext()->logger, QUIET, "==== DeviceSanitizer: MSAN"); - if (UR_RESULT_SUCCESS == result) { result = ur_sanitizer_layer::msan::urCheckVersion(UR_API_VERSION_CURRENT); } diff --git a/unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map b/unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map new file mode 100644 index 0000000000000..b6a3c258366f2 --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map @@ -0,0 +1,6 @@ +{ + global: + urLoaderLayerGetInterface; + local: + *; +}; diff --git a/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp b/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp index 266639e68fba2..4ecaf80a2a460 100644 --- a/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp @@ -1704,8 +1704,6 @@ ur_result_t urGetIPCExpProcAddrTable( ur_result_t initTsanDDITable(ur_dditable_t *dditable) { ur_result_t result = UR_RESULT_SUCCESS; - UR_LOG_L(getContext()->logger, QUIET, "==== DeviceSanitizer: TSAN"); - if (UR_RESULT_SUCCESS == result) { result = ur_sanitizer_layer::tsan::urCheckVersion(UR_API_VERSION_CURRENT); } diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp index c7ec6b24cd58a..416ca84d38cda 100644 --- a/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp @@ -60,4 +60,19 @@ ur_result_t context_t::init(ur_dditable_t *dditable, return UR_RESULT_SUCCESS; } +ur_result_t context_t::interceptDdiTable(ur_dditable_t *dditable) { + switch (enabledType) { + case SanitizerType::AddressSanitizer: + return initAsanDDITable(dditable); + case SanitizerType::MemorySanitizer: + return initMsanDDITable(dditable); + case SanitizerType::ThreadSanitizer: + return initTsanDDITable(dditable); + default: + break; + } + + return UR_RESULT_SUCCESS; +} + } // namespace ur_sanitizer_layer diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp index 86789d9bbb57b..e10c6df485935 100644 --- a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp @@ -46,6 +46,9 @@ class __urdlllocal context_t : public proxy_layer_context_t, const std::set &enabledLayerNames, codeloc_data codelocData) override; + /// @brief Hook up one more ddi table to the already initialized sanitizer. + ur_result_t interceptDdiTable(ur_dditable_t *dditable); + ur_result_t tearDown() override; }; diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp new file mode 100644 index 0000000000000..64c7accc20560 --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp @@ -0,0 +1,118 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_sanitizer_layer_entry.cpp + * + */ + +#include "logger/ur_logger.hpp" +#include "ur_layer_interface.h" +#include "ur_sanitizer_layer.hpp" + +#include +#include +#include +#include + +namespace { + +std::mutex layerMutex; +// Loader instances that have the layer initialized. +uint32_t initCount = 0; + +const char *getSanitizerName(ur_sanitizer_layer::SanitizerType type) { + switch (type) { + case ur_sanitizer_layer::SanitizerType::AddressSanitizer: + return "ASAN"; + case ur_sanitizer_layer::SanitizerType::MemorySanitizer: + return "MSAN"; + case ur_sanitizer_layer::SanitizerType::ThreadSanitizer: + return "TSAN"; + default: + return nullptr; + } +} + +ur_result_t UR_APICALL layerInit(ur_dditable_t *pDdiTable, + const char *const *ppEnabledLayerNames, + uint32_t numEnabledLayerNames) { + if (!pDdiTable || (numEnabledLayerNames && !ppEnabledLayerNames)) { + return UR_RESULT_ERROR_INVALID_NULL_POINTER; + } + + std::set enabledLayerNames; + for (uint32_t i = 0; i < numEnabledLayerNames; i++) { + enabledLayerNames.insert(ppEnabledLayerNames[i]); + } + + std::lock_guard lock(layerMutex); + + auto *context = ur_sanitizer_layer::getContext(); + + ur_result_t result; + if (initCount == 0) { + result = context->init(pDdiTable, enabledLayerNames, codeloc_data{}); + // Announced once per process, unlike the per-loader ddi table setup. + const char *name = getSanitizerName(context->enabledType); + if (result == UR_RESULT_SUCCESS && name) { + UR_LOG_L(context->logger, QUIET, "==== DeviceSanitizer: {}", name); + } + } else { + // The state is already up, so only hook up this loader's ddi table. + UR_LOG(DEBUG, "sanitizer layer is already initialized, intercepting an " + "additional ddi table"); + result = context->interceptDdiTable(pDdiTable); + } + + if (result == UR_RESULT_SUCCESS) { + initCount++; + } + + return result; +} + +ur_result_t UR_APICALL layerTearDown() { + std::lock_guard lock(layerMutex); + + if (initCount == 0 || --initCount != 0) { + return UR_RESULT_SUCCESS; + } + + ur_result_t result = ur_sanitizer_layer::getContext()->tearDown(); + ur_sanitizer_layer::context_t::forceDelete(); + + return result; +} + +} // namespace + +extern "C" UR_APIEXPORT ur_result_t UR_APICALL +urLoaderLayerGetInterface(uint32_t version, ur_layer_interface_t *pInterface) { + // This library has its own copy of the logger; keep it on UR_LOG_LOADER. + logger::init("loader"); + + if (version != UR_LAYER_INTERFACE_VERSION) { + UR_LOG(ERR, "unsupported layer interface version {}, expected {}", version, + UR_LAYER_INTERFACE_VERSION); + return UR_RESULT_ERROR_UNSUPPORTED_VERSION; + } + + if (!pInterface) { + return UR_RESULT_ERROR_INVALID_NULL_POINTER; + } + + pInterface->version = UR_LAYER_INTERFACE_VERSION; + pInterface->pfnInit = layerInit; + pInterface->pfnTearDown = layerTearDown; + + return UR_RESULT_SUCCESS; +} + +static_assert(std::is_same_v, + "the loader casts the entry point to this type"); diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp new file mode 100644 index 0000000000000..2da162573df54 --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp @@ -0,0 +1,36 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_sanitizer_layer_proxy.hpp + * + */ + +#pragma once + +#include "ur_shared_layer.hpp" +#include "ur_util.hpp" + +namespace ur_sanitizer_layer_proxy { + +/// @brief Loader side of the device sanitizer layer. +class __urdlllocal context_t final : public ur_loader::SharedLayer, + public AtomicSingleton { +public: + context_t() + : SharedLayer(MAKE_LIBRARY_NAME("ur_sanitizer_layer", "0"), getNames()) {} + + /// Known without loading the library. Keep in sync with + /// ur_sanitizer_layer::context_t. + static std::vector getNames() { + return {"UR_LAYER_ASAN", "UR_LAYER_MSAN", "UR_LAYER_TSAN"}; + } +}; + +inline context_t *getContext() { return context_t::get_direct(); } + +} // namespace ur_sanitizer_layer_proxy diff --git a/unified-runtime/source/loader/layers/ur_layer_interface.h b/unified-runtime/source/loader/layers/ur_layer_interface.h new file mode 100644 index 0000000000000..21dce716bf688 --- /dev/null +++ b/unified-runtime/source/loader/layers/ur_layer_interface.h @@ -0,0 +1,56 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_layer_interface.h + * + */ + +#ifndef UR_LAYER_INTERFACE_H +#define UR_LAYER_INTERFACE_H 1 + +#include "unified-runtime/ur_api.h" +#include "unified-runtime/ur_ddi.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/// @brief The only symbol a layer shared library exports. +#define UR_LAYER_GET_INTERFACE_FUNC_NAME "urLoaderLayerGetInterface" + +/// @brief Bump whenever ur_layer_interface_t changes. The loader only accepts a +/// library implementing the exact version it asks for. +#define UR_LAYER_INTERFACE_VERSION 1 + +/// @brief Entry points of a layer implemented in a shared library. +typedef struct ur_layer_interface_t { + /// [out] Set to UR_LAYER_INTERFACE_VERSION by the layer. + uint32_t version; + + /// @brief Initialize the layer and route pDdiTable through it. Called once + /// per loader instance; all of them share one layer state. + ur_result_t(UR_APICALL *pfnInit)(ur_dditable_t *pDdiTable, + const char *const *ppEnabledLayerNames, + uint32_t numEnabledLayerNames); + + /// @brief Tear the layer down. The state is destroyed once every loader + /// instance that initialized the layer has torn it down. + ur_result_t(UR_APICALL *pfnTearDown)(void); +} ur_layer_interface_t; + +/// @brief Type of the UR_LAYER_GET_INTERFACE_FUNC_NAME entry point. Returns +/// UR_RESULT_ERROR_UNSUPPORTED_VERSION and leaves pInterface untouched +/// if the layer doesn't implement the requested version. +typedef ur_result_t(UR_APICALL *ur_pfnLoaderLayerGetInterface_t)( + uint32_t version, ur_layer_interface_t *pInterface); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* UR_LAYER_INTERFACE_H */ diff --git a/unified-runtime/source/loader/layers/ur_shared_layer.cpp b/unified-runtime/source/loader/layers/ur_shared_layer.cpp new file mode 100644 index 0000000000000..fd5d6b4339d0c --- /dev/null +++ b/unified-runtime/source/loader/layers/ur_shared_layer.cpp @@ -0,0 +1,116 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_shared_layer.cpp + * + */ + +#include "ur_shared_layer.hpp" +#include "logger/ur_logger.hpp" +#include "ur_adapter_search.hpp" + +#include + +namespace ur_loader { + +namespace { + +/// @brief Candidate paths for a layer library, belonging to this loader first. +std::vector getLayerLoadPaths(const std::string &libraryName) { + std::vector paths; + + if (auto dir = getLoaderLibPath(); dir.has_value()) { + paths.emplace_back(dir.value() / libraryName); + } + if (auto path = getAdapterNameAsPath(libraryName); path.has_value()) { + paths.emplace_back(path.value()); + } + + return paths; +} + +} // namespace + +ur_result_t SharedLayer::load() { + if (library) { + return UR_RESULT_SUCCESS; + } + + for (const auto &path : getLayerLoadPaths(libraryName)) { + auto handle = LibLoader::loadAdapterLibrary(path.string().c_str()); + if (!handle) { + continue; + } + + auto pfnGetInterface = reinterpret_cast( + LibLoader::getFunctionPtr(handle.get(), + UR_LAYER_GET_INTERFACE_FUNC_NAME)); + ur_layer_interface_t candidate = {}; + if (!pfnGetInterface || + pfnGetInterface(UR_LAYER_INTERFACE_VERSION, &candidate) != + UR_RESULT_SUCCESS || + !candidate.pfnInit || !candidate.pfnTearDown) { + UR_LOG(ERR, "{} doesn't implement layer interface version {}", + path.string(), UR_LAYER_INTERFACE_VERSION); + continue; + } + + library = std::move(handle); + layerInterface = candidate; + return UR_RESULT_SUCCESS; + } + + // At QUIET, which always prints: this aborts the loader initialization. + UR_LOG(QUIET, + "the {} layer is enabled but its library couldn't be loaded, make " + "sure it is installed next to the loader", + libraryName); + + return UR_RESULT_ERROR_LAYER_NOT_PRESENT; +} + +ur_result_t SharedLayer::init(ur_dditable_t *dditable, + const std::set &enabledLayerNames, + codeloc_data) { + bool enabled = std::any_of( + layerNames.begin(), layerNames.end(), [&enabledLayerNames](auto &name) { + return enabledLayerNames.find(name) != enabledLayerNames.end(); + }); + if (!enabled) { + return UR_RESULT_SUCCESS; + } + + if (ur_result_t result = load(); result != UR_RESULT_SUCCESS) { + return result; + } + + // The layer picks the names it implements out of all the enabled ones itself. + std::vector names; + names.reserve(enabledLayerNames.size()); + for (const auto &name : enabledLayerNames) { + names.push_back(name.c_str()); + } + + return layerInterface.pfnInit(dditable, names.data(), + static_cast(names.size())); +} + +ur_result_t SharedLayer::tearDown() { + if (!library) { + return UR_RESULT_SUCCESS; + } + + ur_result_t result = layerInterface.pfnTearDown(); + layerInterface = {}; + // Stays mapped as long as another loader instance still references it. + library.reset(); + + return result; +} + +} // namespace ur_loader diff --git a/unified-runtime/source/loader/layers/ur_shared_layer.hpp b/unified-runtime/source/loader/layers/ur_shared_layer.hpp new file mode 100644 index 0000000000000..ee814d70d0f85 --- /dev/null +++ b/unified-runtime/source/loader/layers/ur_shared_layer.hpp @@ -0,0 +1,55 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_shared_layer.hpp + * + */ + +#ifndef UR_SHARED_LAYER_HPP +#define UR_SHARED_LAYER_HPP 1 + +#include "ur_layer_interface.h" +#include "ur_lib_loader.hpp" +#include "ur_proxy_layer.hpp" + +#include +#include + +namespace ur_loader { + +/// @brief A layer implemented in a separate shared library, so that all loader +/// instances of a process share one instance of it. +/// +/// A static loader can be linked into several libraries of one process, each +/// copy with its own hidden state. A layer compiled into the loader would be +/// duplicated along with it, which a layer holding state that has to exist +/// exactly once cannot tolerate. +class __urdlllocal SharedLayer : public proxy_layer_context_t { +public: + SharedLayer(std::string libraryName, std::vector layerNames) + : libraryName(std::move(libraryName)), layerNames(std::move(layerNames)) { + } + + ur_result_t init(ur_dditable_t *dditable, + const std::set &enabledLayerNames, + codeloc_data codelocData) override; + ur_result_t tearDown() override; + +private: + ur_result_t load(); + + const std::string libraryName; + const std::vector layerNames; + + LibLoader::Lib library; + ur_layer_interface_t layerInterface = {}; +}; + +} // namespace ur_loader + +#endif /* UR_SHARED_LAYER_HPP */ diff --git a/unified-runtime/source/loader/ur_lib.cpp b/unified-runtime/source/loader/ur_lib.cpp index 9030e1c7c7574..947f837255d65 100644 --- a/unified-runtime/source/loader/ur_lib.cpp +++ b/unified-runtime/source/loader/ur_lib.cpp @@ -49,10 +49,18 @@ void context_t::parseEnvEnabledLayers() { } } -void context_t::initLayers() { +// An enabled layer that fails to come up (e.g. a missing shared library) fails +// the whole loader; coming up silently without it would be worse. +ur_result_t context_t::initLayers() { for (auto &[layer, _] : layers) { - layer->init(&urDdiTable, enabledLayerNames, codelocData); + ur_result_t result = + layer->init(&urDdiTable, enabledLayerNames, codelocData); + if (result != UR_RESULT_SUCCESS) { + return result; + } } + + return UR_RESULT_SUCCESS; } void context_t::tearDownLayers() const { @@ -88,8 +96,8 @@ __urdlllocal ur_result_t context_t::Init( enabledLayerNames.merge(hLoaderConfig->getEnabledLayerNames()); } - if (!enabledLayerNames.empty()) { - initLayers(); + if (UR_RESULT_SUCCESS == result && !enabledLayerNames.empty()) { + result = initLayers(); } #if defined(UR_STATIC_UMF) diff --git a/unified-runtime/source/loader/ur_lib.hpp b/unified-runtime/source/loader/ur_lib.hpp index 527699e6b879e..0ec380e72d80c 100644 --- a/unified-runtime/source/loader/ur_lib.hpp +++ b/unified-runtime/source/loader/ur_lib.hpp @@ -24,7 +24,7 @@ #include "tracing/ur_tracing_layer.hpp" #endif #if UR_ENABLE_SANITIZER -#include "sanitizer/ur_sanitizer_layer.hpp" +#include "sanitizer/ur_sanitizer_layer_proxy.hpp" #endif #include @@ -76,8 +76,8 @@ class __urdlllocal context_t : public AtomicSingleton { // Initialize tracing layer after sanitizer layer to make sure tracing // layer will properly print all API calls. #if UR_ENABLE_SANITIZER - {ur_sanitizer_layer::getContext(), - ur_sanitizer_layer::context_t::forceDelete}, + {ur_sanitizer_layer_proxy::getContext(), + ur_sanitizer_layer_proxy::context_t::forceDelete}, #endif #if UR_ENABLE_TRACING {ur_tracing_layer::getContext(), @@ -92,7 +92,7 @@ class __urdlllocal context_t : public AtomicSingleton { ur_tracing_layer::context_t::getNames(), #endif #if UR_ENABLE_SANITIZER - ur_sanitizer_layer::context_t::getNames(), + ur_sanitizer_layer_proxy::context_t::getNames(), #endif }; std::string s; @@ -111,7 +111,7 @@ class __urdlllocal context_t : public AtomicSingleton { codeloc_data codelocData; void parseEnvEnabledLayers(); - void initLayers(); + ur_result_t initLayers(); void tearDownLayers() const; }; diff --git a/unified-runtime/test/layers/sanitizer/CMakeLists.txt b/unified-runtime/test/layers/sanitizer/CMakeLists.txt index b739efcf02940..6c03701260bf2 100644 --- a/unified-runtime/test/layers/sanitizer/CMakeLists.txt +++ b/unified-runtime/test/layers/sanitizer/CMakeLists.txt @@ -18,6 +18,12 @@ endfunction() add_sanitizer_test(asan asan.cpp) add_sanitizer_test(sanitizer_options sanitizer_options.cpp) +add_sanitizer_test(shared_layer shared_layer.cpp) +target_include_directories(shared_layer-test PRIVATE + ${PROJECT_SOURCE_DIR}/source/loader/layers +) +target_link_libraries(shared_layer-test PRIVATE ${CMAKE_DL_LIBS}) + # add the source here in order to test for unexported functions add_test_source(sanitizer_options ${PROJECT_SOURCE_DIR}/source/loader/layers/sanitizer/sanitizer_common/sanitizer_options.cpp diff --git a/unified-runtime/test/layers/sanitizer/shared_layer.cpp b/unified-runtime/test/layers/sanitizer/shared_layer.cpp new file mode 100644 index 0000000000000..57f5219ee7374 --- /dev/null +++ b/unified-runtime/test/layers/sanitizer/shared_layer.cpp @@ -0,0 +1,105 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file shared_layer.cpp + * + */ + +// RUN: UR_LOG_LOADER="level:debug;flush:debug;output:stdout" shared_layer-test +// REQUIRES: sanitizer + +#include "ur_layer_interface.h" +#include "ur_util.hpp" + +#include +#include + +namespace { + +constexpr const char *LibraryName = + MAKE_LIBRARY_NAME("ur_sanitizer_layer", "0"); + +struct SharedSanitizerLayer : public ::testing::Test { + void SetUp() override { + handle = dlopen(LibraryName, RTLD_LAZY | RTLD_LOCAL); + ASSERT_NE(handle, nullptr) << dlerror(); + } + + void TearDown() override { + if (handle) { + dlclose(handle); + } + } + + ur_pfnLoaderLayerGetInterface_t getInterfaceFn() { + return reinterpret_cast( + dlsym(handle, UR_LAYER_GET_INTERFACE_FUNC_NAME)); + } + + void *handle = nullptr; +}; + +TEST_F(SharedSanitizerLayer, GetInterface) { + auto pfnGetInterface = getInterfaceFn(); + ASSERT_NE(pfnGetInterface, nullptr) << dlerror(); + + ur_layer_interface_t interface = {}; + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION, &interface), + UR_RESULT_SUCCESS); + ASSERT_EQ(interface.version, UR_LAYER_INTERFACE_VERSION); + ASSERT_NE(interface.pfnInit, nullptr); + ASSERT_NE(interface.pfnTearDown, nullptr); +} + +TEST_F(SharedSanitizerLayer, GetInterfaceRejectsUnknownVersion) { + auto pfnGetInterface = getInterfaceFn(); + ASSERT_NE(pfnGetInterface, nullptr) << dlerror(); + + ur_layer_interface_t interface = {}; + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION + 1, &interface), + UR_RESULT_ERROR_UNSUPPORTED_VERSION); + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION, nullptr), + UR_RESULT_ERROR_INVALID_NULL_POINTER); +} + +// Consumers have their own copy of the layer's dependencies, so nothing but the +// entry point may be visible. +TEST_F(SharedSanitizerLayer, EntryPointIsTheOnlyExportedSymbol) { + ASSERT_NE(dlsym(handle, UR_LAYER_GET_INTERFACE_FUNC_NAME), nullptr); + + for (const char *name : {"urLoaderInit", "urContextCreate", + "_ZN20ur_sanitizer_layer10getContextEv"}) { + EXPECT_EQ(dlsym(handle, name), nullptr) << name << " must not be exported"; + } +} + +// The shared state has to outlive all but the last loader instance. +TEST_F(SharedSanitizerLayer, InitIsReferenceCounted) { + auto pfnGetInterface = getInterfaceFn(); + ASSERT_NE(pfnGetInterface, nullptr) << dlerror(); + + ur_layer_interface_t interface = {}; + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION, &interface), + UR_RESULT_SUCCESS); + + // No sanitizer is enabled here, so this only exercises the book-keeping. + ur_dditable_t firstTable = {}; + ur_dditable_t secondTable = {}; + ASSERT_EQ(interface.pfnInit(&firstTable, nullptr, 0), UR_RESULT_SUCCESS); + ASSERT_EQ(interface.pfnInit(&secondTable, nullptr, 0), UR_RESULT_SUCCESS); + + ASSERT_EQ(interface.pfnTearDown(), UR_RESULT_SUCCESS); + ASSERT_EQ(interface.pfnTearDown(), UR_RESULT_SUCCESS); + // Tearing down more often than initializing must not underflow the count. + ASSERT_EQ(interface.pfnTearDown(), UR_RESULT_SUCCESS); + + ASSERT_EQ(interface.pfnInit(nullptr, nullptr, 0), + UR_RESULT_ERROR_INVALID_NULL_POINTER); +} + +} // namespace