| 1 | //===-- ubsan_offload_hsa_interceptors.cpp ----------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include <dlfcn.h> |
| 10 | |
| 11 | #include "interception/interception.h" |
| 12 | #include "sanitizer_common/sanitizer_atomic.h" |
| 13 | #include "sanitizer_common/sanitizer_common.h" |
| 14 | #include "sanitizer_common/sanitizer_libc.h" |
| 15 | #include "sanitizer_common/sanitizer_mutex.h" |
| 16 | #include "sanitizer_common/sanitizer_offload.h" |
| 17 | #include "sanitizer_common/sanitizer_platform.h" |
| 18 | #include "ubsan_diag.h" |
| 19 | #include "ubsan_offload.h" |
| 20 | |
| 21 | #if !SANITIZER_LINUX |
| 22 | #error "Offload UBSan reporting is supported on Linux only" |
| 23 | #endif |
| 24 | |
| 25 | using namespace __sanitizer; |
| 26 | using namespace __ubsan; |
| 27 | |
| 28 | namespace __ubsan { |
| 29 | |
| 30 | static StaticSpinMutex InitMutex; |
| 31 | static atomic_uint8_t Initialized; |
| 32 | |
| 33 | void Initialize() { |
| 34 | if (LIKELY(atomic_load(&Initialized, memory_order_acquire))) |
| 35 | return; |
| 36 | SpinMutexLock L(&InitMutex); |
| 37 | if (atomic_load(a: &Initialized, mo: memory_order_relaxed)) |
| 38 | return; |
| 39 | SanitizerToolName = "UndefinedBehaviorSanitizer" ; |
| 40 | __ubsan_set_offload_symbolize( |
| 41 | Fn: [](uptr PC) { return Offload::Get().Symbolize(PC); }); |
| 42 | Offload::Get().RegisterHandler(Fn: HandleOffloadReport); |
| 43 | Atexit(function: [] { Offload::Get().UntrackImages(); }); |
| 44 | AddDieCallback(callback: [] { Offload::Get().UntrackImages(); }); |
| 45 | atomic_store(a: &Initialized, v: 1, mo: memory_order_release); |
| 46 | } |
| 47 | |
| 48 | } // namespace __ubsan |
| 49 | |
| 50 | // The shared runtime exports these to every program, act as if HSA is absent |
| 51 | // when it is not loaded. |
| 52 | #define UBSAN_HSA_ENTER(name) \ |
| 53 | Initialize(); \ |
| 54 | if (UNLIKELY(!REAL(name))) { \ |
| 55 | INTERCEPT_FUNCTION(name); \ |
| 56 | if (UNLIKELY(!REAL(name))) { \ |
| 57 | VReport(1, "%s: cannot find %s in this process\n", SanitizerToolName, \ |
| 58 | #name); \ |
| 59 | return HSA_STATUS_ERROR; \ |
| 60 | } \ |
| 61 | } |
| 62 | |
| 63 | #define UBSAN_HSA_FORWARD(name, ...) \ |
| 64 | UBSAN_HSA_ENTER(name); \ |
| 65 | if (UNLIKELY(!Offload::Get().Ready())) \ |
| 66 | return REAL(name)(__VA_ARGS__); |
| 67 | |
| 68 | static bool FromHsa(void *P) { |
| 69 | Dl_info Info = {}; |
| 70 | if (!dladdr(address: P, info: &Info) || !Info.dli_fname) |
| 71 | return false; |
| 72 | return internal_strstr(haystack: Info.dli_fname, SANITIZER_HSA_LIBRARY); |
| 73 | } |
| 74 | |
| 75 | // Callers bind to whichever 'hsa_init' comes first, if HSA was loaded before |
| 76 | // the runtime the interceptors are bypassed. |
| 77 | static void CheckInterposed() { |
| 78 | void *Sym = dlsym(RTLD_DEFAULT, name: "hsa_init" ); |
| 79 | if (!Sym || !FromHsa(P: Sym)) |
| 80 | return; |
| 81 | Report(format: "WARNING: %s: the runtime is loaded too late to intercept HSA, GPU " |
| 82 | "errors will not be reported. Link the runtime first or use " |
| 83 | "LD_PRELOAD.\n" , |
| 84 | SanitizerToolName); |
| 85 | } |
| 86 | |
| 87 | INTERCEPTOR(hsa_status_t, hsa_init, void) { |
| 88 | UBSAN_HSA_ENTER(hsa_init); |
| 89 | |
| 90 | hsa_status_t Status = REAL(hsa_init)(); |
| 91 | if (Status != HSA_STATUS_SUCCESS) |
| 92 | return Status; |
| 93 | |
| 94 | Offload::Get().Init(); |
| 95 | return Status; |
| 96 | } |
| 97 | |
| 98 | INTERCEPTOR(hsa_status_t, hsa_shut_down, void) { |
| 99 | UBSAN_HSA_ENTER(hsa_shut_down); |
| 100 | |
| 101 | Offload::Get().Shutdown(); |
| 102 | return REAL(hsa_shut_down)(); |
| 103 | } |
| 104 | |
| 105 | INTERCEPTOR(hsa_status_t, hsa_executable_freeze, hsa_executable_t Executable, |
| 106 | const char *Options) { |
| 107 | UBSAN_HSA_FORWARD(hsa_executable_freeze, Executable, Options); |
| 108 | |
| 109 | hsa_status_t Status = REAL(hsa_executable_freeze)(Executable, Options); |
| 110 | if (Status == HSA_STATUS_SUCCESS) |
| 111 | Offload::Get().TrackExecutable(Exec: Executable); |
| 112 | return Status; |
| 113 | } |
| 114 | |
| 115 | INTERCEPTOR(hsa_status_t, hsa_executable_destroy, hsa_executable_t Executable) { |
| 116 | UBSAN_HSA_FORWARD(hsa_executable_destroy, Executable); |
| 117 | |
| 118 | Offload::Get().UntrackExecutable(Exec: Executable); |
| 119 | return REAL(hsa_executable_destroy)(Executable); |
| 120 | } |
| 121 | |
| 122 | extern "C" void __ubsan_offload_init() { __ubsan::Initialize(); } |
| 123 | |
| 124 | __attribute__((constructor(0))) static void UbsanOffloadDynInit() { |
| 125 | __ubsan_offload_init(); |
| 126 | CheckInterposed(); |
| 127 | } |
| 128 | |