1//===-- asan_globals.cpp --------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file is a part of AddressSanitizer, an address sanity checker.
10//
11// Handle globals.
12//===----------------------------------------------------------------------===//
13
14#include "asan_interceptors.h"
15#include "asan_internal.h"
16#include "asan_mapping.h"
17#include "asan_poisoning.h"
18#include "asan_report.h"
19#include "asan_stack.h"
20#include "asan_stats.h"
21#include "asan_suppressions.h"
22#include "asan_thread.h"
23#include "sanitizer_common/sanitizer_common.h"
24#include "sanitizer_common/sanitizer_dense_map.h"
25#include "sanitizer_common/sanitizer_list.h"
26#include "sanitizer_common/sanitizer_mutex.h"
27#include "sanitizer_common/sanitizer_placement_new.h"
28#include "sanitizer_common/sanitizer_stackdepot.h"
29#include "sanitizer_common/sanitizer_symbolizer.h"
30#include "sanitizer_common/sanitizer_thread_safety.h"
31
32namespace __asan {
33
34typedef __asan_global Global;
35
36struct GlobalListNode {
37 const Global *g = nullptr;
38 GlobalListNode *next = nullptr;
39};
40typedef IntrusiveList<GlobalListNode> ListOfGlobals;
41
42static Mutex mu_for_globals;
43static ListOfGlobals list_of_all_globals SANITIZER_GUARDED_BY(mu_for_globals);
44
45struct DynInitGlobal {
46 Global g = {};
47 bool initialized = false;
48 DynInitGlobal *next = nullptr;
49};
50
51// We want to remember where a certain range of globals was registered.
52struct GlobalRegistrationSite {
53 u32 stack_id;
54 Global *g_first, *g_last;
55};
56typedef InternalMmapVector<GlobalRegistrationSite> GlobalRegistrationSiteVector;
57static GlobalRegistrationSiteVector *global_registration_site_vector;
58
59static ListOfGlobals &GlobalsByIndicator(uptr odr_indicator)
60 SANITIZER_REQUIRES(mu_for_globals) {
61 using MapOfGlobals = DenseMap<uptr, ListOfGlobals>;
62
63 static MapOfGlobals *globals_by_indicator = nullptr;
64 if (!globals_by_indicator) {
65 alignas(
66 alignof(MapOfGlobals)) static char placeholder[sizeof(MapOfGlobals)];
67 globals_by_indicator = new (placeholder) MapOfGlobals();
68 }
69
70 return (*globals_by_indicator)[odr_indicator];
71}
72
73static const char *current_dynamic_init_module_name
74 SANITIZER_GUARDED_BY(mu_for_globals) = nullptr;
75
76using DynInitGlobalsByModule =
77 DenseMap<const char *, IntrusiveList<DynInitGlobal>>;
78
79// TODO: Add a NoDestroy helper, this patter is very common in sanitizers.
80static DynInitGlobalsByModule &DynInitGlobals()
81 SANITIZER_REQUIRES(mu_for_globals) {
82 static DynInitGlobalsByModule *globals_by_module = nullptr;
83 if (!globals_by_module) {
84 alignas(alignof(DynInitGlobalsByModule)) static char
85 placeholder[sizeof(DynInitGlobalsByModule)];
86 globals_by_module = new (placeholder) DynInitGlobalsByModule();
87 }
88
89 return *globals_by_module;
90}
91
92ALWAYS_INLINE void PoisonShadowForGlobal(const Global *g, u8 value) {
93 FastPoisonShadow(aligned_beg: g->beg, aligned_size: g->size_with_redzone, value);
94}
95
96ALWAYS_INLINE void PoisonRedZones(const Global &g) {
97 uptr aligned_size = RoundUpTo(size: g.size, ASAN_SHADOW_GRANULARITY);
98 FastPoisonShadow(aligned_beg: g.beg + aligned_size, aligned_size: g.size_with_redzone - aligned_size,
99 value: kAsanGlobalRedzoneMagic);
100 if (g.size != aligned_size) {
101 FastPoisonShadowPartialRightRedzone(
102 aligned_addr: g.beg + RoundDownTo(x: g.size, ASAN_SHADOW_GRANULARITY),
103 size: g.size % ASAN_SHADOW_GRANULARITY, ASAN_SHADOW_GRANULARITY,
104 value: kAsanGlobalRedzoneMagic);
105 }
106}
107
108const uptr kMinimalDistanceFromAnotherGlobal = 64;
109
110static void AddGlobalToList(ListOfGlobals &list, const Global *g) {
111 list.push_front(x: new (GetGlobalLowLevelAllocator()) GlobalListNode{.g: g});
112}
113
114static void UnpoisonDynamicGlobals(IntrusiveList<DynInitGlobal> &dyn_globals,
115 bool mark_initialized) {
116 for (auto &dyn_g : dyn_globals) {
117 const Global *g = &dyn_g.g;
118 if (dyn_g.initialized)
119 continue;
120 // Unpoison the whole global.
121 PoisonShadowForGlobal(g, value: 0);
122 // Poison redzones back.
123 PoisonRedZones(g: *g);
124 if (mark_initialized)
125 dyn_g.initialized = true;
126 }
127}
128
129static void PoisonDynamicGlobals(
130 const IntrusiveList<DynInitGlobal> &dyn_globals) {
131 for (auto &dyn_g : dyn_globals) {
132 const Global *g = &dyn_g.g;
133 if (dyn_g.initialized)
134 continue;
135 PoisonShadowForGlobal(g, value: kAsanInitializationOrderMagic);
136 }
137}
138
139static bool IsAddressNearGlobal(uptr addr, const __asan_global &g) {
140 if (addr <= g.beg - kMinimalDistanceFromAnotherGlobal) return false;
141 if (addr >= g.beg + g.size_with_redzone) return false;
142 return true;
143}
144
145static void ReportGlobal(const Global &g, const char *prefix) {
146 DataInfo info;
147 bool symbolized = Symbolizer::GetOrInit()->SymbolizeData(address: g.beg, info: &info);
148 Report(
149 format: "%s Global[%p]: beg=%p size=%zu/%zu name=%s source=%s module=%s "
150 "dyn_init=%zu "
151 "odr_indicator=%p\n",
152 prefix, (void *)&g, (void *)g.beg, g.size, g.size_with_redzone, g.name,
153 g.module_name, (symbolized ? info.module : "?"), g.has_dynamic_init,
154 (void *)g.odr_indicator);
155
156 if (symbolized && info.line != 0) {
157 Report(format: " location: name=%s, %d\n", info.file, static_cast<int>(info.line));
158 } else if (g.gcc_location != 0) {
159 // Fallback to Global::gcc_location
160 Report(format: " location: name=%s, %d\n", g.gcc_location->filename, g.gcc_location->line_no);
161 }
162}
163
164static u32 FindRegistrationSite(const Global *g) {
165 mu_for_globals.CheckLocked();
166 CHECK(global_registration_site_vector);
167 for (uptr i = 0, n = global_registration_site_vector->size(); i < n; i++) {
168 GlobalRegistrationSite &grs = (*global_registration_site_vector)[i];
169 if (g >= grs.g_first && g <= grs.g_last)
170 return grs.stack_id;
171 }
172 return 0;
173}
174
175int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites,
176 int max_globals) {
177 if (!flags()->report_globals) return 0;
178 Lock lock(&mu_for_globals);
179 int res = 0;
180 for (const auto &l : list_of_all_globals) {
181 const Global &g = *l.g;
182 if (flags()->report_globals >= 2)
183 ReportGlobal(g, prefix: "Search");
184 if (IsAddressNearGlobal(addr, g)) {
185 internal_memcpy(dest: &globals[res], src: &g, n: sizeof(g));
186 if (reg_sites)
187 reg_sites[res] = FindRegistrationSite(g: &g);
188 res++;
189 if (res == max_globals)
190 break;
191 }
192 }
193 return res;
194}
195
196enum GlobalSymbolState {
197 UNREGISTERED = 0,
198 REGISTERED = 1
199};
200
201// Check ODR violation for given global G via special ODR indicator. We use
202// this method in case compiler instruments global variables through their
203// local aliases.
204static void CheckODRViolationViaIndicator(const Global *g)
205 SANITIZER_REQUIRES(mu_for_globals) {
206 // Instrumentation requests to skip ODR check.
207 if (g->odr_indicator == UINTPTR_MAX)
208 return;
209
210 ListOfGlobals &relevant_globals = GlobalsByIndicator(odr_indicator: g->odr_indicator);
211
212 u8 *odr_indicator = reinterpret_cast<u8 *>(g->odr_indicator);
213 if (*odr_indicator == REGISTERED) {
214 // If *odr_indicator is REGISTERED, some module have already registered
215 // externally visible symbol with the same name. This is an ODR violation.
216 for (const auto &l : relevant_globals) {
217 if ((flags()->detect_odr_violation >= 2 || g->size != l.g->size) &&
218 !IsODRViolationSuppressed(global_var_name: g->name))
219 ReportODRViolation(g1: g, stack_id1: FindRegistrationSite(g), g2: l.g,
220 stack_id2: FindRegistrationSite(g: l.g));
221 }
222 } else { // UNREGISTERED
223 *odr_indicator = REGISTERED;
224 }
225
226 AddGlobalToList(list&: relevant_globals, g);
227}
228
229// Check ODR violation for given global G by checking if it's already poisoned.
230// We use this method in case compiler doesn't use private aliases for global
231// variables.
232static void CheckODRViolationViaPoisoning(const Global *g)
233 SANITIZER_REQUIRES(mu_for_globals) {
234 if (__asan_region_is_poisoned(beg: g->beg, size: g->size_with_redzone)) {
235 // This check may not be enough: if the first global is much larger
236 // the entire redzone of the second global may be within the first global.
237 for (const auto &l : list_of_all_globals) {
238 if (g->beg == l.g->beg &&
239 (flags()->detect_odr_violation >= 2 || g->size != l.g->size) &&
240 !IsODRViolationSuppressed(global_var_name: g->name)) {
241 ReportODRViolation(g1: g, stack_id1: FindRegistrationSite(g), g2: l.g,
242 stack_id2: FindRegistrationSite(g: l.g));
243 }
244 }
245 }
246}
247
248// Clang provides two different ways for global variables protection:
249// it can poison the global itself or its private alias. In former
250// case we may poison same symbol multiple times, that can help us to
251// cheaply detect ODR violation: if we try to poison an already poisoned
252// global, we have ODR violation error.
253// In latter case, we poison each symbol exactly once, so we use special
254// indicator symbol to perform similar check.
255// In either case, compiler provides a special odr_indicator field to Global
256// structure, that can contain two kinds of values:
257// 1) Non-zero value. In this case, odr_indicator is an address of
258// corresponding indicator variable for given global.
259// 2) Zero. This means that we don't use private aliases for global variables
260// and can freely check ODR violation with the first method.
261//
262// This routine chooses between two different methods of ODR violation
263// detection.
264static inline bool UseODRIndicator(const Global *g) {
265 return g->odr_indicator > 0;
266}
267
268// Register a global variable.
269// This function may be called more than once for every global
270// so we store the globals in a map.
271static void RegisterGlobal(const Global *g) SANITIZER_REQUIRES(mu_for_globals) {
272 CHECK(AsanInited());
273 if (flags()->report_globals >= 2)
274 ReportGlobal(g: *g, prefix: "Added");
275 CHECK(flags()->report_globals);
276 CHECK(AddrIsInMem(g->beg));
277 if (!AddrIsAlignedByGranularity(a: g->beg)) {
278 Report(format: "The following global variable is not properly aligned.\n");
279 Report(format: "This may happen if another global with the same name\n");
280 Report(format: "resides in another non-instrumented module.\n");
281 Report(format: "Or the global comes from a C file built w/o -fno-common.\n");
282 Report(format: "In either case this is likely an ODR violation bug,\n");
283 Report(format: "but AddressSanitizer can not provide more details.\n");
284 ReportODRViolation(g1: g, stack_id1: FindRegistrationSite(g), g2: g, stack_id2: FindRegistrationSite(g));
285 CHECK(AddrIsAlignedByGranularity(g->beg));
286 }
287 CHECK(AddrIsAlignedByGranularity(g->size_with_redzone));
288 if (flags()->detect_odr_violation) {
289 // Try detecting ODR (One Definition Rule) violation, i.e. the situation
290 // where two globals with the same name are defined in different modules.
291 if (UseODRIndicator(g))
292 CheckODRViolationViaIndicator(g);
293 else
294 CheckODRViolationViaPoisoning(g);
295 }
296 if (CanPoisonMemory())
297 PoisonRedZones(g: *g);
298
299 AddGlobalToList(list&: list_of_all_globals, g);
300
301 if (g->has_dynamic_init) {
302 DynInitGlobals()[g->module_name].push_back(
303 x: new (GetGlobalLowLevelAllocator()) DynInitGlobal{.g: *g, .initialized: false});
304 }
305}
306
307static void UnregisterGlobal(const Global *g)
308 SANITIZER_REQUIRES(mu_for_globals) {
309 CHECK(AsanInited());
310 if (flags()->report_globals >= 2)
311 ReportGlobal(g: *g, prefix: "Removed");
312 CHECK(flags()->report_globals);
313 CHECK(AddrIsInMem(g->beg));
314 CHECK(AddrIsAlignedByGranularity(g->beg));
315 CHECK(AddrIsAlignedByGranularity(g->size_with_redzone));
316 if (CanPoisonMemory())
317 PoisonShadowForGlobal(g, value: 0);
318 // We unpoison the shadow memory for the global but we do not remove it from
319 // the list because that would require O(n^2) time with the current list
320 // implementation. It might not be worth doing anyway.
321
322 // Release ODR indicator.
323 if (UseODRIndicator(g) && g->odr_indicator != UINTPTR_MAX) {
324 u8 *odr_indicator = reinterpret_cast<u8 *>(g->odr_indicator);
325 *odr_indicator = UNREGISTERED;
326 }
327}
328
329void StopInitOrderChecking() {
330 if (!flags()->check_initialization_order)
331 return;
332 Lock lock(&mu_for_globals);
333 flags()->check_initialization_order = false;
334 DynInitGlobals().forEach(fn: [&](auto &kv) {
335 UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false);
336 return true;
337 });
338}
339
340static bool IsASCII(unsigned char c) { return /*0x00 <= c &&*/ c <= 0x7F; }
341
342const char *MaybeDemangleGlobalName(const char *name) {
343 // We can spoil names of globals with C linkage, so use an heuristic
344 // approach to check if the name should be demangled.
345 bool should_demangle = false;
346 if (name[0] == '_' && name[1] == 'Z')
347 should_demangle = true;
348 else if (SANITIZER_WINDOWS && name[0] == '\01' && name[1] == '?')
349 should_demangle = true;
350
351 return should_demangle ? Symbolizer::GetOrInit()->Demangle(name) : name;
352}
353
354// Check if the global is a zero-terminated ASCII string. If so, print it.
355void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g) {
356 for (uptr p = g.beg; p < g.beg + g.size - 1; p++) {
357 unsigned char c = *(unsigned char *)p;
358 if (c == '\0' || !IsASCII(c)) return;
359 }
360 if (*(char *)(g.beg + g.size - 1) != '\0') return;
361 str->AppendF(format: " '%s' is ascii string '%s'\n", MaybeDemangleGlobalName(name: g.name),
362 (char *)g.beg);
363}
364
365void PrintGlobalLocation(InternalScopedString *str, const __asan_global &g,
366 bool print_module_name) {
367 DataInfo info;
368 if (Symbolizer::GetOrInit()->SymbolizeData(address: g.beg, info: &info) && info.line != 0) {
369 str->AppendF(format: "%s:%d", info.file, static_cast<int>(info.line));
370 } else if (g.gcc_location != 0) {
371 // Fallback to Global::gcc_location
372 str->AppendF(format: "%s", g.gcc_location->filename ? g.gcc_location->filename
373 : g.module_name);
374 if (g.gcc_location->line_no)
375 str->AppendF(format: ":%d", g.gcc_location->line_no);
376 if (g.gcc_location->column_no)
377 str->AppendF(format: ":%d", g.gcc_location->column_no);
378 } else {
379 str->AppendF(format: "%s", g.module_name);
380 }
381 if (print_module_name && info.module)
382 str->AppendF(format: " in %s", info.module);
383}
384
385} // namespace __asan
386
387// ---------------------- Interface ---------------- {{{1
388using namespace __asan;
389
390// Apply __asan_register_globals to all globals found in the same loaded
391// executable or shared library as `flag'. The flag tracks whether globals have
392// already been registered or not for this image.
393void __asan_register_image_globals(uptr *flag) {
394 if (*flag)
395 return;
396 AsanApplyToGlobals(op: __asan_register_globals, needle: flag);
397 *flag = 1;
398}
399
400// This mirrors __asan_register_image_globals.
401void __asan_unregister_image_globals(uptr *flag) {
402 if (!*flag)
403 return;
404 AsanApplyToGlobals(op: __asan_unregister_globals, needle: flag);
405 *flag = 0;
406}
407
408void __asan_register_elf_globals(uptr *flag, void *start, void *stop) {
409 if (*flag || start == stop)
410 return;
411 CHECK_EQ(0, ((uptr)stop - (uptr)start) % sizeof(__asan_global));
412 __asan_global *globals_start = (__asan_global*)start;
413 __asan_global *globals_stop = (__asan_global*)stop;
414 __asan_register_globals(globals: globals_start, n: globals_stop - globals_start);
415 *flag = 1;
416}
417
418void __asan_unregister_elf_globals(uptr *flag, void *start, void *stop) {
419 if (!*flag || start == stop)
420 return;
421 CHECK_EQ(0, ((uptr)stop - (uptr)start) % sizeof(__asan_global));
422 __asan_global *globals_start = (__asan_global*)start;
423 __asan_global *globals_stop = (__asan_global*)stop;
424 __asan_unregister_globals(globals: globals_start, n: globals_stop - globals_start);
425 *flag = 0;
426}
427
428// Register an array of globals.
429void __asan_register_globals(__asan_global *globals, uptr n) {
430 if (!flags()->report_globals) return;
431 GET_STACK_TRACE_MALLOC;
432 u32 stack_id = StackDepotPut(stack);
433 Lock lock(&mu_for_globals);
434 if (!global_registration_site_vector) {
435 global_registration_site_vector =
436 new (GetGlobalLowLevelAllocator()) GlobalRegistrationSiteVector;
437 global_registration_site_vector->reserve(new_size: 128);
438 }
439 GlobalRegistrationSite site = {.stack_id: stack_id, .g_first: &globals[0], .g_last: &globals[n - 1]};
440 global_registration_site_vector->push_back(element: site);
441 if (flags()->report_globals >= 2) {
442 PRINT_CURRENT_STACK();
443 Printf(format: "=== ID %d; %p %p\n", stack_id, (void *)&globals[0],
444 (void *)&globals[n - 1]);
445 }
446 for (uptr i = 0; i < n; i++) {
447 if (SANITIZER_WINDOWS && globals[i].beg == 0) {
448 // The MSVC incremental linker may pad globals out to 256 bytes. As long
449 // as __asan_global is less than 256 bytes large and its size is a power
450 // of two, we can skip over the padding.
451 static_assert(
452 sizeof(__asan_global) < 256 &&
453 (sizeof(__asan_global) & (sizeof(__asan_global) - 1)) == 0,
454 "sizeof(__asan_global) incompatible with incremental linker padding");
455 // If these are padding bytes, the rest of the global should be zero.
456 CHECK(globals[i].size == 0 && globals[i].size_with_redzone == 0 &&
457 globals[i].name == nullptr && globals[i].module_name == nullptr &&
458 globals[i].odr_indicator == 0);
459 continue;
460 }
461 RegisterGlobal(g: &globals[i]);
462 }
463
464 // Poison the metadata. It should not be accessible to user code.
465 PoisonShadow(addr: reinterpret_cast<uptr>(globals), size: n * sizeof(__asan_global),
466 value: kAsanGlobalRedzoneMagic);
467}
468
469// Unregister an array of globals.
470// We must do this when a shared objects gets dlclosed.
471void __asan_unregister_globals(__asan_global *globals, uptr n) {
472 if (!flags()->report_globals) return;
473 Lock lock(&mu_for_globals);
474 for (uptr i = 0; i < n; i++) {
475 if (SANITIZER_WINDOWS && globals[i].beg == 0) {
476 // Skip globals that look like padding from the MSVC incremental linker.
477 // See comment in __asan_register_globals.
478 continue;
479 }
480 UnregisterGlobal(g: &globals[i]);
481 }
482
483 // Unpoison the metadata.
484 PoisonShadow(addr: reinterpret_cast<uptr>(globals), size: n * sizeof(__asan_global), value: 0);
485}
486
487// This method runs immediately prior to dynamic initialization in each TU,
488// when all dynamically initialized globals are unpoisoned. This method
489// poisons all global variables not defined in this TU, so that a dynamic
490// initializer can only touch global variables in the same TU.
491void __asan_before_dynamic_init(const char *module_name) {
492 if (!flags()->check_initialization_order || !CanPoisonMemory())
493 return;
494 bool strict_init_order = flags()->strict_init_order;
495 CHECK(module_name);
496 CHECK(AsanInited());
497 Lock lock(&mu_for_globals);
498 if (current_dynamic_init_module_name == module_name)
499 return;
500 if (flags()->report_globals >= 3)
501 Printf(format: "DynInitPoison module: %s\n", module_name);
502
503 if (current_dynamic_init_module_name == nullptr) {
504 // First call, poison all globals from other modules.
505 DynInitGlobals().forEach(fn: [&](auto &kv) {
506 if (kv.first != module_name) {
507 PoisonDynamicGlobals(kv.second);
508 } else {
509 UnpoisonDynamicGlobals(kv.second,
510 /*mark_initialized=*/!strict_init_order);
511 }
512 return true;
513 });
514 } else {
515 // Module changed.
516 PoisonDynamicGlobals(dyn_globals: DynInitGlobals()[current_dynamic_init_module_name]);
517 UnpoisonDynamicGlobals(dyn_globals&: DynInitGlobals()[module_name],
518 /*mark_initialized=*/!strict_init_order);
519 }
520 current_dynamic_init_module_name = module_name;
521}
522
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.
539bool allow_after_dynamic_init SANITIZER_GUARDED_BY(mu_for_globals) = false;
540
541static 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(format: "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.
557static constexpr bool allow_after_dynamic_init = true;
558#endif // SANITIZER_CAN_USE_PREINIT_ARRAY
559
560// This method runs immediately after dynamic initialization in each TU, when
561// all dynamically initialized globals except for those defined in the current
562// TU are poisoned. It simply unpoisons all dynamically initialized globals.
563void __asan_after_dynamic_init() {
564 if (!flags()->check_initialization_order || !CanPoisonMemory())
565 return;
566 CHECK(AsanInited());
567 Lock lock(&mu_for_globals);
568 if (!allow_after_dynamic_init)
569 return;
570 if (!current_dynamic_init_module_name)
571 return;
572
573 if (flags()->report_globals >= 3)
574 Printf(format: "DynInitUnpoison\n");
575
576 DynInitGlobals().forEach(fn: [&](auto &kv) {
577 UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false);
578 return true;
579 });
580
581 current_dynamic_init_module_name = nullptr;
582}
583