Skip to content

Commit 29e849b

Browse files
authored
[asan] Optimize initialization order checking (#101837)
The pair `__asan_before_dynamic_init` and `__asan_after_dynamic_init` is executed for each TU. `__asan_after_dynamic_init` unpoisons all globals, which makes the time complexity O(N^2), where N is the maximum of the global count and the TU count. This is expensive for large binaries. This patch decreases the time complexity to O(N), when lld and static runtime is used on SANITIZER_CAN_USE_PREINIT_ARRAY platforms. This requires: Enabling incremental poisoning (`__asan_before_dynamic_init` since #101597). Making most `__asan_after_dynamic_init` calls do nothing.
1 parent d3c9bb0 commit 29e849b

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

compiler-rt/lib/asan/asan_globals.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,43 @@ void __asan_before_dynamic_init(const char *module_name) {
520520
current_dynamic_init_module_name = module_name;
521521
}
522522

523+
// Maybe SANITIZER_CAN_USE_PREINIT_ARRAY is to conservative for `.init_array`,
524+
// however we should not make mistake here. If `UnpoisonBeforeMain` was not
525+
// executed at all we will have false reports on globals.
526+
#if SANITIZER_CAN_USE_PREINIT_ARRAY
527+
// This optimization aims to reduce the overhead of `__asan_after_dynamic_init`
528+
// calls by leveraging incremental unpoisoning/poisoning in
529+
// `__asan_before_dynamic_init`. We expect most `__asan_after_dynamic_init
530+
// calls` to be no-ops. However, to ensure all globals are unpoisoned before the
531+
// `main`, we force `UnpoisonBeforeMain` to fully execute
532+
// `__asan_after_dynamic_init`.
533+
534+
// With lld, `UnpoisonBeforeMain` runs after standard `.init_array`, making it
535+
// the final `__asan_after_dynamic_init` call for the static runtime. In
536+
// contrast, GNU ld executes it earlier, causing subsequent
537+
// `__asan_after_dynamic_init` calls to perform full unpoisoning, losing the
538+
// optimization.
539+
bool allow_after_dynamic_init SANITIZER_GUARDED_BY(mu_for_globals) = false;
540+
541+
static void UnpoisonBeforeMain(void) {
542+
{
543+
Lock lock(&mu_for_globals);
544+
if (allow_after_dynamic_init)
545+
return;
546+
allow_after_dynamic_init = true;
547+
}
548+
if (flags()->report_globals >= 3)
549+
Printf("UnpoisonBeforeMain\n");
550+
__asan_after_dynamic_init();
551+
}
552+
553+
__attribute__((section(".init_array.65537"), used)) static void (
554+
*asan_after_init_array)(void) = UnpoisonBeforeMain;
555+
#else
556+
// Incremental poisoning is disabled, unpoison globals immediately.
557+
static constexpr bool allow_after_dynamic_init = true;
558+
#endif // SANITIZER_CAN_USE_PREINIT_ARRAY
559+
523560
// This method runs immediately after dynamic initialization in each TU, when
524561
// all dynamically initialized globals except for those defined in the current
525562
// TU are poisoned. It simply unpoisons all dynamically initialized globals.
@@ -528,6 +565,8 @@ void __asan_after_dynamic_init() {
528565
return;
529566
CHECK(AsanInited());
530567
Lock lock(&mu_for_globals);
568+
if (!allow_after_dynamic_init)
569+
return;
531570
if (!current_dynamic_init_module_name)
532571
return;
533572

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %clangxx_asan -O3 %S/../initialization-nobug.cpp %S/../Helpers/initialization-nobug-extra.cpp -fuse-ld=lld -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInit"
2+
3+
// Same as initialization-nobug.cpp, but with lld we expect just one
4+
// `DynInitUnpoison` executed after `AfterDynamicInit` at the end.
5+
// REQUIRES: lld-available
6+
7+
// With dynamic runtimes `AfterDynamicInit` will called before `executable`
8+
// contructors, with constructors of dynamic runtime.
9+
// XFAIL: asan-dynamic-runtime
10+
11+
// CHECK: DynInitPoison
12+
// CHECK: DynInitPoison
13+
// CHECK: UnpoisonBeforeMain
14+
// CHECK: DynInitUnpoison

compiler-rt/test/asan/TestCases/initialization-nobug.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// A collection of various initializers which shouldn't trip up initialization
22
// order checking. If successful, this will just return 0.
33

4-
// RUN: %clangxx_asan -O0 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInit"
5-
// RUN: %clangxx_asan -O1 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInit"
6-
// RUN: %clangxx_asan -O2 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInit"
7-
// RUN: %clangxx_asan -O3 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInit"
4+
// RUN: %clangxx_asan -O0 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInitPoison"
5+
// RUN: %clangxx_asan -O1 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInitPoison"
6+
// RUN: %clangxx_asan -O2 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInitPoison"
7+
// RUN: %clangxx_asan -O3 %s %p/Helpers/initialization-nobug-extra.cpp -o %t && %env_asan_opts=check_initialization_order=true:report_globals=3 %run %t 2>&1 | FileCheck %s --implicit-check-not "DynInitPoison"
88

99
// Simple access:
1010
// Make sure that accessing a global in the same TU is safe
@@ -44,6 +44,10 @@ int getStructWithDtorValue() { return struct_with_dtor.value; }
4444
int main() { return 0; }
4545

4646
// CHECK: DynInitPoison
47-
// CHECK: DynInitUnpoison
4847
// CHECK: DynInitPoison
48+
49+
// In general case entire set of DynInitPoison must be followed by at lest one
50+
// DynInitUnpoison. In some cases we can limit the number of DynInitUnpoison,
51+
// see initialization-nobug-lld.cpp.
52+
4953
// CHECK: DynInitUnpoison

0 commit comments

Comments
 (0)