[compiler-rt] [compiler-rt] Support Windows GNU sanitizer runtimes (PR #209902)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 11:49:32 PDT 2026


https://github.com/oltolm updated https://github.com/llvm/llvm-project/pull/209902

>From 4671b848aa10f07aa60618766a687a2fa4af72c5 Mon Sep 17 00:00:00 2001
From: Oleg Tolmatcev <oleg.tolmatcev at gmail.com>
Date: Wed, 15 Jul 2026 18:07:03 +0200
Subject: [PATCH 1/3] [compiler-rt] Support Windows GNU sanitizer runtimes

Handle Windows GNU-specific section and weak-symbol behavior in the sanitizer runtime. Keep Clang on the existing Windows path because it also defines __GNUC__ on Windows.

Co-authored-by: Hannes Domani <ssbssa at yahoo.de>
---
 compiler-rt/lib/asan/asan_globals_win.cpp     | 31 +++++----
 compiler-rt/lib/asan/asan_malloc_win.cpp      | 14 +++--
 compiler-rt/lib/asan/asan_win.cpp             | 63 +++++++++++++++----
 .../asan/asan_win_common_runtime_thunk.cpp    | 11 ++--
 .../lib/asan/asan_win_common_runtime_thunk.h  | 20 +++---
 .../asan/asan_win_dynamic_runtime_thunk.cpp   |  8 +--
 compiler-rt/lib/builtins/emutls.c             |  1 +
 compiler-rt/lib/builtins/gcc_personality_v0.c |  2 +-
 .../lib/interception/interception_win.cpp     | 27 ++++++++
 .../sanitizer_internal_defs.h                 |  8 +--
 .../lib/sanitizer_common/sanitizer_win.cpp    |  8 ++-
 .../lib/sanitizer_common/sanitizer_win_defs.h | 26 ++++++--
 .../sanitizer_win_thunk_interception.cpp      | 24 ++++---
 .../sanitizer_win_thunk_interception.h        | 48 ++++++++------
 compiler-rt/lib/ubsan/ubsan_handlers.cpp      |  7 ++-
 15 files changed, 203 insertions(+), 95 deletions(-)

diff --git a/compiler-rt/lib/asan/asan_globals_win.cpp b/compiler-rt/lib/asan/asan_globals_win.cpp
index 2b59595dcd3bf..5c038ebb3f228 100644
--- a/compiler-rt/lib/asan/asan_globals_win.cpp
+++ b/compiler-rt/lib/asan/asan_globals_win.cpp
@@ -12,16 +12,23 @@
 
 #include "asan_interface_internal.h"
 #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))
-    __declspec(allocate(".ASAN$GA")) __asan_global __asan_globals_start = {};
+    IN_SECTION(".ASAN$GA") __asan_global __asan_globals_start = {};
 extern "C" alignas(sizeof(__asan_global))
-    __declspec(allocate(".ASAN$GZ")) __asan_global __asan_globals_end = {};
-#pragma comment(linker, "/merge:.ASAN=.data")
+    IN_SECTION(".ASAN$GZ") __asan_global __asan_globals_end = {};
+#  pragma comment(linker, "/merge:.ASAN=.data")
 
 static void call_on_globals(void (*hook)(__asan_global *, uptr)) {
   __asan_global *start = &__asan_globals_start + 1;
@@ -51,12 +58,14 @@ static void unregister_dso_globals() {
 }
 
 // Register globals
-#pragma section(".CRT$XCU", long, read)
-#pragma section(".CRT$XTX", long, read)
-extern "C" __declspec(allocate(".CRT$XCU"))
-void (*const __asan_dso_reg_hook)() = &register_dso_globals;
-extern "C" __declspec(allocate(".CRT$XTX"))
-void (*const __asan_dso_unreg_hook)() = &unregister_dso_globals;
+#  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)() =
+    &register_dso_globals;
+extern "C" IN_SECTION(".CRT$XTX") void (*const __asan_dso_unreg_hook)() =
+    &unregister_dso_globals;
 
 } // namespace __asan
 
diff --git a/compiler-rt/lib/asan/asan_malloc_win.cpp b/compiler-rt/lib/asan/asan_malloc_win.cpp
index e728591cc145e..abb1b496d1b40 100644
--- a/compiler-rt/lib/asan/asan_malloc_win.cpp
+++ b/compiler-rt/lib/asan/asan_malloc_win.cpp
@@ -14,12 +14,14 @@
 #include "sanitizer_common/sanitizer_allocator_interface.h"
 #include "sanitizer_common/sanitizer_platform.h"
 #if SANITIZER_WINDOWS
-#include "asan_allocator.h"
-#include "asan_interceptors.h"
-#include "asan_internal.h"
-#include "asan_stack.h"
-#include "interception/interception.h"
-#include <stddef.h>
+#  include <stddef.h>
+
+#  include "asan_allocator.h"
+#  include "asan_interceptors.h"
+#  include "asan_internal.h"
+#  include "asan_stack.h"
+#  include "interception/interception.h"
+#  include "sanitizer_common/sanitizer_win_defs.h"
 
 // Intentionally not including windows.h here, to avoid the risk of
 // pulling in conflicting declarations of these functions. (With mingw-w64,
diff --git a/compiler-rt/lib/asan/asan_win.cpp b/compiler-rt/lib/asan/asan_win.cpp
index 845408ac38abc..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,9 +414,10 @@ 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)
-__declspec(allocate(".CRT$XCAB")) int (*__intercept_seh)() =
-    __asan_set_seh_filter;
+#    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
 // early as possible. Initializers in .CRT$XL* are called directly by ntdll,
@@ -397,10 +428,12 @@ static void NTAPI asan_thread_init(void *module, DWORD reason, void *reserved) {
     __asan_init();
 }
 
-#pragma section(".CRT$XLAB", long, read)
-__declspec(allocate(".CRT$XLAB")) void(NTAPI *__asan_tls_init)(
-    void *, unsigned long, void *) = asan_thread_init;
-#endif
+#    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
 
 static void NTAPI asan_thread_exit(void *module, DWORD reason, void *reserved) {
   if (reason == DLL_THREAD_DETACH) {
@@ -411,9 +444,13 @@ static void NTAPI asan_thread_exit(void *module, DWORD reason, void *reserved) {
   }
 }
 
-#pragma section(".CRT$XLY", long, read)
-__declspec(allocate(".CRT$XLY")) void(NTAPI *__asan_tls_exit)(
-    void *, unsigned long, void *) = asan_thread_exit;
+#  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 056e49336d326..1c6bac48141b2 100644
--- a/compiler-rt/lib/asan/asan_win_common_runtime_thunk.cpp
+++ b/compiler-rt/lib/asan/asan_win_common_runtime_thunk.cpp
@@ -83,12 +83,11 @@ static void WINAPI asan_thread_init(void *mod, unsigned long reason,
 // Our cloned variables must be initialized before C/C++ constructors.  If TLS
 // is used, our .CRT$XLAB initializer will run first. If not, our .CRT$XIB
 // initializer is needed as a backup.
-extern "C" __declspec(allocate(".CRT$XIB")) int (*__asan_thunk_init)() =
-    asan_thunk_init;
+extern "C" IN_SECTION(".CRT$XIB") int (*__asan_thunk_init)() = asan_thunk_init;
 WIN_FORCE_LINK(__asan_thunk_init)
 
-extern "C" __declspec(allocate(".CRT$XLAB")) void(WINAPI *__asan_tls_init)(
-    void *, unsigned long, void *) = asan_thread_init;
+extern "C" IN_SECTION(".CRT$XLAB") void(WINAPI* __asan_tls_init)(
+    void*, unsigned long, void*) = asan_thread_init;
 WIN_FORCE_LINK(__asan_tls_init)
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -101,12 +100,14 @@ static int SetSEHFilter() { return __asan_set_seh_filter(); }
 
 // Unfortunately, putting a pointer to __asan_set_seh_filter into
 // __asan_intercept_seh gets optimized out, so we have to use an extra function.
-extern "C" __declspec(allocate(".CRT$XCAB")) int (*__asan_seh_interceptor)() =
+extern "C" IN_SECTION(".CRT$XCAB") int (*__asan_seh_interceptor)() =
     SetSEHFilter;
 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 159cc152474e7..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,17 +18,19 @@
     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();
diff --git a/compiler-rt/lib/asan/asan_win_dynamic_runtime_thunk.cpp b/compiler-rt/lib/asan/asan_win_dynamic_runtime_thunk.cpp
index 421fe651b7d91..2a864aa4ee8c1 100644
--- a/compiler-rt/lib/asan/asan_win_dynamic_runtime_thunk.cpp
+++ b/compiler-rt/lib/asan/asan_win_dynamic_runtime_thunk.cpp
@@ -33,8 +33,8 @@ extern "C" int __cdecl atexit(void(__cdecl *f)(void));
 extern "C" void __cdecl _initterm(void *a, void *b);
 
 namespace {
-__declspec(allocate(".CRT$XTW")) void *before_global_dtors = 0;
-__declspec(allocate(".CRT$XTY")) void *after_global_dtors = 0;
+IN_SECTION(".CRT$XTW") void* before_global_dtors = 0;
+IN_SECTION(".CRT$XTY") void* after_global_dtors = 0;
 
 void UnregisterGlobals() {
   _initterm(&before_global_dtors, &after_global_dtors);
@@ -47,8 +47,8 @@ int ScheduleUnregisterGlobals() { return atexit(UnregisterGlobals); }
 // atexit() is initialized (.CRT$XIC).  As this is executed before C++
 // initializers (think ctors for globals), UnregisterGlobals gets executed after
 // dtors for C++ globals.
-extern "C" __declspec(allocate(".CRT$XID")) int (
-    *__asan_schedule_unregister_globals)() = ScheduleUnregisterGlobals;
+extern "C" IN_SECTION(".CRT$XID") int (*__asan_schedule_unregister_globals)() =
+    ScheduleUnregisterGlobals;
 WIN_FORCE_LINK(__asan_schedule_unregister_globals)
 
 #endif  // SANITIZER_DYNAMIC_RUNTIME_THUNK
diff --git a/compiler-rt/lib/builtins/emutls.c b/compiler-rt/lib/builtins/emutls.c
index a8a5d7c0749a8..9d127e991a9b4 100644
--- a/compiler-rt/lib/builtins/emutls.c
+++ b/compiler-rt/lib/builtins/emutls.c
@@ -138,6 +138,7 @@ static __inline void emutls_unlock(void) { pthread_mutex_unlock(&emutls_mutex);
 #include <assert.h>
 #include <malloc.h>
 #include <stdio.h>
+#define WIN32_LEAN_AND_MEAN 1
 #include <windows.h>
 
 static LPCRITICAL_SECTION emutls_mutex;
diff --git a/compiler-rt/lib/builtins/gcc_personality_v0.c b/compiler-rt/lib/builtins/gcc_personality_v0.c
index 6d92a7b248f3c..02d4e65d78d1c 100644
--- a/compiler-rt/lib/builtins/gcc_personality_v0.c
+++ b/compiler-rt/lib/builtins/gcc_personality_v0.c
@@ -22,8 +22,8 @@
 #endif
 
 #if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__)
+#define WIN32_LEAN_AND_MEAN 1
 #include <windows.h>
-#include <winnt.h>
 
 EXCEPTION_DISPOSITION _GCC_specific_handler(PEXCEPTION_RECORD, void *, PCONTEXT,
                                             PDISPATCHER_CONTEXT,
diff --git a/compiler-rt/lib/interception/interception_win.cpp b/compiler-rt/lib/interception/interception_win.cpp
index 19bccb3566d72..6fd1f97308cdd 100644
--- a/compiler-rt/lib/interception/interception_win.cpp
+++ b/compiler-rt/lib/interception/interception_win.cpp
@@ -1110,6 +1110,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 3a1d1257a3481..4c4b5cf964256 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
@@ -759,9 +759,11 @@ static int RunAtexit() {
   return ret;
 }
 
-#pragma section(".CRT$XID", long, read)
-__declspec(allocate(".CRT$XID")) int (*__run_atexit)() = RunAtexit;
-#endif
+#    if !defined(__GNUC__) || defined(__clang__)
+#      pragma section(".CRT$XID", long, read)
+#    endif
+IN_SECTION(".CRT$XID") int (*__run_atexit)() = RunAtexit;
+#  endif
 
 // ------------------ sanitizer_libc.h
 fd_t OpenFile(const char *filename, FileAccessMode mode, error_t *last_error) {
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h
index bfe38a3323674..e25dedb3d2fbb 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h
@@ -43,7 +43,13 @@
 #define STRINGIFY_(A) #A
 #define STRINGIFY(A) STRINGIFY_(A)
 
-#if !SANITIZER_GO
+#  if !defined(__GNUC__) || defined(__clang__)
+#    define IN_SECTION(n) __declspec(allocate(n))
+#  else
+#    define IN_SECTION(n) __attribute__((section(n)))
+#  endif
+
+#  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
@@ -161,14 +167,24 @@
 //   }
 //
 
-#else // SANITIZER_GO
+#  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.
 // The shenanigans above don't work for gcc.
-# define WIN_WEAK_EXPORT_DEF(ReturnType, Name, ...)                            \
-  extern "C" ReturnType Name(__VA_ARGS__)
+#    define WIN_WEAK_EXPORT_DEF(ReturnType, Name, ...) \
+      extern "C" ReturnType Name(__VA_ARGS__)
 
-#endif // SANITIZER_GO
+#  endif  // SANITIZER_GO
 
 #endif // SANITIZER_WINDOWS
 #endif // SANITIZER_WIN_DEFS_H
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 16ba155630b0a..794f61fd34418 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.cpp
@@ -61,14 +61,11 @@ void initialize_thunks(const sanitizer_thunk *first,
 #  pragma section(".WEAK$Z", read)  // weak end
 
 extern "C" {
-__declspec(allocate(
-    ".INTR$A")) sanitizer_thunk __sanitizer_intercept_thunk_begin;
-__declspec(allocate(".INTR$Z")) sanitizer_thunk __sanitizer_intercept_thunk_end;
-
-__declspec(allocate(
-    ".WEAK$A")) sanitizer_thunk __sanitizer_register_weak_thunk_begin;
-__declspec(allocate(
-    ".WEAK$Z")) sanitizer_thunk __sanitizer_register_weak_thunk_end;
+IN_SECTION(".INTR$A") sanitizer_thunk __sanitizer_intercept_thunk_begin;
+IN_SECTION(".INTR$Z") sanitizer_thunk __sanitizer_intercept_thunk_end;
+
+IN_SECTION(".WEAK$A") sanitizer_thunk __sanitizer_register_weak_thunk_begin;
+IN_SECTION(".WEAK$Z") sanitizer_thunk __sanitizer_register_weak_thunk_end;
 }
 
 extern "C" int __sanitizer_thunk_init() {
@@ -92,8 +89,9 @@ extern "C" int __sanitizer_thunk_init() {
 // We want to call dll_thunk_init before C/C++ initializers / constructors are
 // executed, otherwise functions like memset might be invoked.
 #  pragma section(".CRT$XIB", long, read)
-__declspec(allocate(".CRT$XIB")) int (*__sanitizer_thunk_init_ptr)() =
+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,9 +100,9 @@ static void WINAPI sanitizer_thunk_thread_init(void *mod, unsigned long reason,
 }
 
 #  pragma section(".CRT$XLAB", long, read)
-__declspec(allocate(".CRT$XLAB")) void(
-    WINAPI *__sanitizer_thunk_thread_init_ptr)(void *, unsigned long, void *) =
+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)
\ No newline at end of file
+        // 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 278450d68ac51..492ef6b2b98b2 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win_thunk_interception.h
@@ -14,6 +14,7 @@
 #include <stdint.h>
 
 #include "sanitizer_internal_defs.h"
+#include "sanitizer_win_defs.h"
 
 extern "C" {
 __declspec(dllimport) bool __cdecl __sanitizer_override_function(
@@ -53,8 +54,8 @@ void initialize_thunks(const sanitizer_thunk *begin,
         sanitizer_export,                                              \
         reinterpret_cast<__sanitizer::uptr>(local_function));          \
   }                                                                    \
-  __pragma(section(".INTR$M", long, read)) __declspec(allocate(        \
-      ".INTR$M")) int (*__sanitizer_static_thunk_##local_function)() = \
+  __pragma(section(".INTR$M", long, read)) IN_SECTION(".INTR$M") int ( \
+      *__sanitizer_static_thunk_##local_function)() =                  \
       intercept_##local_function;
 
 // ------------------ Weak symbol registration macros ---------------------- //
@@ -68,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;
-#endif  // SANITIZER_WIN_STATIC_RUNTIME_THUNK_H
+#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 2614d49bf238b..38e156b85489c 100644
--- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp
+++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
@@ -927,7 +927,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,
@@ -936,6 +936,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

>From 9cfae7205066537b476dc3b9ab1dc2fdf4666e29 Mon Sep 17 00:00:00 2001
From: Oleg Tolmatcev <oleg.tolmatcev at gmail.com>
Date: Wed, 15 Jul 2026 18:09:46 +0200
Subject: [PATCH 2/3] [compiler-rt] Adjust ASan Windows tests for mingw-w64

Update the Windows ASan tests for the mingw-w64 configuration.
---
 .../test/asan/TestCases/Windows/fuse-lld.cpp     | 10 ++++++----
 .../test/asan/TestCases/Windows/unsymbolized.cpp | 16 +++++++++++-----
 2 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/compiler-rt/test/asan/TestCases/Windows/fuse-lld.cpp b/compiler-rt/test/asan/TestCases/Windows/fuse-lld.cpp
index 5fa5945df4dc0..b689b403fcb92 100644
--- a/compiler-rt/test/asan/TestCases/Windows/fuse-lld.cpp
+++ b/compiler-rt/test/asan/TestCases/Windows/fuse-lld.cpp
@@ -2,8 +2,8 @@
 //
 // REQUIRES: lld-available
 //
-// RUN: %clangxx_asan -O2 %s -o %t.exe -g -gcodeview -fuse-ld=lld -Wl,-debug
-// RUN: not %run %t.exe 2>&1 | FileCheck %s
+// RUN: %clangxx_asan %if target={{.*-windows-gnu}} %{ -gcodeview -gcolumn-info -Wl,--pdb= %} %else %{ -g -gcodeview -Wl,-debug %} -O2 %s -o %t.exe -fuse-ld=lld
+// RUN: not %run %t.exe 2>&1 | FileCheck %s --check-prefixes=CHECK,%if target={{.*-windows-msvc.*}} %{MSVC%} %else %{MINGW%}
 
 #include <stdlib.h>
 
@@ -13,7 +13,9 @@ int main() {
   return x[5];
   // CHECK: heap-use-after-free
   // CHECK: free
-  // CHECK: main{{.*}}fuse-lld.cpp:[[@LINE-4]]:3
+  // MSVC: main{{.*}}fuse-lld.cpp:[[@LINE-4]]:3
+  // MINGW: {{.*fuse-lld.cpp.tmp.exe\+0x14000[0-9a-f]+}}
   // CHECK: malloc
-  // CHECK: main{{.*}}fuse-lld.cpp:[[@LINE-7]]:20
+  // MSVC: main{{.*}}fuse-lld.cpp:[[@LINE-8]]:20
+  // MINGW: {{.*fuse-lld.cpp.tmp.exe\+0x14000[0-9a-f]+}}
 }
diff --git a/compiler-rt/test/asan/TestCases/Windows/unsymbolized.cpp b/compiler-rt/test/asan/TestCases/Windows/unsymbolized.cpp
index ade5e5b914be8..29c9c3f7f15b9 100644
--- a/compiler-rt/test/asan/TestCases/Windows/unsymbolized.cpp
+++ b/compiler-rt/test/asan/TestCases/Windows/unsymbolized.cpp
@@ -3,9 +3,13 @@
 // which is the default image base of an executable.
 
 // RUN: rm -f %t.pdb
-// RUN: %clangxx_asan -c -O2 %s -o %t.obj
-// RUN: lld-link /nologo /OUT:%t.exe %t.obj -defaultlib:libcmt -nodefaultlib:msvcrt -defaultlib:oldnames %asan_static_runtime_thunk %asan_lib
-// RUN: not %run %t.exe 2>&1 | FileCheck %s
+// RUN: %if target={{.*-windows-gnu}} %{ \
+// RUN:   %clangxx_asan -O2 %s -o %t.exe -gcodeview -gcolumn-info -fuse-ld=lld \
+// RUN: %} %else %{ \
+// RUN:   %clangxx_asan -c -O2 %s -o %t.obj && \
+// RUN:   lld-link /nologo /OUT:%t.exe %t.obj -defaultlib:libcmt -nodefaultlib:msvcrt -defaultlib:oldnames %asan_static_runtime_thunk %asan_lib \
+// RUN: %}
+// RUN: not %run %t.exe 2>&1 | FileCheck %s --check-prefixes=CHECK,%if target={{.*-windows-msvc.*}} %{MSVC%} %else %{MINGW%}
 // REQUIRES: lld-available
 
 #include "../defines.h"
@@ -22,6 +26,8 @@ int do_uaf(void) {
   free(x);
   return x[5];
   // CHECK: AddressSanitizer: heap-use-after-free
-  // CHECK: #0 {{0x[a-f0-9]+ \(.*[\\/]unsymbolized.cpp.*.exe\+(0x40|0x14000)[a-f0-9]{4}\)}}
-  // CHECK: #1 {{0x[a-f0-9]+ \(.*[\\/]unsymbolized.cpp.*.exe\+(0x40|0x14000)[a-f0-9]{4}\)}}
+  // MSVC: #0 {{0x[a-f0-9]+ \(.*[\\/]unsymbolized.cpp.*.exe\+(0x40|0x14000)[a-f0-9]{4}\)}}
+  // MSVC: #1 {{0x[a-f0-9]+ \(.*[\\/]unsymbolized.cpp.*.exe\+(0x40|0x14000)[a-f0-9]{4}\)}}
+  // MINGW: #0 {{0x[a-f0-9]+ in do_uaf\(\) \(.*[\\/]unsymbolized.cpp.*.exe\+0x14000[a-f0-9]{4}\)}}
+  // MINGW: #1 {{0x[a-f0-9]+ in main \(.*[\\/]unsymbolized.cpp.*.exe\+0x14000[a-f0-9]{4}\)}}
 }

>From 7db83fffba20fe6cac82301844d780011c191f26 Mon Sep 17 00:00:00 2001
From: Oleg Tolmatcev <oleg.tolmatcev at gmail.com>
Date: Thu, 16 Jul 2026 20:31:12 +0200
Subject: [PATCH 3/3] [compiler-rt] Add libbacktrace support on Windows

---
 .../sanitizer_symbolizer_libbacktrace.cpp        | 16 ++++++++--------
 .../sanitizer_symbolizer_win.cpp                 |  8 ++++++++
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libbacktrace.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libbacktrace.cpp
index d78dab93487f1..849e5ee475eb5 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libbacktrace.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libbacktrace.cpp
@@ -19,15 +19,15 @@
 
 #if SANITIZER_LIBBACKTRACE
 # include "backtrace-supported.h"
-# if SANITIZER_POSIX && BACKTRACE_SUPPORTED && !BACKTRACE_USES_MALLOC
-#  include "backtrace.h"
-#  if SANITIZER_CP_DEMANGLE
-#   undef ARRAY_SIZE
-#   include "demangle.h"
+#  if BACKTRACE_SUPPORTED && !BACKTRACE_USES_MALLOC
+#    include "backtrace.h"
+#    if SANITIZER_CP_DEMANGLE
+#      undef ARRAY_SIZE
+#      include "demangle.h"
+#    endif
+#  else
+#    define SANITIZER_LIBBACKTRACE 0
 #  endif
-# else
-#  define SANITIZER_LIBBACKTRACE 0
-# endif
 #endif
 
 namespace __sanitizer {
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_win.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_win.cpp
index 1ff8b8f1bab48..af6639d740f49 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_win.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_win.cpp
@@ -16,6 +16,7 @@
 
 #  include "sanitizer_dbghelp.h"
 #  include "sanitizer_symbolizer_internal.h"
+#  include "sanitizer_symbolizer_libbacktrace.h"
 
 namespace __sanitizer {
 
@@ -279,6 +280,12 @@ static void ChooseSymbolizerTools(IntrusiveList<SymbolizerTool> *list,
     return;
   }
 
+#  if defined(__GNUC__) && !defined(__clang__)
+  if (SymbolizerTool* tool = LibbacktraceSymbolizer::get(allocator)) {
+    VReport(2, "Using libbacktrace symbolizer.\n");
+    list->push_back(tool);
+  }
+#  else
   // Add llvm-symbolizer.
   const char *user_path = common_flags()->external_symbolizer_path;
 
@@ -301,6 +308,7 @@ static void ChooseSymbolizerTools(IntrusiveList<SymbolizerTool> *list,
   } else {
     VReport(2, "External symbolizer is not present.\n");
   }
+#  endif
 
   // Add the dbghelp based symbolizer.
   list->push_back(new(*allocator) WinSymbolizerTool());



More information about the llvm-commits mailing list