[compiler-rt] [compiler-rt] Support Windows GNU sanitizer runtimes (PR #209902)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 21 12:03:13 PDT 2026
https://github.com/oltolm updated https://github.com/llvm/llvm-project/pull/209902
>From 3cfd170d68267e945690d8d3b27849c3ec2938d1 Mon Sep 17 00:00:00 2001
From: Oleg Tolmatcev <oleg.tolmatcev at gmail.com>
Date: Thu, 30 Jul 2026 19:52:20 +0200
Subject: [PATCH] [compiler-rt] Support Windows GNU sanitizer runtimes
Co-authored-by: Hannes Domani <ssbssa at yahoo.de>
---
compiler-rt/lib/asan/asan_globals_win.cpp | 16 ++++--
compiler-rt/lib/asan/asan_win.cpp | 50 ++++++++++++++++---
.../asan/asan_win_common_runtime_thunk.cpp | 2 +
.../lib/asan/asan_win_common_runtime_thunk.h | 22 ++++----
.../lib/interception/interception_win.cpp | 27 ++++++++++
.../sanitizer_internal_defs.h | 8 +--
.../lib/sanitizer_common/sanitizer_win.cpp | 4 +-
.../lib/sanitizer_common/sanitizer_win_defs.h | 18 ++++++-
.../sanitizer_win_thunk_interception.cpp | 2 +
.../sanitizer_win_thunk_interception.h | 41 ++++++++-------
compiler-rt/lib/ubsan/ubsan_handlers.cpp | 7 ++-
11 files changed, 152 insertions(+), 45 deletions(-)
diff --git a/compiler-rt/lib/asan/asan_globals_win.cpp b/compiler-rt/lib/asan/asan_globals_win.cpp
index aeef42749fa8c..5c038ebb3f228 100644
--- a/compiler-rt/lib/asan/asan_globals_win.cpp
+++ b/compiler-rt/lib/asan/asan_globals_win.cpp
@@ -14,10 +14,16 @@
#if SANITIZER_WINDOWS
# include "sanitizer_common/sanitizer_win_defs.h"
+# if defined(__GNUC__) && !defined(__clang__)
+extern "C" void __debugbreak();
+# endif
+
namespace __asan {
-# pragma section(".ASAN$GA", read, write)
-# pragma section(".ASAN$GZ", read, write)
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".ASAN$GA", read, write)
+# pragma section(".ASAN$GZ", read, write)
+# endif
extern "C" alignas(sizeof(__asan_global))
IN_SECTION(".ASAN$GA") __asan_global __asan_globals_start = {};
extern "C" alignas(sizeof(__asan_global))
@@ -52,8 +58,10 @@ static void unregister_dso_globals() {
}
// Register globals
-# pragma section(".CRT$XCU", long, read)
-# pragma section(".CRT$XTX", long, read)
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".CRT$XCU", long, read)
+# pragma section(".CRT$XTX", long, read)
+# endif
extern "C" IN_SECTION(".CRT$XCU") void (*const __asan_dso_reg_hook)() =
®ister_dso_globals;
extern "C" IN_SECTION(".CRT$XTX") void (*const __asan_dso_unreg_hook)() =
diff --git a/compiler-rt/lib/asan/asan_win.cpp b/compiler-rt/lib/asan/asan_win.cpp
index c671a2d6327eb..2e6781988d5eb 100644
--- a/compiler-rt/lib/asan/asan_win.cpp
+++ b/compiler-rt/lib/asan/asan_win.cpp
@@ -15,6 +15,9 @@
#include "sanitizer_common/sanitizer_platform.h"
#if SANITIZER_WINDOWS
# define WIN32_LEAN_AND_MEAN
+# if defined(__GNUC__) && !defined(__clang__)
+# include <malloc.h>
+# endif
# include <stdlib.h>
# include <windows.h>
@@ -228,7 +231,17 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) {
// ---------------------- TSD ---------------- {{{
static bool tsd_key_inited = false;
+# if !defined(__GNUC__) || defined(__clang__)
static __declspec(thread) void *fake_tsd = 0;
+# else
+struct TlsVoidPtr {
+ operator void*() { return TlsGetValue(tls_index); }
+ void operator=(void* v) { TlsSetValue(tls_index, v); }
+
+ DWORD tls_index;
+};
+static TlsVoidPtr fake_tsd;
+# endif
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/ns-winternl-_teb
// "[This structure may be altered in future versions of Windows. Applications
@@ -261,7 +274,7 @@ void AsanTSDInit(void (*destructor)(void *tsd)) {
void *AsanTSDGet() {
CHECK(tsd_key_inited);
- return IsTlsInitialized() ? fake_tsd : nullptr;
+ return IsTlsInitialized() ? (void*)fake_tsd : nullptr;
}
void AsanTSDSet(void *tsd) {
@@ -332,7 +345,12 @@ ShadowExceptionHandler(PEXCEPTION_POINTERS exception_pointers) {
#endif
void InitializePlatformExceptionHandlers() {
-#if SANITIZER_WINDOWS64
+# if defined(__GNUC__) && !defined(__clang__)
+ fake_tsd.tls_index = TlsAlloc();
+ CHECK(fake_tsd.tls_index != TLS_OUT_OF_INDEXES);
+# endif
+
+# if SANITIZER_WINDOWS64
// On Win64, we map memory on demand with access violation handler.
// Install our exception handler.
CHECK(AddVectoredExceptionHandler(TRUE, &ShadowExceptionHandler));
@@ -340,7 +358,19 @@ void InitializePlatformExceptionHandlers() {
}
bool IsSystemHeapAddress(uptr addr) {
- return ::HeapValidate(GetProcessHeap(), 0, (void *)addr) != FALSE;
+ HANDLE process_heap = GetProcessHeap();
+ if (::HeapValidate(process_heap, 0, (void*)addr))
+ return true;
+
+# if defined(__GNUC__) && !defined(__clang__)
+ HANDLE crt_heap = (HANDLE)_get_heap_handle();
+ if (crt_heap == process_heap)
+ return false;
+
+ return ::HeapValidate(crt_heap, 0, (void*)addr) != FALSE;
+# else
+ return false;
+# endif
}
// We want to install our own exception handler (EH) to print helpful reports
@@ -384,7 +414,9 @@ bool HandleDlopenInit() {
// beginning of C++ initialization. We set our priority to XCAB to run
// immediately after the CRT runs. This way, our exception filter is called
// first and we can delegate to their filter if appropriate.
-#pragma section(".CRT$XCAB", long, read)
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".CRT$XCAB", long, read)
+# endif
IN_SECTION(".CRT$XCAB") int (*__intercept_seh)() = __asan_set_seh_filter;
// Piggyback on the TLS initialization callback directory to initialize asan as
@@ -396,7 +428,9 @@ static void NTAPI asan_thread_init(void *module, DWORD reason, void *reserved) {
__asan_init();
}
-#pragma section(".CRT$XLAB", long, read)
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".CRT$XLAB", long, read)
+# endif
IN_SECTION(".CRT$XLAB")
void(NTAPI* __asan_tls_init)(void*, unsigned long, void*) = asan_thread_init;
# endif
@@ -410,10 +444,14 @@ static void NTAPI asan_thread_exit(void *module, DWORD reason, void *reserved) {
}
}
-#pragma section(".CRT$XLY", long, read)
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".CRT$XLY", long, read)
+# endif
IN_SECTION(".CRT$XLY")
void(NTAPI* __asan_tls_exit)(void*, unsigned long, void*) = asan_thread_exit;
+extern "C" void (*const __asan_dso_reg_hook)();
+
WIN_FORCE_LINK(__asan_dso_reg_hook)
// }}}
diff --git a/compiler-rt/lib/asan/asan_win_common_runtime_thunk.cpp b/compiler-rt/lib/asan/asan_win_common_runtime_thunk.cpp
index f49296f624360..1c6bac48141b2 100644
--- a/compiler-rt/lib/asan/asan_win_common_runtime_thunk.cpp
+++ b/compiler-rt/lib/asan/asan_win_common_runtime_thunk.cpp
@@ -105,7 +105,9 @@ extern "C" IN_SECTION(".CRT$XCAB") int (*__asan_seh_interceptor)() =
WIN_FORCE_LINK(__asan_seh_interceptor)
}
+# ifdef SANITIZER_STATIC_RUNTIME_THUNK
WIN_FORCE_LINK(__asan_dso_reg_hook)
+# endif
#endif // defined(SANITIZER_DYNAMIC_RUNTIME_THUNK) ||
// defined(SANITIZER_STATIC_RUNTIME_THUNK)
diff --git a/compiler-rt/lib/asan/asan_win_common_runtime_thunk.h b/compiler-rt/lib/asan/asan_win_common_runtime_thunk.h
index 8d457f75f5526..fcb695e0fa92a 100644
--- a/compiler-rt/lib/asan/asan_win_common_runtime_thunk.h
+++ b/compiler-rt/lib/asan/asan_win_common_runtime_thunk.h
@@ -18,21 +18,23 @@
defined(SANITIZER_DYNAMIC_RUNTIME_THUNK)
# include "sanitizer_common/sanitizer_win_defs.h"
-# pragma section(".CRT$XIB", long, \
- read) // C initializer (during C init before dyninit)
-# pragma section(".CRT$XID", long, \
- read) // First C initializer after CRT initializers
-# pragma section(".CRT$XCAB", long, \
- read) // First C++ initializer after startup initializers
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".CRT$XIB", long, \
+ read) // C initializer (during C init before dyninit)
+# pragma section(".CRT$XID", long, \
+ read) // First C initializer after CRT initializers
+# pragma section(".CRT$XCAB", long, \
+ read) // First C++ initializer after startup initializers
-# pragma section(".CRT$XTW", long, read) // First ASAN globals terminator
-# pragma section(".CRT$XTY", long, read) // Last ASAN globals terminator
+# pragma section(".CRT$XTW", long, read) // First ASAN globals terminator
+# pragma section(".CRT$XTY", long, read) // Last ASAN globals terminator
-# pragma section(".CRT$XLAB", long, read) // First TLS initializer
+# pragma section(".CRT$XLAB", long, read) // First TLS initializer
+# endif
# ifdef SANITIZER_STATIC_RUNTIME_THUNK
extern "C" void __asan_initialize_static_thunk();
# endif
#endif // defined(SANITIZER_STATIC_RUNTIME_THUNK) ||
- // defined(SANITIZER_DYNAMIC_RUNTIME_THUNK)
+ // defined(SANITIZER_DYNAMIC_RUNTIME_THUNK)
\ No newline at end of file
diff --git a/compiler-rt/lib/interception/interception_win.cpp b/compiler-rt/lib/interception/interception_win.cpp
index 86a622ed48b50..407c6f591510a 100644
--- a/compiler-rt/lib/interception/interception_win.cpp
+++ b/compiler-rt/lib/interception/interception_win.cpp
@@ -1118,6 +1118,33 @@ bool OverrideFunctionWithDetour(
bool OverrideFunctionWithRedirectJump(
uptr old_func, uptr new_func, uptr *orig_old_func) {
+# if SANITIZER_WINDOWS64 && defined(__GNUC__) && !defined(__clang__)
+ u8* old_u8 = (u8*)old_func;
+
+ // relative indirect jump
+ if (old_u8[0] == 0xff && old_u8[1] == 0x25) {
+ sptr relative_offset = *(s32*)(old_func + 2);
+ uptr* absolute_target_ptr = (uptr*)(old_func + 6 + relative_offset);
+ if (orig_old_func)
+ *orig_old_func = *absolute_target_ptr;
+
+ // Change memory protection to writable.
+ DWORD protection = 0;
+ if (!ChangeMemoryProtection((uptr)absolute_target_ptr, sizeof(uptr),
+ &protection))
+ return false;
+
+ *absolute_target_ptr = new_func;
+
+ // Restore previous memory protection.
+ if (!RestoreMemoryProtection((uptr)absolute_target_ptr, sizeof(uptr),
+ protection))
+ return false;
+
+ return true;
+ }
+# endif
+
// Check whether the first instruction is a relative jump.
if (*(u8*)old_func != 0xE9)
return false;
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
index c694897b6556b..c4bfee6c479e3 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
@@ -63,10 +63,10 @@
// For example:
// SANITIZER_INTERFACE_WEAK_DEF(bool, compare, int a, int b) { return a > b; }
//
-#if SANITIZER_WINDOWS
-#include "sanitizer_win_defs.h"
-# define SANITIZER_INTERFACE_WEAK_DEF(ReturnType, Name, ...) \
- WIN_WEAK_EXPORT_DEF(ReturnType, Name, __VA_ARGS__)
+#if SANITIZER_WINDOWS && (!defined(__GNUC__) || defined(__clang__))
+# include "sanitizer_win_defs.h"
+# define SANITIZER_INTERFACE_WEAK_DEF(ReturnType, Name, ...) \
+ WIN_WEAK_EXPORT_DEF(ReturnType, Name, __VA_ARGS__)
#else
# define SANITIZER_INTERFACE_WEAK_DEF(ReturnType, Name, ...) \
extern "C" SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE \
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
index 52f6cb6ea9cc0..4c4b5cf964256 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
@@ -759,7 +759,9 @@ static int RunAtexit() {
return ret;
}
-#pragma section(".CRT$XID", long, read)
+# if !defined(__GNUC__) || defined(__clang__)
+# pragma section(".CRT$XID", long, read)
+# endif
IN_SECTION(".CRT$XID") int (*__run_atexit)() = RunAtexit;
# endif
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h
index 7f36f3ae743c3..e25dedb3d2fbb 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h
@@ -43,9 +43,13 @@
#define STRINGIFY_(A) #A
#define STRINGIFY(A) STRINGIFY_(A)
-# define IN_SECTION(n) __declspec(allocate(n))
+# if !defined(__GNUC__) || defined(__clang__)
+# define IN_SECTION(n) __declspec(allocate(n))
+# else
+# define IN_SECTION(n) __attribute__((section(n)))
+# endif
-# if !SANITIZER_GO
+# if !SANITIZER_GO && defined(__clang__)
// ----------------- A workaround for the absence of weak symbols --------------
// We don't have a direct equivalent of weak symbols when using MSVC, but we can
@@ -163,6 +167,16 @@
// }
//
+# elif !SANITIZER_GO
+
+# define WIN_FORCE_LINK(Name) \
+ extern "C" __typeof__(Name) Name; \
+ static __attribute__((used)) __typeof__(&Name) __force_link_##Name = \
+ &Name;
+
+# define WIN_WEAK_EXPORT_DEF(ReturnType, Name, ...) \
+ extern "C" __attribute__((dllexport)) ReturnType Name(__VA_ARGS__)
+
# else // SANITIZER_GO
// Go neither needs nor wants weak references.
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.cpp
index d9cf6ac0a76af..794f61fd34418 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.cpp
@@ -91,6 +91,7 @@ extern "C" int __sanitizer_thunk_init() {
# pragma section(".CRT$XIB", long, read)
extern "C" IN_SECTION(".CRT$XIB") int (*__sanitizer_thunk_init_ptr)() =
__sanitizer_thunk_init;
+WIN_FORCE_LINK(__sanitizer_thunk_init_ptr)
static void WINAPI sanitizer_thunk_thread_init(void *mod, unsigned long reason,
void *reserved) {
@@ -102,5 +103,6 @@ static void WINAPI sanitizer_thunk_thread_init(void *mod, unsigned long reason,
extern "C" IN_SECTION(".CRT$XLAB") void(
WINAPI* __sanitizer_thunk_thread_init_ptr)(void*, unsigned long, void*) =
sanitizer_thunk_thread_init;
+WIN_FORCE_LINK(__sanitizer_thunk_thread_init_ptr)
#endif // defined(SANITIZER_STATIC_RUNTIME_THUNK) ||
// defined(SANITIZER_DYNAMIC_RUNTIME_THUNK)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.h b/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.h
index 4beaab5f15b93..492ef6b2b98b2 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.h
@@ -69,21 +69,28 @@ void initialize_thunks(const sanitizer_thunk *begin,
# define REGISTER_WEAK_FUNCTION_ADDRESS(fn) &fn
#endif
-#define REGISTER_WEAK_FUNCTION(local_function) \
- extern "C" void local_function(); \
- extern "C" void WEAK_EXPORT_NAME(local_function)(); \
- WIN_WEAK_IMPORT_DEF(local_function) \
- REGISTER_WEAK_OPTNONE static int register_weak_##local_function() { \
- if ((uintptr_t)REGISTER_WEAK_FUNCTION_ADDRESS(local_function) != \
- (uintptr_t)REGISTER_WEAK_FUNCTION_ADDRESS( \
- WEAK_EXPORT_NAME(local_function))) { \
- return __sanitizer::register_weak( \
- SANITIZER_STRINGIFY(WEAK_EXPORT_NAME(local_function)), \
- reinterpret_cast<__sanitizer::uptr>(local_function)); \
- } \
- return 0; \
- } \
- __pragma(section(".WEAK$M", long, read)) __declspec(allocate( \
- ".WEAK$M")) int (*__sanitizer_register_weak_##local_function)() = \
- register_weak_##local_function;
+#if !defined(__GNUC__) || defined(__clang__)
+# define REGISTER_WEAK_FUNCTION(local_function) \
+ extern "C" void local_function(); \
+ extern "C" void WEAK_EXPORT_NAME(local_function)(); \
+ WIN_WEAK_IMPORT_DEF(local_function) \
+ REGISTER_WEAK_OPTNONE static int register_weak_##local_function() { \
+ if ((uintptr_t)REGISTER_WEAK_FUNCTION_ADDRESS(local_function) != \
+ (uintptr_t)REGISTER_WEAK_FUNCTION_ADDRESS( \
+ WEAK_EXPORT_NAME(local_function))) { \
+ return __sanitizer::register_weak( \
+ SANITIZER_STRINGIFY(WEAK_EXPORT_NAME(local_function)), \
+ reinterpret_cast<__sanitizer::uptr>(local_function)); \
+ } \
+ return 0; \
+ } \
+ __pragma(section(".WEAK$M", long, read)) __declspec(allocate( \
+ ".WEAK$M")) int (*__sanitizer_register_weak_##local_function)() = \
+ register_weak_##local_function;
+#else
+// GNU ld does not support /alternatename. User overrides of weak sanitizer
+// functions are not forwarded to the sanitizer DLL on this toolchain.
+# define REGISTER_WEAK_FUNCTION(local_function) \
+ extern "C" void local_function();
+#endif
#endif // SANITIZER_WIN_THUNK_INTERCEPTION_H
diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.cpp b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
index 0cd8a437adf4d..af207ee6ccccf 100644
--- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp
+++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
@@ -935,7 +935,7 @@ static void handleCFIBadIcall(CFICheckFailData *Data, ValueHandle Function,
namespace __ubsan {
-#ifdef _WIN32
+#if defined(_WIN32) && (!defined(__GNUC__) || defined(__clang__))
extern "C" void __ubsan_handle_cfi_bad_type_default(CFICheckFailData *Data,
ValueHandle Vtable,
bool ValidVtable,
@@ -944,6 +944,11 @@ extern "C" void __ubsan_handle_cfi_bad_type_default(CFICheckFailData *Data,
}
WIN_WEAK_ALIAS(__ubsan_handle_cfi_bad_type, __ubsan_handle_cfi_bad_type_default)
+void __ubsan_handle_cfi_bad_type(CFICheckFailData *Data, ValueHandle Vtable,
+ bool ValidVtable, ReportOptions Opts);
+#elif defined(_WIN32)
+// GNU ld does not support /alternatename. The real implementation lives in
+// ubsan_handlers_cxx.cpp.
void __ubsan_handle_cfi_bad_type(CFICheckFailData *Data, ValueHandle Vtable,
bool ValidVtable, ReportOptions Opts);
#else
More information about the llvm-commits
mailing list