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 32 namespace __asan { 33 34 typedef __asan_global Global; 35 36 struct GlobalListNode { 37 const Global *g = nullptr; 38 GlobalListNode *next = nullptr; 39 }; 40 typedef IntrusiveList<GlobalListNode> ListOfGlobals; 41 42 static Mutex mu_for_globals; 43 static ListOfGlobals list_of_all_globals SANITIZER_GUARDED_BY(mu_for_globals); 44 45 struct 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. 52 struct GlobalRegistrationSite { 53 u32 stack_id; 54 Global *g_first, *g_last; 55 }; 56 typedef InternalMmapVector<GlobalRegistrationSite> GlobalRegistrationSiteVector; 57 static GlobalRegistrationSiteVector *global_registration_site_vector; 58 59 static 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 73 static const char *current_dynamic_init_module_name 74 SANITIZER_GUARDED_BY(mu_for_globals) = nullptr; 75 76 using DynInitGlobalsByModule = 77 DenseMap<const char *, IntrusiveList<DynInitGlobal>>; 78 79 // TODO: Add a NoDestroy helper, this patter is very common in sanitizers. 80 static 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 92 ALWAYS_INLINE void PoisonShadowForGlobal(const Global *g, u8 value) { 93 FastPoisonShadow(g->beg, g->size_with_redzone, value); 94 } 95 96 ALWAYS_INLINE void PoisonRedZones(const Global &g) { 97 uptr aligned_size = RoundUpTo(g.size, ASAN_SHADOW_GRANULARITY); 98 FastPoisonShadow(g.beg + aligned_size, g.size_with_redzone - aligned_size, 99 kAsanGlobalRedzoneMagic); 100 if (g.size != aligned_size) { 101 FastPoisonShadowPartialRightRedzone( 102 g.beg + RoundDownTo(g.size, ASAN_SHADOW_GRANULARITY), 103 g.size % ASAN_SHADOW_GRANULARITY, ASAN_SHADOW_GRANULARITY, 104 kAsanGlobalRedzoneMagic); 105 } 106 } 107 108 const uptr kMinimalDistanceFromAnotherGlobal = 64; 109 110 static void AddGlobalToList(ListOfGlobals &list, const Global *g) { 111 list.push_front(new (GetGlobalLowLevelAllocator()) GlobalListNode{g}); 112 } 113 114 static 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, 0); 122 // Poison redzones back. 123 PoisonRedZones(*g); 124 if (mark_initialized) 125 dyn_g.initialized = true; 126 } 127 } 128 129 static 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, kAsanInitializationOrderMagic); 136 } 137 } 138 139 static 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 145 static void ReportGlobal(const Global &g, const char *prefix) { 146 DataInfo info; 147 bool symbolized = Symbolizer::GetOrInit()->SymbolizeData(g.beg, &info); 148 Report( 149 "%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(" 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(" location: name=%s, %d\n", g.gcc_location->filename, g.gcc_location->line_no); 161 } 162 } 163 164 static 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 175 int 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, "Search"); 184 if (IsAddressNearGlobal(addr, g)) { 185 internal_memcpy(&globals[res], &g, sizeof(g)); 186 if (reg_sites) 187 reg_sites[res] = FindRegistrationSite(&g); 188 res++; 189 if (res == max_globals) 190 break; 191 } 192 } 193 return res; 194 } 195 196 enum 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. 204 static 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(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(g->name)) 219 ReportODRViolation(g, FindRegistrationSite(g), l.g, 220 FindRegistrationSite(l.g)); 221 } 222 } else { // UNREGISTERED 223 *odr_indicator = REGISTERED; 224 } 225 226 AddGlobalToList(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. 232 static void CheckODRViolationViaPoisoning(const Global *g) 233 SANITIZER_REQUIRES(mu_for_globals) { 234 if (__asan_region_is_poisoned(g->beg, 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(g->name)) { 241 ReportODRViolation(g, FindRegistrationSite(g), l.g, 242 FindRegistrationSite(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. 264 static 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. 271 static void RegisterGlobal(const Global *g) SANITIZER_REQUIRES(mu_for_globals) { 272 CHECK(AsanInited()); 273 if (flags()->report_globals >= 2) 274 ReportGlobal(*g, "Added"); 275 CHECK(flags()->report_globals); 276 CHECK(AddrIsInMem(g->beg)); 277 if (!AddrIsAlignedByGranularity(g->beg)) { 278 Report("The following global variable is not properly aligned.\n"); 279 Report("This may happen if another global with the same name\n"); 280 Report("resides in another non-instrumented module.\n"); 281 Report("Or the global comes from a C file built w/o -fno-common.\n"); 282 Report("In either case this is likely an ODR violation bug,\n"); 283 Report("but AddressSanitizer can not provide more details.\n"); 284 ReportODRViolation(g, FindRegistrationSite(g), g, 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); 298 299 AddGlobalToList(list_of_all_globals, g); 300 301 if (g->has_dynamic_init) { 302 DynInitGlobals()[g->module_name].push_back( 303 new (GetGlobalLowLevelAllocator()) DynInitGlobal{*g, false}); 304 } 305 } 306 307 static void UnregisterGlobal(const Global *g) 308 SANITIZER_REQUIRES(mu_for_globals) { 309 CHECK(AsanInited()); 310 if (flags()->report_globals >= 2) 311 ReportGlobal(*g, "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, 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 329 void StopInitOrderChecking() { 330 if (!flags()->check_initialization_order) 331 return; 332 Lock lock(&mu_for_globals); 333 flags()->check_initialization_order = false; 334 DynInitGlobals().forEach([&](auto &kv) { 335 UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false); 336 return true; 337 }); 338 } 339 340 static bool IsASCII(unsigned char c) { return /*0x00 <= c &&*/ c <= 0x7F; } 341 342 const 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. 355 void 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(" '%s' is ascii string '%s'\n", MaybeDemangleGlobalName(g.name), 362 (char *)g.beg); 363 } 364 365 void PrintGlobalLocation(InternalScopedString *str, const __asan_global &g, 366 bool print_module_name) { 367 DataInfo info; 368 if (Symbolizer::GetOrInit()->SymbolizeData(g.beg, &info) && info.line != 0) { 369 str->AppendF("%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("%s", g.gcc_location->filename ? g.gcc_location->filename 373 : g.module_name); 374 if (g.gcc_location->line_no) 375 str->AppendF(":%d", g.gcc_location->line_no); 376 if (g.gcc_location->column_no) 377 str->AppendF(":%d", g.gcc_location->column_no); 378 } else { 379 str->AppendF("%s", g.module_name); 380 } 381 if (print_module_name && info.module) 382 str->AppendF(" in %s", info.module); 383 } 384 385 } // namespace __asan 386 387 // ---------------------- Interface ---------------- {{{1 388 using 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. 393 void __asan_register_image_globals(uptr *flag) { 394 if (*flag) 395 return; 396 AsanApplyToGlobals(__asan_register_globals, flag); 397 *flag = 1; 398 } 399 400 // This mirrors __asan_register_image_globals. 401 void __asan_unregister_image_globals(uptr *flag) { 402 if (!*flag) 403 return; 404 AsanApplyToGlobals(__asan_unregister_globals, flag); 405 *flag = 0; 406 } 407 408 void __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_start, globals_stop - globals_start); 415 *flag = 1; 416 } 417 418 void __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_start, globals_stop - globals_start); 425 *flag = 0; 426 } 427 428 // Register an array of globals. 429 void __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(128); 438 } 439 GlobalRegistrationSite site = {stack_id, &globals[0], &globals[n - 1]}; 440 global_registration_site_vector->push_back(site); 441 if (flags()->report_globals >= 2) { 442 PRINT_CURRENT_STACK(); 443 Printf("=== 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(&globals[i]); 462 } 463 464 // Poison the metadata. It should not be accessible to user code. 465 PoisonShadow(reinterpret_cast<uptr>(globals), n * sizeof(__asan_global), 466 kAsanGlobalRedzoneMagic); 467 } 468 469 // Unregister an array of globals. 470 // We must do this when a shared objects gets dlclosed. 471 void __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(&globals[i]); 481 } 482 483 // Unpoison the metadata. 484 PoisonShadow(reinterpret_cast<uptr>(globals), n * sizeof(__asan_global), 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. 491 void __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("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([&](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(DynInitGlobals()[current_dynamic_init_module_name]); 517 UnpoisonDynamicGlobals(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. 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 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. 563 void __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("DynInitUnpoison\n"); 575 576 DynInitGlobals().forEach([&](auto &kv) { 577 UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false); 578 return true; 579 }); 580 581 current_dynamic_init_module_name = nullptr; 582 } 583