diff --git a/source/loader/CMakeLists.txt b/source/loader/CMakeLists.txt index 48329cfb37..deec891775 100644 --- a/source/loader/CMakeLists.txt +++ b/source/loader/CMakeLists.txt @@ -144,12 +144,16 @@ if(UR_ENABLE_SANITIZER) ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_interceptor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_interceptor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_libdevice.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_options.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_options.hpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_quarantine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_quarantine.hpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_report.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_report.hpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_shadow_setup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_shadow_setup.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_statistics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_statistics.hpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_validator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan_validator.hpp ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/common.hpp diff --git a/source/loader/layers/sanitizer/asan_allocator.hpp b/source/loader/layers/sanitizer/asan_allocator.hpp index 88dfd2d074..249ef896d0 100644 --- a/source/loader/layers/sanitizer/asan_allocator.hpp +++ b/source/loader/layers/sanitizer/asan_allocator.hpp @@ -45,6 +45,7 @@ struct AllocInfo { StackTrace ReleaseStack; void print(); + size_t getRedzoneSize() { return AllocSize - (UserEnd - UserBegin); } }; using AllocationMap = std::map>; diff --git a/source/loader/layers/sanitizer/asan_interceptor.cpp b/source/loader/layers/sanitizer/asan_interceptor.cpp index c4b8986b58..3489c1fd0b 100644 --- a/source/loader/layers/sanitizer/asan_interceptor.cpp +++ b/source/loader/layers/sanitizer/asan_interceptor.cpp @@ -58,7 +58,7 @@ ur_result_t urEnqueueUSMSet(ur_queue_handle_t Queue, void *Ptr, char Value, Queue, Ptr, 1, &Value, Size, NumEvents, EventWaitList, OutEvent); } -ur_result_t enqueueMemSetShadow(ur_context_handle_t Context, +ur_result_t enqueueMemSetShadow(std::shared_ptr &ContextInfo, std::shared_ptr &DeviceInfo, ur_queue_handle_t Queue, uptr Ptr, uptr Size, u8 Value) { @@ -105,9 +105,10 @@ ur_result_t enqueueMemSetShadow(ur_context_handle_t Context, } assert(ShadowBegin <= ShadowEnd); + { - static const size_t PageSize = - GetVirtualMemGranularity(Context, DeviceInfo->Handle); + static const size_t PageSize = GetVirtualMemGranularity( + ContextInfo->Handle, DeviceInfo->Handle); ur_physical_mem_properties_t Desc{ UR_STRUCTURE_TYPE_PHYSICAL_MEM_PROPERTIES, nullptr, 0}; @@ -118,8 +119,8 @@ ur_result_t enqueueMemSetShadow(ur_context_handle_t Context, MappedPtr <= ShadowEnd; MappedPtr += PageSize) { if (!PhysicalMem) { auto URes = getContext()->urDdiTable.PhysicalMem.pfnCreate( - Context, DeviceInfo->Handle, PageSize, &Desc, - &PhysicalMem); + ContextInfo->Handle, DeviceInfo->Handle, PageSize, + &Desc, &PhysicalMem); if (URes != UR_RESULT_SUCCESS) { getContext()->logger.error("urPhysicalMemCreate(): {}", URes); @@ -133,8 +134,8 @@ ur_result_t enqueueMemSetShadow(ur_context_handle_t Context, // FIXME: No flag to check the failed reason is VA is already mapped auto URes = getContext()->urDdiTable.VirtualMem.pfnMap( - Context, (void *)MappedPtr, PageSize, PhysicalMem, 0, - UR_VIRTUAL_MEM_ACCESS_FLAG_READ_WRITE); + ContextInfo->Handle, (void *)MappedPtr, PageSize, + PhysicalMem, 0, UR_VIRTUAL_MEM_ACCESS_FLAG_READ_WRITE); if (URes != UR_RESULT_SUCCESS) { getContext()->logger.debug("urVirtualMemMap({}, {}): {}", (void *)MappedPtr, PageSize, @@ -145,6 +146,7 @@ ur_result_t enqueueMemSetShadow(ur_context_handle_t Context, if (URes == UR_RESULT_SUCCESS) { // Reset PhysicalMem to null since it's been mapped PhysicalMem = nullptr; + ContextInfo->Stats.UpdateShadowMmaped(PageSize); auto URes = urEnqueueUSMSet(Queue, (void *)MappedPtr, 0, PageSize); @@ -173,11 +175,10 @@ ur_result_t enqueueMemSetShadow(ur_context_handle_t Context, } // namespace -SanitizerInterceptor::SanitizerInterceptor(logger::Logger &logger) - : logger(logger) { - if (Options(logger).MaxQuarantineSizeMB) { +SanitizerInterceptor::SanitizerInterceptor() { + if (getOptions().MaxQuarantineSizeMB) { m_Quarantine = std::make_unique( - static_cast(Options(logger).MaxQuarantineSizeMB) * 1024 * + static_cast(getOptions().MaxQuarantineSizeMB) * 1024 * 1024); } } @@ -231,8 +232,8 @@ ur_result_t SanitizerInterceptor::allocateMemory( Alignment = MinAlignment; } - uptr RZLog = ComputeRZLog(Size, Options(logger).MinRZSize, - Options(logger).MaxRZSize); + uptr RZLog = + ComputeRZLog(Size, getOptions().MinRZSize, getOptions().MaxRZSize); uptr RZSize = RZLog2Size(RZLog); uptr RoundedSize = RoundUpTo(Size, Alignment); uptr NeededSize = RoundedSize + RZSize * 2; @@ -259,6 +260,9 @@ ur_result_t SanitizerInterceptor::allocateMemory( return UR_RESULT_ERROR_INVALID_ARGUMENT; } + // Udpate statistics + ContextInfo->Stats.UpdateUSMMalloced(NeededSize, NeededSize - Size); + uptr AllocBegin = reinterpret_cast(Allocated); [[maybe_unused]] uptr AllocEnd = AllocBegin + NeededSize; uptr UserBegin = AllocBegin + RZSize; @@ -313,7 +317,8 @@ ur_result_t SanitizerInterceptor::releaseMemory(ur_context_handle_t Context, } auto AllocInfoIt = *AllocInfoItOp; - auto &AllocInfo = AllocInfoIt->second; + // NOTE: AllocInfoIt will be erased later, so "AllocInfo" must be a new reference here + auto AllocInfo = AllocInfoIt->second; if (AllocInfo->Context != Context) { if (AllocInfo->UserBegin == Addr) { @@ -347,23 +352,35 @@ ur_result_t SanitizerInterceptor::releaseMemory(ur_context_handle_t Context, // If quarantine is disabled, USM is freed immediately if (!m_Quarantine) { getContext()->logger.debug("Free: {}", (void *)AllocInfo->AllocBegin); + + ContextInfo->Stats.UpdateUSMRealFreed(AllocInfo->AllocSize, + AllocInfo->getRedzoneSize()); + std::scoped_lock Guard(m_AllocationMapMutex); m_AllocationMap.erase(AllocInfoIt); + return getContext()->urDdiTable.USM.pfnFree( Context, (void *)(AllocInfo->AllocBegin)); } + // If quarantine is enabled, cache it auto ReleaseList = m_Quarantine->put(AllocInfo->Device, AllocInfoIt); if (ReleaseList.size()) { std::scoped_lock Guard(m_AllocationMapMutex); for (auto &It : ReleaseList) { getContext()->logger.info("Quarantine Free: {}", (void *)It->second->AllocBegin); + + ContextInfo->Stats.UpdateUSMRealFreed(AllocInfo->AllocSize, + AllocInfo->getRedzoneSize()); + m_AllocationMap.erase(It); + UR_CALL(getContext()->urDdiTable.USM.pfnFree( Context, (void *)(It->second->AllocBegin))); } } + ContextInfo->Stats.UpdateUSMFreed(AllocInfo->AllocSize); return UR_RESULT_SUCCESS; } @@ -385,8 +402,8 @@ ur_result_t SanitizerInterceptor::preLaunchKernel(ur_kernel_handle_t Kernel, return UR_RESULT_ERROR_INVALID_QUEUE; } - UR_CALL( - prepareLaunch(Context, DeviceInfo, InternalQueue, Kernel, LaunchInfo)); + UR_CALL(prepareLaunch(ContextInfo, DeviceInfo, InternalQueue, Kernel, + LaunchInfo)); UR_CALL(updateShadowMemory(ContextInfo, DeviceInfo, InternalQueue)); @@ -442,9 +459,10 @@ ur_result_t DeviceInfo::allocShadowMemory(ur_context_handle_t Context) { // Set shadow memory for null pointer ManagedQueue Queue(Context, Handle); + auto CI = getContext()->interceptor->getContextInfo(Context); auto DI = getContext()->interceptor->getDeviceInfo(Handle); auto URes = - enqueueMemSetShadow(Context, DI, Queue, 0, 1, kNullPointerRedzoneMagic); + enqueueMemSetShadow(CI, DI, Queue, 0, 1, kNullPointerRedzoneMagic); if (URes != UR_RESULT_SUCCESS) { getContext()->logger.error("enqueueMemSetShadow(NullPointerRZ): {}", URes); @@ -461,8 +479,9 @@ ur_result_t DeviceInfo::allocShadowMemory(ur_context_handle_t Context) { /// /// ref: https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm#mapping ur_result_t SanitizerInterceptor::enqueueAllocInfo( - ur_context_handle_t Context, std::shared_ptr &DeviceInfo, - ur_queue_handle_t Queue, std::shared_ptr &AI) { + std::shared_ptr &ContextInfo, + std::shared_ptr &DeviceInfo, ur_queue_handle_t Queue, + std::shared_ptr &AI) { if (AI->IsReleased) { int ShadowByte; switch (AI->Type) { @@ -482,13 +501,13 @@ ur_result_t SanitizerInterceptor::enqueueAllocInfo( ShadowByte = 0xff; assert(false && "Unknow AllocInfo Type"); } - UR_CALL(enqueueMemSetShadow(Context, DeviceInfo, Queue, AI->AllocBegin, - AI->AllocSize, ShadowByte)); + UR_CALL(enqueueMemSetShadow(ContextInfo, DeviceInfo, Queue, + AI->AllocBegin, AI->AllocSize, ShadowByte)); return UR_RESULT_SUCCESS; } // Init zero - UR_CALL(enqueueMemSetShadow(Context, DeviceInfo, Queue, AI->AllocBegin, + UR_CALL(enqueueMemSetShadow(ContextInfo, DeviceInfo, Queue, AI->AllocBegin, AI->AllocSize, 0)); uptr TailBegin = RoundUpTo(AI->UserEnd, ASAN_SHADOW_GRANULARITY); @@ -498,8 +517,8 @@ ur_result_t SanitizerInterceptor::enqueueAllocInfo( if (TailBegin != AI->UserEnd) { auto Value = AI->UserEnd - RoundDownTo(AI->UserEnd, ASAN_SHADOW_GRANULARITY); - UR_CALL(enqueueMemSetShadow(Context, DeviceInfo, Queue, AI->UserEnd, 1, - static_cast(Value))); + UR_CALL(enqueueMemSetShadow(ContextInfo, DeviceInfo, Queue, AI->UserEnd, + 1, static_cast(Value))); } int ShadowByte; @@ -525,11 +544,11 @@ ur_result_t SanitizerInterceptor::enqueueAllocInfo( } // Left red zone - UR_CALL(enqueueMemSetShadow(Context, DeviceInfo, Queue, AI->AllocBegin, + UR_CALL(enqueueMemSetShadow(ContextInfo, DeviceInfo, Queue, AI->AllocBegin, AI->UserBegin - AI->AllocBegin, ShadowByte)); // Right red zone - UR_CALL(enqueueMemSetShadow(Context, DeviceInfo, Queue, TailBegin, + UR_CALL(enqueueMemSetShadow(ContextInfo, DeviceInfo, Queue, TailBegin, TailEnd - TailBegin, ShadowByte)); return UR_RESULT_SUCCESS; @@ -542,7 +561,7 @@ ur_result_t SanitizerInterceptor::updateShadowMemory( std::scoped_lock Guard(AllocInfos.Mutex); for (auto &AI : AllocInfos.List) { - UR_CALL(enqueueAllocInfo(ContextInfo->Handle, DeviceInfo, Queue, AI)); + UR_CALL(enqueueAllocInfo(ContextInfo, DeviceInfo, Queue, AI)); } AllocInfos.List.clear(); @@ -705,23 +724,23 @@ SanitizerInterceptor::getMemBuffer(ur_mem_handle_t MemHandle) { } ur_result_t SanitizerInterceptor::prepareLaunch( - ur_context_handle_t Context, std::shared_ptr &DeviceInfo, - ur_queue_handle_t Queue, ur_kernel_handle_t Kernel, - USMLaunchInfo &LaunchInfo) { + std::shared_ptr &ContextInfo, + std::shared_ptr &DeviceInfo, ur_queue_handle_t Queue, + ur_kernel_handle_t Kernel, USMLaunchInfo &LaunchInfo) { auto Program = GetProgram(Kernel); do { auto KernelInfo = getKernelInfo(Kernel); // Validate pointer arguments - if (Options(logger).DetectKernelArguments) { + if (getOptions().DetectKernelArguments) { for (const auto &[ArgIndex, PtrPair] : KernelInfo->PointerArgs) { auto Ptr = PtrPair.first; if (Ptr == nullptr) { continue; } if (auto ValidateResult = ValidateUSMPointer( - Context, DeviceInfo->Handle, (uptr)Ptr)) { + ContextInfo->Handle, DeviceInfo->Handle, (uptr)Ptr)) { ReportInvalidKernelArgument(Kernel, ArgIndex, (uptr)Ptr, ValidateResult, PtrPair.second); exit(1); @@ -779,7 +798,7 @@ ur_result_t SanitizerInterceptor::prepareLaunch( // We use "uint64_t" here because EnqueueWriteGlobal will fail when it's "uint32_t" // Because EnqueueWriteGlobal is a async write, so // we need to extend its lifetime - static uint64_t Debug = Options(logger).Debug ? 1 : 0; + static uint64_t Debug = getOptions().Debug ? 1 : 0; EnqueueWriteGlobal(kSPIR_AsanDebug, &Debug, sizeof(Debug), false); // Write shadow memory offset for global memory @@ -820,12 +839,12 @@ ur_result_t SanitizerInterceptor::prepareLaunch( LocalWorkSize[Dim]; } - auto EnqueueAllocateShadowMemory = [Context, &DeviceInfo, + auto EnqueueAllocateShadowMemory = [Context = ContextInfo->Handle, + Device = DeviceInfo->Handle, Queue](size_t Size, uptr &Ptr) { void *Allocated = nullptr; auto URes = getContext()->urDdiTable.USM.pfnDeviceAlloc( - Context, DeviceInfo->Handle, nullptr, nullptr, Size, - &Allocated); + Context, Device, nullptr, nullptr, Size, &Allocated); if (URes != UR_RESULT_SUCCESS) { return URes; } @@ -852,7 +871,7 @@ ur_result_t SanitizerInterceptor::prepareLaunch( LocalMemoryUsage, PrivateMemoryUsage); // Write shadow memory offset for local memory - if (Options(logger).DetectLocals) { + if (getOptions().DetectLocals) { // CPU needn't this if (DeviceInfo->Type == DeviceType::GPU_PVC || DeviceInfo->Type == DeviceType::GPU_DG2) { @@ -883,6 +902,9 @@ ur_result_t SanitizerInterceptor::prepareLaunch( LaunchInfo.Data->LocalShadowOffset + LocalShadowMemorySize - 1; + ContextInfo->Stats.UpdateShadowMalloced( + LocalShadowMemorySize); + getContext()->logger.info( "ShadowMemory(Local, {} - {})", (void *)LaunchInfo.Data->LocalShadowOffset, @@ -892,7 +914,7 @@ ur_result_t SanitizerInterceptor::prepareLaunch( } // Write shadow memory offset for private memory - if (Options(logger).DetectPrivates) { + if (getOptions().DetectPrivates) { if (DeviceInfo->Type == DeviceType::CPU) { LaunchInfo.Data->PrivateShadowOffset = DeviceInfo->ShadowOffset; } else if (DeviceInfo->Type == DeviceType::GPU_PVC || @@ -920,6 +942,10 @@ ur_result_t SanitizerInterceptor::prepareLaunch( LaunchInfo.Data->PrivateShadowOffsetEnd = LaunchInfo.Data->PrivateShadowOffset + PrivateShadowMemorySize - 1; + + ContextInfo->Stats.UpdateShadowMalloced( + PrivateShadowMemorySize); + getContext()->logger.info( "ShadowMemory(Private, {} - {})", (void *)LaunchInfo.Data->PrivateShadowOffset, @@ -961,10 +987,13 @@ SanitizerInterceptor::findAllocInfoByContext(ur_context_handle_t Context) { } ContextInfo::~ContextInfo() { + Stats.Print(Handle); + [[maybe_unused]] auto Result = getContext()->urDdiTable.Context.pfnRelease(Handle); assert(Result == UR_RESULT_SUCCESS); + // check memory leaks std::vector AllocInfos = getContext()->interceptor->findAllocInfoByContext(Handle); for (const auto &It : AllocInfos) { @@ -1006,13 +1035,19 @@ USMLaunchInfo::~USMLaunchInfo() { [[maybe_unused]] ur_result_t Result; if (Data) { auto Type = GetDeviceType(Context, Device); + auto ContextInfo = getContext()->interceptor->getContextInfo(Context); if (Type == DeviceType::GPU_PVC || Type == DeviceType::GPU_DG2) { if (Data->PrivateShadowOffset) { + ContextInfo->Stats.UpdateShadowFreed( + Data->PrivateShadowOffsetEnd - Data->PrivateShadowOffset + + 1); Result = getContext()->urDdiTable.USM.pfnFree( Context, (void *)Data->PrivateShadowOffset); assert(Result == UR_RESULT_SUCCESS); } if (Data->LocalShadowOffset) { + ContextInfo->Stats.UpdateShadowFreed( + Data->LocalShadowOffsetEnd - Data->LocalShadowOffset + 1); Result = getContext()->urDdiTable.USM.pfnFree( Context, (void *)Data->LocalShadowOffset); assert(Result == UR_RESULT_SUCCESS); diff --git a/source/loader/layers/sanitizer/asan_interceptor.hpp b/source/loader/layers/sanitizer/asan_interceptor.hpp index 271669d60e..c9d8f76acd 100644 --- a/source/loader/layers/sanitizer/asan_interceptor.hpp +++ b/source/loader/layers/sanitizer/asan_interceptor.hpp @@ -15,6 +15,8 @@ #include "asan_allocator.hpp" #include "asan_buffer.hpp" #include "asan_libdevice.hpp" +#include "asan_options.hpp" +#include "asan_statistics.hpp" #include "common.hpp" #include "ur_sanitizer_layer.hpp" @@ -111,6 +113,8 @@ struct ContextInfo { std::vector DeviceList; std::unordered_map AllocInfosMap; + AsanStatsWrapper Stats; + explicit ContextInfo(ur_context_handle_t Context) : Handle(Context) { [[maybe_unused]] auto Result = getContext()->urDdiTable.Context.pfnRetain(Context); @@ -163,7 +167,7 @@ struct DeviceGlobalInfo { class SanitizerInterceptor { public: - explicit SanitizerInterceptor(logger::Logger &logger); + explicit SanitizerInterceptor(); ~SanitizerInterceptor(); @@ -233,17 +237,19 @@ class SanitizerInterceptor { return m_KernelMap[Kernel]; } + const AsanOptions &getOptions() { return m_Options; } + private: ur_result_t updateShadowMemory(std::shared_ptr &ContextInfo, std::shared_ptr &DeviceInfo, ur_queue_handle_t Queue); - ur_result_t enqueueAllocInfo(ur_context_handle_t Context, + ur_result_t enqueueAllocInfo(std::shared_ptr &ContextInfo, std::shared_ptr &DeviceInfo, ur_queue_handle_t Queue, std::shared_ptr &AI); /// Initialize Global Variables & Kernel Name at first Launch - ur_result_t prepareLaunch(ur_context_handle_t Context, + ur_result_t prepareLaunch(std::shared_ptr &ContextInfo, std::shared_ptr &DeviceInfo, ur_queue_handle_t Queue, ur_kernel_handle_t Kernel, @@ -273,7 +279,8 @@ class SanitizerInterceptor { ur_shared_mutex m_AllocationMapMutex; std::unique_ptr m_Quarantine; - logger::Logger &logger; + + AsanOptions m_Options; std::unordered_set m_Adapters; ur_shared_mutex m_AdaptersMutex; diff --git a/source/loader/layers/sanitizer/asan_options.cpp b/source/loader/layers/sanitizer/asan_options.cpp new file mode 100644 index 0000000000..5c42ab8fca --- /dev/null +++ b/source/loader/layers/sanitizer/asan_options.cpp @@ -0,0 +1,142 @@ +/* + * + * Copyright (C) 2024 Intel Corporation + * + * Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. + * See LICENSE.TXT + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file asan_options.cpp + * + */ + +#include "asan_options.hpp" +#include "ur_sanitizer_layer.hpp" + +#include +#include +#include + +namespace ur_sanitizer_layer { + +AsanOptions::AsanOptions() { + std::optional OptionsEnvMap; + try { + OptionsEnvMap = getenv_to_map("UR_LAYER_ASAN_OPTIONS"); + } catch (const std::invalid_argument &e) { + std::stringstream SS; + SS << "[ERROR]: "; + SS << e.what(); + getContext()->logger.always(SS.str().c_str()); + die("Sanitizer failed to parse options.\n"); + } + + if (!OptionsEnvMap.has_value()) { + return; + } + + const char *TrueStrings[] = {"1", "true"}; + const char *FalseStrings[] = {"0", "false"}; + + auto InplaceToLower = [](std::string &S) { + std::transform(S.begin(), S.end(), S.begin(), + [](unsigned char C) { return std::tolower(C); }); + }; + auto IsTrue = [&](const std::string &S) { + return std::any_of(std::begin(TrueStrings), std::end(TrueStrings), + [&](const char *CS) { return S == CS; }); + }; + auto IsFalse = [&](const std::string &S) { + return std::any_of(std::begin(FalseStrings), std::end(FalseStrings), + [&](const char *CS) { return S == CS; }); + }; + + auto SetBoolOption = [&](const std::string &Name, bool &Opt) { + auto KV = OptionsEnvMap->find(Name); + if (KV != OptionsEnvMap->end()) { + auto Value = KV->second.front(); + InplaceToLower(Value); + if (IsTrue(Value)) { + Opt = true; + } else if (IsFalse(Value)) { + Opt = false; + } else { + std::stringstream SS; + SS << "\"" << Name << "\" is set to \"" << Value + << "\", which is not an valid setting. "; + SS << "Acceptable input are: for enable, use:"; + for (auto &S : TrueStrings) { + SS << " \"" << S << "\""; + } + SS << "; "; + SS << "for disable, use:"; + for (auto &S : FalseStrings) { + SS << " \"" << S << "\""; + } + SS << "."; + getContext()->logger.error(SS.str().c_str()); + die("Sanitizer failed to parse options.\n"); + } + } + }; + + SetBoolOption("debug", Debug); + SetBoolOption("detect_kernel_arguments", DetectKernelArguments); + SetBoolOption("detect_locals", DetectLocals); + SetBoolOption("detect_privates", DetectPrivates); + SetBoolOption("print_stats", PrintStats); + + auto KV = OptionsEnvMap->find("quarantine_size_mb"); + if (KV != OptionsEnvMap->end()) { + const auto &Value = KV->second.front(); + try { + auto temp_long = std::stoul(Value); + if (temp_long > UINT32_MAX) { + throw std::out_of_range(""); + } + MaxQuarantineSizeMB = temp_long; + } catch (...) { + getContext()->logger.error("\"quarantine_size_mb\" should be " + "an integer in range[0, {}].", + UINT32_MAX); + die("Sanitizer failed to parse options.\n"); + } + } + + KV = OptionsEnvMap->find("redzone"); + if (KV != OptionsEnvMap->end()) { + const auto &Value = KV->second.front(); + try { + MinRZSize = std::stoul(Value); + if (MinRZSize < 16) { + MinRZSize = 16; + getContext()->logger.warning("Trying to set redzone size to a " + "value less than 16 is ignored."); + } + } catch (...) { + getContext()->logger.error( + "\"redzone\" should be an integer in range[0, 16]."); + die("Sanitizer failed to parse options.\n"); + } + } + + KV = OptionsEnvMap->find("max_redzone"); + if (KV != OptionsEnvMap->end()) { + const auto &Value = KV->second.front(); + try { + MaxRZSize = std::stoul(Value); + if (MaxRZSize > 2048) { + MaxRZSize = 2048; + getContext()->logger.warning( + "Trying to set max redzone size to a " + "value greater than 2048 is ignored."); + } + } catch (...) { + getContext()->logger.error( + "\"max_redzone\" should be an integer in range[0, 2048]."); + die("Sanitizer failed to parse options.\n"); + } + } +} + +} // namespace ur_sanitizer_layer diff --git a/source/loader/layers/sanitizer/asan_options.hpp b/source/loader/layers/sanitizer/asan_options.hpp index eb3f6bb03d..8206c015af 100644 --- a/source/loader/layers/sanitizer/asan_options.hpp +++ b/source/loader/layers/sanitizer/asan_options.hpp @@ -12,156 +12,21 @@ #pragma once -#include "common/ur_util.hpp" -#include "ur/ur.hpp" -#include "ur_sanitizer_layer.hpp" - -#include -#include -#include +#include "common.hpp" namespace ur_sanitizer_layer { struct AsanOptions { - public: - AsanOptions(AsanOptions &other) = delete; - void operator=(const AsanOptions &) = delete; - - static AsanOptions &getInstance(logger::Logger &logger) { - static AsanOptions instance(logger); - return instance; - } - bool Debug = false; uint64_t MinRZSize = 16; uint64_t MaxRZSize = 2048; uint32_t MaxQuarantineSizeMB = 0; bool DetectLocals = true; bool DetectPrivates = true; + bool PrintStats = false; bool DetectKernelArguments = true; - private: - AsanOptions(logger::Logger &logger) { - std::optional OptionsEnvMap; - try { - OptionsEnvMap = getenv_to_map("UR_LAYER_ASAN_OPTIONS"); - } catch (const std::invalid_argument &e) { - std::stringstream SS; - SS << "[ERROR]: "; - SS << e.what(); - logger.always(SS.str().c_str()); - die("Sanitizer failed to parse options.\n"); - } - - if (!OptionsEnvMap.has_value()) { - return; - } - - const char *TrueStrings[] = {"1", "true"}; - const char *FalseStrings[] = {"0", "false"}; - - auto InplaceToLower = [](std::string &S) { - std::transform(S.begin(), S.end(), S.begin(), - [](unsigned char C) { return std::tolower(C); }); - }; - auto IsTrue = [&](const std::string &S) { - return std::any_of(std::begin(TrueStrings), std::end(TrueStrings), - [&](const char *CS) { return S == CS; }); - }; - auto IsFalse = [&](const std::string &S) { - return std::any_of(std::begin(FalseStrings), std::end(FalseStrings), - [&](const char *CS) { return S == CS; }); - }; - - auto SetBoolOption = [&](const std::string &Name, bool &Opt) { - auto KV = OptionsEnvMap->find(Name); - if (KV != OptionsEnvMap->end()) { - auto Value = KV->second.front(); - InplaceToLower(Value); - if (IsTrue(Value)) { - Opt = true; - } else if (IsFalse(Value)) { - Opt = false; - } else { - std::stringstream SS; - SS << "\"" << Name << "\" is set to \"" << Value - << "\", which is not an valid setting. "; - SS << "Acceptable input are: for enable, use:"; - for (auto &S : TrueStrings) { - SS << " \"" << S << "\""; - } - SS << "; "; - SS << "for disable, use:"; - for (auto &S : FalseStrings) { - SS << " \"" << S << "\""; - } - SS << "."; - logger.error(SS.str().c_str()); - die("Sanitizer failed to parse options.\n"); - } - } - }; - - SetBoolOption("debug", Debug); - SetBoolOption("detect_locals", DetectLocals); - SetBoolOption("detect_privates", DetectPrivates); - SetBoolOption("detect_kernel_arguments", DetectKernelArguments); - - auto KV = OptionsEnvMap->find("quarantine_size_mb"); - if (KV != OptionsEnvMap->end()) { - const auto &Value = KV->second.front(); - try { - auto temp_long = std::stoul(Value); - if (temp_long > UINT32_MAX) { - throw std::out_of_range(""); - } - MaxQuarantineSizeMB = temp_long; - } catch (...) { - logger.error("\"quarantine_size_mb\" should be " - "an integer in range[0, {}].", - UINT32_MAX); - die("Sanitizer failed to parse options.\n"); - } - } - - KV = OptionsEnvMap->find("redzone"); - if (KV != OptionsEnvMap->end()) { - const auto &Value = KV->second.front(); - try { - MinRZSize = std::stoul(Value); - if (MinRZSize < 16) { - MinRZSize = 16; - logger.warning("Trying to set redzone size to a " - "value less than 16 is ignored."); - } - } catch (...) { - logger.error( - "\"redzone\" should be an integer in range[0, 16]."); - die("Sanitizer failed to parse options.\n"); - } - } - - KV = OptionsEnvMap->find("max_redzone"); - if (KV != OptionsEnvMap->end()) { - const auto &Value = KV->second.front(); - try { - MaxRZSize = std::stoul(Value); - if (MaxRZSize > 2048) { - MaxRZSize = 2048; - logger.warning("Trying to set max redzone size to a " - "value greater than 2048 is ignored."); - } - } catch (...) { - logger.error( - "\"max_redzone\" should be an integer in range[0, 2048]."); - die("Sanitizer failed to parse options.\n"); - } - } - } + explicit AsanOptions(); }; -inline const AsanOptions &Options(logger::Logger &logger) { - return AsanOptions::getInstance(logger); -} - } // namespace ur_sanitizer_layer diff --git a/source/loader/layers/sanitizer/asan_report.cpp b/source/loader/layers/sanitizer/asan_report.cpp index 047ba905e0..43c4f6df0f 100644 --- a/source/loader/layers/sanitizer/asan_report.cpp +++ b/source/loader/layers/sanitizer/asan_report.cpp @@ -137,7 +137,7 @@ void ReportUseAfterFree(const DeviceSanitizerReport &Report, getContext()->logger.always(" #0 {} {}:{}", Func, File, Report.Line); getContext()->logger.always(""); - if (Options(getContext()->logger).MaxQuarantineSizeMB > 0) { + if (getContext()->interceptor->getOptions().MaxQuarantineSizeMB > 0) { auto AllocInfoItOp = getContext()->interceptor->findAllocInfoByAddress(Report.Address); @@ -172,10 +172,11 @@ void ReportInvalidKernelArgument(ur_kernel_handle_t Kernel, uint32_t ArgIndex, DemangleName(GetKernelName(Kernel))); Stack.print(); auto &AI = VR.AI; + ArgIndex = ArgIndex + 1; switch (VR.Type) { case ValidateUSMResult::MAYBE_HOST_POINTER: getContext()->logger.always("The {}th argument {} is not a USM pointer", - ArgIndex + 1, (void *)Addr); + ArgIndex, (void *)Addr); break; case ValidateUSMResult::RELEASED_POINTER: getContext()->logger.always( diff --git a/source/loader/layers/sanitizer/asan_statistics.cpp b/source/loader/layers/sanitizer/asan_statistics.cpp new file mode 100644 index 0000000000..8d1389296d --- /dev/null +++ b/source/loader/layers/sanitizer/asan_statistics.cpp @@ -0,0 +1,160 @@ +/* + * + * Copyright (C) 2024 Intel Corporation + * + * Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. + * See LICENSE.TXT + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file asan_statistics.cpp + * + */ + +#include "asan_statistics.hpp" +#include "asan_interceptor.hpp" +#include "ur_sanitizer_layer.hpp" + +#include + +namespace ur_sanitizer_layer { + +struct AsanStats { + void UpdateUSMMalloced(uptr MallocedSize, uptr RedzoneSize); + void UpdateUSMFreed(uptr FreedSize); + void UpdateUSMRealFreed(uptr FreedSize, uptr RedzoneSize); + + void UpdateShadowMmaped(uptr ShadowSize); + void UpdateShadowMalloced(uptr ShadowSize); + void UpdateShadowFreed(uptr ShadowSize); + + void Print(ur_context_handle_t Context); + + private: + std::atomic UsmMalloced; + std::atomic UsmMallocedRedzones; + + // Quarantined memory + std::atomic UsmFreed; + + std::atomic ShadowMmaped; + std::atomic ShadowMalloced; + + double Overhead = 0.0; + + void UpdateOverhead(); +}; + +void AsanStats::Print(ur_context_handle_t Context) { + getContext()->logger.always("Stats: Context {}", (void *)Context); + getContext()->logger.always("Stats: peak memory overhead: {}%", + Overhead * 100); +} + +void AsanStats::UpdateUSMMalloced(uptr MallocedSize, uptr RedzoneSize) { + UsmMalloced += MallocedSize; + UsmMallocedRedzones += RedzoneSize; + getContext()->logger.debug( + "Stats: UpdateUSMMalloced(UsmMalloced={}, UsmMallocedRedzones={})", + UsmMalloced, UsmMallocedRedzones); + UpdateOverhead(); +} + +void AsanStats::UpdateUSMFreed(uptr FreedSize) { + UsmFreed += FreedSize; + getContext()->logger.debug("Stats: UpdateUSMFreed(UsmFreed={})", UsmFreed); +} + +void AsanStats::UpdateUSMRealFreed(uptr FreedSize, uptr RedzoneSize) { + UsmMalloced -= FreedSize; + UsmMallocedRedzones -= RedzoneSize; + if (getContext()->interceptor->getOptions().MaxQuarantineSizeMB) { + UsmFreed -= FreedSize; + } + getContext()->logger.debug( + "Stats: UpdateUSMRealFreed(UsmMalloced={}, UsmMallocedRedzones={})", + UsmMalloced, UsmMallocedRedzones); + UpdateOverhead(); +} + +void AsanStats::UpdateShadowMmaped(uptr ShadowSize) { + ShadowMmaped += ShadowSize; + getContext()->logger.debug("Stats: UpdateShadowMmaped(ShadowMmaped={})", + ShadowMmaped); + UpdateOverhead(); +} + +void AsanStats::UpdateShadowMalloced(uptr ShadowSize) { + ShadowMalloced += ShadowSize; + getContext()->logger.debug("Stats: UpdateShadowMalloced(ShadowMalloced={})", + ShadowMalloced); + UpdateOverhead(); +} + +void AsanStats::UpdateShadowFreed(uptr ShadowSize) { + ShadowMalloced -= ShadowSize; + getContext()->logger.debug("Stats: UpdateShadowFreed(ShadowMalloced={})", + ShadowMalloced); + UpdateOverhead(); +} + +void AsanStats::UpdateOverhead() { + auto ShadowSize = ShadowMmaped + ShadowMalloced; + auto TotalSize = UsmMalloced + ShadowSize; + if (TotalSize == 0) { + return; + } + auto NewOverhead = (ShadowSize + UsmMallocedRedzones) / (double)TotalSize; + Overhead = std::max(Overhead, NewOverhead); +} + +void AsanStatsWrapper::UpdateUSMMalloced(uptr MallocedSize, uptr RedzoneSize) { + if (Stat) { + Stat->UpdateUSMMalloced(MallocedSize, RedzoneSize); + } +} + +void AsanStatsWrapper::UpdateUSMFreed(uptr FreedSize) { + if (Stat) { + Stat->UpdateUSMFreed(FreedSize); + } +} + +void AsanStatsWrapper::UpdateUSMRealFreed(uptr FreedSize, uptr RedzoneSize) { + if (Stat) { + Stat->UpdateUSMRealFreed(FreedSize, RedzoneSize); + } +} + +void AsanStatsWrapper::UpdateShadowMmaped(uptr ShadowSize) { + if (Stat) { + Stat->UpdateShadowMmaped(ShadowSize); + } +} + +void AsanStatsWrapper::UpdateShadowMalloced(uptr ShadowSize) { + if (Stat) { + Stat->UpdateShadowMalloced(ShadowSize); + } +} + +void AsanStatsWrapper::UpdateShadowFreed(uptr ShadowSize) { + if (Stat) { + Stat->UpdateShadowFreed(ShadowSize); + } +} + +void AsanStatsWrapper::Print(ur_context_handle_t Context) { + if (Stat) { + Stat->Print(Context); + } +} + +AsanStatsWrapper::AsanStatsWrapper() : Stat(nullptr) { + if (getContext()->interceptor->getOptions().PrintStats) { + Stat = new AsanStats; + } +} + +AsanStatsWrapper::~AsanStatsWrapper() { delete Stat; } + +} // namespace ur_sanitizer_layer diff --git a/source/loader/layers/sanitizer/asan_statistics.hpp b/source/loader/layers/sanitizer/asan_statistics.hpp new file mode 100644 index 0000000000..2e84df1dce --- /dev/null +++ b/source/loader/layers/sanitizer/asan_statistics.hpp @@ -0,0 +1,40 @@ +/* + * + * Copyright (C) 2024 Intel Corporation + * + * Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. + * See LICENSE.TXT + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file asan_statistics.hpp + * + */ + +#pragma once + +#include "common.hpp" + +namespace ur_sanitizer_layer { + +struct AsanStats; + +struct AsanStatsWrapper { + + AsanStatsWrapper(); + ~AsanStatsWrapper(); + + void UpdateUSMMalloced(uptr MallocedSize, uptr RedzoneSize); + void UpdateUSMFreed(uptr FreedSize); + void UpdateUSMRealFreed(uptr FreedSize, uptr RedzoneSize); + + void UpdateShadowMmaped(uptr ShadowSize); + void UpdateShadowMalloced(uptr ShadowSize); + void UpdateShadowFreed(uptr ShadowSize); + + void Print(ur_context_handle_t Context); + + private: + AsanStats *Stat; +}; + +} // namespace ur_sanitizer_layer diff --git a/source/loader/layers/sanitizer/ur_sanddi.cpp b/source/loader/layers/sanitizer/ur_sanddi.cpp index 8930049ea0..39ea834164 100644 --- a/source/loader/layers/sanitizer/ur_sanddi.cpp +++ b/source/loader/layers/sanitizer/ur_sanddi.cpp @@ -1384,7 +1384,7 @@ __urdlllocal ur_result_t UR_APICALL urKernelSetArgPointer( "==== urKernelSetArgPointer (argIndex={}, pArgValue={})", argIndex, pArgValue); - if (Options(getContext()->logger).DetectKernelArguments) { + if (getContext()->interceptor->getOptions().DetectKernelArguments) { auto KI = getContext()->interceptor->getKernelInfo(hKernel); std::scoped_lock Guard(KI->Mutex); KI->PointerArgs[argIndex] = {pArgValue, GetCurrentBacktrace()}; @@ -1675,7 +1675,7 @@ ur_result_t context_t::init(ur_dditable_t *dditable, if (enabledLayerNames.count("UR_LAYER_ASAN")) { enabledType = SanitizerType::AddressSanitizer; - interceptor = std::make_unique(logger); + interceptor = std::make_unique(); } else if (enabledLayerNames.count("UR_LAYER_MSAN")) { enabledType = SanitizerType::MemorySanitizer; } else if (enabledLayerNames.count("UR_LAYER_TSAN")) {