[compiler-rt] [sanitizer_common] Add GNU/Linux main wrapper support (PR #196365)

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Thu May 7 09:50:14 PDT 2026


https://github.com/Camsyn created https://github.com/llvm/llvm-project/pull/196365

## Summary

This patch adds a small `sanitizer_common` primitive for opt-in `main` wrapping on GNU/Linux glibc. Sanitizer runtimes can define:

```c++
#define MAIN_WRAPPER(main, argc, argv, envp) ...
#include "sanitizer_common/sanitizer_common_interceptors_main.inc"
```

and call `INIT_MAIN_WRAPPER` from their interceptor initialization path.

## Design space

| # | Approach | Prerequisite | Drawback |
|---|---|---|---|
| 1 | Instrumentation: add hooks at entry and exit points of `main` | The sanitizer already has an instrumentation pass, e.g. ASan | Depends on instrumentation; cannot express full wrapping |
| 2 | Instrumentation: synthesize a new `main` and wrap the old `main` | The sanitizer already has an instrumentation pass, e.g. ASan | Depends on instrumentation; may interfere with user or downstream passes that treat `main` specially |
| 3 | Link-time interception: `--wrap=main` | User does not use `--wrap=main`; GNU/Linux linker semantics | Platform-specific; conflicts with user-provided `--wrap=main` |
| 4 | Runtime interception: intercept `__libc_start_main` and wrap `main` | GNU/Linux glibc | Platform-specific |

This patch chooses approach 4. It gives sanitizer runtimes a driver/link-time primitive that can fully wrap `main` without relying on sanitizer IR instrumentation and without rewriting the user's `main` symbol. That preserves the behavior of user or downstream passes that may reason about `main` before sanitizer instrumentation runs.

The implementation is intentionally limited to GNU/Linux glibc, where `__libc_start_main` is the process startup entry used to call `main`. Other libc/startup ABIs need separate providers.

## Notes

This PR only adds the `sanitizer_common` mechanism. Individual sanitizer runtimes can opt in by defining `MAIN_WRAPPER` in follow-up patches.


>From db21c0f320c5ef063c7e7596619adbb9a3fdce58 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Fri, 8 May 2026 00:47:37 +0800
Subject: [PATCH] Add sanitizer_common main wrapper support

---
 .../lib/sanitizer_common/CMakeLists.txt       |  1 +
 .../lib/sanitizer_common/sanitizer_common.h   |  3 ++
 .../sanitizer_common_interceptors_main.inc    | 47 +++++++++++++++++++
 3 files changed, 51 insertions(+)
 create mode 100644 compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_main.inc

diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt
index 96c23c6d8ab82..c7a6da2e0aab2 100644
--- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt
+++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt
@@ -135,6 +135,7 @@ set(SANITIZER_IMPL_HEADERS
   sanitizer_common_interceptors.inc
   sanitizer_common_interceptors_format.inc
   sanitizer_common_interceptors_ioctl.inc
+  sanitizer_common_interceptors_main.inc
   sanitizer_common_interceptors_memintrinsics.inc
   sanitizer_common_interface.inc
   sanitizer_common_interface_posix.inc
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h
index 4dd2187df2272..4ec27339be041 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h
@@ -34,6 +34,9 @@ struct SignalContext;
 struct StackTrace;
 struct SymbolizedStack;
 
+// The full type of the function `main`.
+using MainFnTy = int (*)(int argc, char** argv, char** envp);
+
 // Constants.
 const uptr kWordSize = SANITIZER_WORDSIZE / 8;
 const uptr kWordSizeInBits = 8 * kWordSize;
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_main.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_main.inc
new file mode 100644
index 0000000000000..436241f599cde
--- /dev/null
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_main.inc
@@ -0,0 +1,47 @@
+//===-- sanitizer_common_interceptors_main.inc -----------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Common main wrapper interceptor for tools that define MAIN_WRAPPER.
+//
+//===----------------------------------------------------------------------===//
+
+#include "interception/interception.h"
+#include "sanitizer_common.h"
+#include "sanitizer_platform.h"
+
+// __libc_start_main is a dynamic symbol in glibc.
+#if SANITIZER_LINUX && SANITIZER_GLIBC && defined(MAIN_WRAPPER)
+namespace __sanitizer {
+namespace {
+
+MainFnTy real_main;
+
+int WrappedMain(int argc, char **argv, char **envp) {
+  return MAIN_WRAPPER(real_main, argc, argv, envp);
+}
+
+}  // namespace
+}  // namespace __sanitizer
+
+INTERCEPTOR(int, __libc_start_main, __sanitizer::MainFnTy main, int argc,
+            char **argv, void (*init)(), void (*fini)(), void (*rtld_fini)(),
+            void *stack_end) {
+  // This provider is for dynamic glibc startup only. Other libc/startup
+  // schemes need separate platform-specific providers.
+  __interception::DoesNotSupportStaticLinking();
+  if (!REAL(__libc_start_main))
+    CHECK(INTERCEPT_FUNCTION(__libc_start_main));
+  __sanitizer::real_main = main;
+  return REAL(__libc_start_main)(__sanitizer::WrappedMain, argc, argv, init,
+                                 fini, rtld_fini, stack_end);
+}
+
+#  define INIT_MAIN_WRAPPER COMMON_INTERCEPT_FUNCTION(__libc_start_main)
+#else
+#  define INIT_MAIN_WRAPPER
+#endif



More information about the llvm-commits mailing list