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

via llvm-commits llvm-commits at lists.llvm.org
Thu May 7 10:10:39 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Kunqiu Chen (Camsyn)

<details>
<summary>Changes</summary>

## 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 downstream passes that treat `main` specially |
| 3 | Link-time interception: `--wrap=main` | User does not use `--wrap=main`; GNU/Linux linker | 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 primitive that can fully wrap `main` while

1. without relying on sanitizer IR instrumentation, and
2. preserving the behavior of the user-specific operations on `main`.

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.

- As the prerequisite to land the discussion in https://github.com/llvm/llvm-project/pull/195238#issuecomment-4373072749


---
Full diff: https://github.com/llvm/llvm-project/pull/196365.diff


3 Files Affected:

- (modified) compiler-rt/lib/sanitizer_common/CMakeLists.txt (+1) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_common.h (+3) 
- (added) compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_main.inc (+47) 


``````````diff
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

``````````

</details>


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


More information about the llvm-commits mailing list