1 /* 2 * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 #include "precompiled.hpp" 26 #include "cds/aotClassLinker.hpp" 27 #include "cds/archiveBuilder.hpp" 28 #include "cds/archiveHeapWriter.hpp" 29 #include "cds/archiveUtils.inline.hpp" 30 #include "cds/cds_globals.hpp" 31 #include "cds/cdsConfig.hpp" 32 #include "cds/dynamicArchive.hpp" 33 #include "cds/regeneratedClasses.hpp" 34 #include "classfile/classLoader.hpp" 35 #include "classfile/classLoaderData.inline.hpp" 36 #include "classfile/symbolTable.hpp" 37 #include "classfile/systemDictionaryShared.hpp" 38 #include "classfile/vmSymbols.hpp" 39 #include "gc/shared/collectedHeap.hpp" 40 #include "gc/shared/gcVMOperations.hpp" 41 #include "gc/shared/gc_globals.hpp" 42 #include "jvm.h" 43 #include "logging/log.hpp" 44 #include "memory/metaspaceClosure.hpp" 45 #include "memory/resourceArea.hpp" 46 #include "oops/klass.inline.hpp" 47 #include "runtime/arguments.hpp" 48 #include "runtime/os.hpp" 49 #include "runtime/sharedRuntime.hpp" 50 #include "runtime/vmOperations.hpp" 51 #include "runtime/vmThread.hpp" 52 #include "utilities/align.hpp" 53 #include "utilities/bitMap.inline.hpp" 54 55 56 class DynamicArchiveBuilder : public ArchiveBuilder { 57 const char* _archive_name; 58 public: 59 DynamicArchiveBuilder(const char* archive_name) : _archive_name(archive_name) {} 60 void mark_pointer(address* ptr_loc) { 61 ArchivePtrMarker::mark_pointer(ptr_loc); 62 } 63 64 static int dynamic_dump_method_comparator(Method* a, Method* b) { 65 Symbol* a_name = a->name(); 66 Symbol* b_name = b->name(); 67 68 if (a_name == b_name) { 69 return 0; 70 } 71 72 u4 a_offset = ArchiveBuilder::current()->any_to_offset_u4(a_name); 73 u4 b_offset = ArchiveBuilder::current()->any_to_offset_u4(b_name); 74 75 if (a_offset < b_offset) { 76 return -1; 77 } else { 78 assert(a_offset > b_offset, "must be"); 79 return 1; 80 } 81 } 82 83 public: 84 DynamicArchiveHeader *_header; 85 86 void init_header(); 87 void release_header(); 88 void post_dump(); 89 void sort_methods(); 90 void sort_methods(InstanceKlass* ik) const; 91 void remark_pointers_for_instance_klass(InstanceKlass* k, bool should_mark) const; 92 void write_archive(char* serialized_data); 93 void gather_array_klasses(); 94 95 public: 96 DynamicArchiveBuilder() : ArchiveBuilder() { } 97 98 // Do this before and after the archive dump to see if any corruption 99 // is caused by dynamic dumping. 100 void verify_universe(const char* info) { 101 if (VerifyBeforeExit) { 102 log_info(cds)("Verify %s", info); 103 // Among other things, this ensures that Eden top is correct. 104 Universe::heap()->prepare_for_verify(); 105 Universe::verify(info); 106 } 107 } 108 109 void doit() { 110 verify_universe("Before CDS dynamic dump"); 111 DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm); 112 113 // Block concurrent class unloading from changing the _dumptime_table 114 MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); 115 SystemDictionaryShared::find_all_archivable_classes(); 116 117 if (SystemDictionaryShared::is_dumptime_table_empty()) { 118 log_warning(cds, dynamic)("There is no class to be included in the dynamic archive."); 119 return; 120 } 121 122 log_info(cds,dynamic)("CDS dynamic dump: clinit = " JLONG_FORMAT "ms)", 123 ClassLoader::class_init_time_ms()); 124 125 init_header(); 126 gather_source_objs(); 127 gather_array_klasses(); 128 reserve_buffer(); 129 130 log_info(cds, dynamic)("Copying %d klasses and %d symbols", 131 klasses()->length(), symbols()->length()); 132 dump_rw_metadata(); 133 dump_ro_metadata(); 134 relocate_metaspaceobj_embedded_pointers(); 135 136 verify_estimate_size(_estimated_metaspaceobj_bytes, "MetaspaceObjs"); 137 138 sort_methods(); 139 140 log_info(cds)("Make classes shareable"); 141 make_klasses_shareable(); 142 143 char* serialized_data; 144 { 145 // Write the symbol table and system dictionaries to the RO space. 146 // Note that these tables still point to the *original* objects, so 147 // they would need to call DynamicArchive::original_to_target() to 148 // get the correct addresses. 149 assert(current_dump_region() == ro_region(), "Must be RO space"); 150 SymbolTable::write_to_archive(symbols()); 151 152 ArchiveBuilder::OtherROAllocMark mark; 153 SystemDictionaryShared::write_to_archive(false); 154 DynamicArchive::dump_array_klasses(); 155 AOTClassLinker::write_to_archive(); 156 157 serialized_data = ro_region()->top(); 158 WriteClosure wc(ro_region()); 159 ArchiveBuilder::serialize_dynamic_archivable_items(&wc); 160 } 161 162 verify_estimate_size(_estimated_hashtable_bytes, "Hashtables"); 163 164 log_info(cds)("Adjust lambda proxy class dictionary"); 165 SystemDictionaryShared::adjust_lambda_proxy_class_dictionary(); 166 167 relocate_to_requested(); 168 169 write_archive(serialized_data); 170 release_header(); 171 DynamicArchive::post_dump(); 172 173 post_dump(); 174 175 assert(_num_dump_regions_used == _total_dump_regions, "must be"); 176 verify_universe("After CDS dynamic dump"); 177 } 178 179 virtual void iterate_roots(MetaspaceClosure* it) { 180 FileMapInfo::metaspace_pointers_do(it); 181 SystemDictionaryShared::dumptime_classes_do(it); 182 iterate_primitive_array_klasses(it); 183 } 184 185 void iterate_primitive_array_klasses(MetaspaceClosure* it) { 186 for (int i = T_BOOLEAN; i <= T_LONG; i++) { 187 assert(is_java_primitive((BasicType)i), "sanity"); 188 Klass* k = Universe::typeArrayKlass((BasicType)i); // this give you "[I", etc 189 assert(MetaspaceShared::is_shared_static((void*)k), 190 "one-dimensional primitive array should be in static archive"); 191 ArrayKlass* ak = ArrayKlass::cast(k); 192 while (ak != nullptr && ak->is_shared()) { 193 Klass* next_k = ak->array_klass_or_null(); 194 if (next_k != nullptr) { 195 ak = ArrayKlass::cast(next_k); 196 } else { 197 ak = nullptr; 198 } 199 } 200 if (ak != nullptr) { 201 assert(ak->dimension() > 1, "sanity"); 202 // this is the lowest dimension that's not in the static archive 203 it->push(&ak); 204 } 205 } 206 } 207 }; 208 209 void DynamicArchiveBuilder::init_header() { 210 FileMapInfo* mapinfo = new FileMapInfo(_archive_name, false); 211 assert(FileMapInfo::dynamic_info() == mapinfo, "must be"); 212 FileMapInfo* base_info = FileMapInfo::current_info(); 213 // header only be available after populate_header 214 mapinfo->populate_header(base_info->core_region_alignment()); 215 _header = mapinfo->dynamic_header(); 216 217 _header->set_base_header_crc(base_info->crc()); 218 for (int i = 0; i < MetaspaceShared::n_regions; i++) { 219 _header->set_base_region_crc(i, base_info->region_crc(i)); 220 } 221 } 222 223 void DynamicArchiveBuilder::release_header() { 224 // We temporarily allocated a dynamic FileMapInfo for dumping, which makes it appear we 225 // have mapped a dynamic archive, but we actually have not. We are in a safepoint now. 226 // Let's free it so that if class loading happens after we leave the safepoint, nothing 227 // bad will happen. 228 assert(SafepointSynchronize::is_at_safepoint(), "must be"); 229 FileMapInfo *mapinfo = FileMapInfo::dynamic_info(); 230 assert(mapinfo != nullptr && _header == mapinfo->dynamic_header(), "must be"); 231 delete mapinfo; 232 assert(!DynamicArchive::is_mapped(), "must be"); 233 _header = nullptr; 234 } 235 236 void DynamicArchiveBuilder::post_dump() { 237 ArchivePtrMarker::reset_map_and_vs(); 238 AOTClassLinker::dispose(); 239 } 240 241 void DynamicArchiveBuilder::sort_methods() { 242 InstanceKlass::disable_method_binary_search(); 243 for (int i = 0; i < klasses()->length(); i++) { 244 Klass* k = get_buffered_addr(klasses()->at(i)); 245 if (k->is_instance_klass()) { 246 sort_methods(InstanceKlass::cast(k)); 247 } 248 } 249 } 250 251 // The address order of the copied Symbols may be different than when the original 252 // klasses were created. Re-sort all the tables. See Method::sort_methods(). 253 void DynamicArchiveBuilder::sort_methods(InstanceKlass* ik) const { 254 assert(ik != nullptr, "DynamicArchiveBuilder currently doesn't support dumping the base archive"); 255 if (MetaspaceShared::is_in_shared_metaspace(ik)) { 256 // We have reached a supertype that's already in the base archive 257 return; 258 } 259 assert(is_in_buffer_space(ik), "method sorting must be done on buffered class, not original class"); 260 if (ik->java_mirror() == nullptr) { 261 // null mirror means this class has already been visited and methods are already sorted 262 return; 263 } 264 ik->remove_java_mirror(); 265 266 if (log_is_enabled(Debug, cds, dynamic)) { 267 ResourceMark rm; 268 log_debug(cds, dynamic)("sorting methods for " PTR_FORMAT " (" PTR_FORMAT ") %s", 269 p2i(ik), p2i(to_requested(ik)), ik->external_name()); 270 } 271 272 // Method sorting may re-layout the [iv]tables, which would change the offset(s) 273 // of the locations in an InstanceKlass that would contain pointers. Let's clear 274 // all the existing pointer marking bits, and re-mark the pointers after sorting. 275 remark_pointers_for_instance_klass(ik, false); 276 277 // Make sure all supertypes have been sorted 278 sort_methods(ik->java_super()); 279 Array<InstanceKlass*>* interfaces = ik->local_interfaces(); 280 int len = interfaces->length(); 281 for (int i = 0; i < len; i++) { 282 sort_methods(interfaces->at(i)); 283 } 284 285 #ifdef ASSERT 286 if (ik->methods() != nullptr) { 287 for (int m = 0; m < ik->methods()->length(); m++) { 288 Symbol* name = ik->methods()->at(m)->name(); 289 assert(MetaspaceShared::is_in_shared_metaspace(name) || is_in_buffer_space(name), "must be"); 290 } 291 } 292 if (ik->default_methods() != nullptr) { 293 for (int m = 0; m < ik->default_methods()->length(); m++) { 294 Symbol* name = ik->default_methods()->at(m)->name(); 295 assert(MetaspaceShared::is_in_shared_metaspace(name) || is_in_buffer_space(name), "must be"); 296 } 297 } 298 #endif 299 300 Method::sort_methods(ik->methods(), /*set_idnums=*/true, dynamic_dump_method_comparator); 301 if (ik->default_methods() != nullptr) { 302 Method::sort_methods(ik->default_methods(), /*set_idnums=*/false, dynamic_dump_method_comparator); 303 } 304 if (ik->is_linked()) { 305 // If the class has already been linked, we must relayout the i/v tables, whose order depends 306 // on the method sorting order. 307 // If the class is unlinked, we cannot layout the i/v tables yet. This is OK, as the 308 // i/v tables will be initialized at runtime after bytecode verification. 309 ik->vtable().initialize_vtable(); 310 ik->itable().initialize_itable(); 311 } 312 313 // Set all the pointer marking bits after sorting. 314 remark_pointers_for_instance_klass(ik, true); 315 } 316 317 template<bool should_mark> 318 class PointerRemarker: public MetaspaceClosure { 319 public: 320 virtual bool do_ref(Ref* ref, bool read_only) { 321 if (should_mark) { 322 ArchivePtrMarker::mark_pointer(ref->addr()); 323 } else { 324 ArchivePtrMarker::clear_pointer(ref->addr()); 325 } 326 return false; // don't recurse 327 } 328 }; 329 330 void DynamicArchiveBuilder::remark_pointers_for_instance_klass(InstanceKlass* k, bool should_mark) const { 331 if (should_mark) { 332 PointerRemarker<true> marker; 333 k->metaspace_pointers_do(&marker); 334 marker.finish(); 335 } else { 336 PointerRemarker<false> marker; 337 k->metaspace_pointers_do(&marker); 338 marker.finish(); 339 } 340 } 341 342 void DynamicArchiveBuilder::write_archive(char* serialized_data) { 343 _header->set_shared_path_table(FileMapInfo::shared_path_table().table()); 344 _header->set_serialized_data(serialized_data); 345 346 FileMapInfo* dynamic_info = FileMapInfo::dynamic_info(); 347 assert(dynamic_info != nullptr, "Sanity"); 348 349 dynamic_info->open_for_write(); 350 ArchiveHeapInfo no_heap_for_dynamic_dump; 351 ArchiveBuilder::write_archive(dynamic_info, &no_heap_for_dynamic_dump); 352 353 address base = _requested_dynamic_archive_bottom; 354 address top = _requested_dynamic_archive_top; 355 size_t file_size = pointer_delta(top, base, sizeof(char)); 356 357 log_info(cds, dynamic)("Written dynamic archive " PTR_FORMAT " - " PTR_FORMAT 358 " [" UINT32_FORMAT " bytes header, " SIZE_FORMAT " bytes total]", 359 p2i(base), p2i(top), _header->header_size(), file_size); 360 361 log_info(cds, dynamic)("%d klasses; %d symbols", klasses()->length(), symbols()->length()); 362 } 363 364 void DynamicArchiveBuilder::gather_array_klasses() { 365 for (int i = 0; i < klasses()->length(); i++) { 366 if (klasses()->at(i)->is_objArray_klass()) { 367 ObjArrayKlass* oak = ObjArrayKlass::cast(klasses()->at(i)); 368 Klass* elem = oak->element_klass(); 369 if (MetaspaceShared::is_shared_static(elem)) { 370 // Only capture the array klass whose element_klass is in the static archive. 371 // During run time, setup (see DynamicArchive::setup_array_klasses()) is needed 372 // so that the element_klass can find its array klasses from the dynamic archive. 373 DynamicArchive::append_array_klass(oak); 374 } else { 375 // The element_klass and its array klasses are in the same archive. 376 assert(!MetaspaceShared::is_shared_static(oak), 377 "we should not gather klasses that are already in the static archive"); 378 } 379 } 380 } 381 log_debug(cds)("Total array klasses gathered for dynamic archive: %d", DynamicArchive::num_array_klasses()); 382 } 383 384 class VM_PopulateDynamicDumpSharedSpace: public VM_GC_Sync_Operation { 385 DynamicArchiveBuilder _builder; 386 public: 387 VM_PopulateDynamicDumpSharedSpace(const char* archive_name) 388 : VM_GC_Sync_Operation(), _builder(archive_name) {} 389 VMOp_Type type() const { return VMOp_PopulateDumpSharedSpace; } 390 void doit() { 391 ResourceMark rm; 392 if (AllowArchivingWithJavaAgent) { 393 log_warning(cds)("This archive was created with AllowArchivingWithJavaAgent. It should be used " 394 "for testing purposes only and should not be used in a production environment"); 395 } 396 FileMapInfo::check_nonempty_dir_in_shared_path_table(); 397 398 _builder.doit(); 399 } 400 ~VM_PopulateDynamicDumpSharedSpace() { 401 RegeneratedClasses::cleanup(); 402 } 403 }; 404 405 // _array_klasses and _dynamic_archive_array_klasses only hold the array klasses 406 // which have element klass in the static archive. 407 GrowableArray<ObjArrayKlass*>* DynamicArchive::_array_klasses = nullptr; 408 Array<ObjArrayKlass*>* DynamicArchive::_dynamic_archive_array_klasses = nullptr; 409 410 void DynamicArchive::append_array_klass(ObjArrayKlass* ak) { 411 if (_array_klasses == nullptr) { 412 _array_klasses = new (mtClassShared) GrowableArray<ObjArrayKlass*>(50, mtClassShared); 413 } 414 _array_klasses->append(ak); 415 } 416 417 void DynamicArchive::dump_array_klasses() { 418 assert(CDSConfig::is_dumping_dynamic_archive(), "sanity"); 419 if (_array_klasses != nullptr) { 420 ArchiveBuilder* builder = ArchiveBuilder::current(); 421 int num_array_klasses = _array_klasses->length(); 422 _dynamic_archive_array_klasses = 423 ArchiveBuilder::new_ro_array<ObjArrayKlass*>(num_array_klasses); 424 for (int i = 0; i < num_array_klasses; i++) { 425 builder->write_pointer_in_buffer(_dynamic_archive_array_klasses->adr_at(i), _array_klasses->at(i)); 426 } 427 } 428 } 429 430 void DynamicArchive::setup_array_klasses() { 431 if (_dynamic_archive_array_klasses != nullptr) { 432 for (int i = 0; i < _dynamic_archive_array_klasses->length(); i++) { 433 ObjArrayKlass* oak = _dynamic_archive_array_klasses->at(i); 434 assert(!oak->is_typeArray_klass(), "all type array classes must be in static archive"); 435 436 Klass* elm = oak->element_klass(); 437 assert(MetaspaceShared::is_shared_static((void*)elm), "must be"); 438 439 if (elm->is_instance_klass()) { 440 assert(InstanceKlass::cast(elm)->array_klasses() == nullptr, "must be"); 441 InstanceKlass::cast(elm)->set_array_klasses(oak); 442 } else { 443 assert(elm->is_array_klass(), "sanity"); 444 assert(ArrayKlass::cast(elm)->higher_dimension() == nullptr, "must be"); 445 ArrayKlass::cast(elm)->set_higher_dimension(oak); 446 } 447 } 448 log_debug(cds)("Total array klasses read from dynamic archive: %d", _dynamic_archive_array_klasses->length()); 449 } 450 } 451 452 void DynamicArchive::serialize_array_klasses(SerializeClosure* soc) { 453 soc->do_ptr(&_dynamic_archive_array_klasses); 454 } 455 456 void DynamicArchive::make_array_klasses_shareable() { 457 if (_array_klasses != nullptr) { 458 int num_array_klasses = _array_klasses->length(); 459 for (int i = 0; i < num_array_klasses; i++) { 460 ObjArrayKlass* k = ArchiveBuilder::current()->get_buffered_addr(_array_klasses->at(i)); 461 k->remove_unshareable_info(); 462 } 463 } 464 } 465 466 void DynamicArchive::post_dump() { 467 if (_array_klasses != nullptr) { 468 delete _array_klasses; 469 _array_klasses = nullptr; 470 } 471 } 472 473 int DynamicArchive::num_array_klasses() { 474 return _array_klasses != nullptr ? _array_klasses->length() : 0; 475 } 476 477 void DynamicArchive::check_for_dynamic_dump() { 478 if (CDSConfig::is_dumping_dynamic_archive() && !CDSConfig::is_using_archive()) { 479 // This could happen if SharedArchiveFile has failed to load: 480 // - -Xshare:off was specified 481 // - SharedArchiveFile points to an non-existent file. 482 // - SharedArchiveFile points to an archive that has failed CRC check 483 // - SharedArchiveFile is not specified and the VM doesn't have a compatible default archive 484 485 #define __THEMSG " is unsupported when base CDS archive is not loaded. Run with -Xlog:cds for more info." 486 if (RecordDynamicDumpInfo) { 487 log_error(cds)("-XX:+RecordDynamicDumpInfo%s", __THEMSG); 488 MetaspaceShared::unrecoverable_loading_error(); 489 } else { 490 assert(ArchiveClassesAtExit != nullptr, "sanity"); 491 log_warning(cds)("-XX:ArchiveClassesAtExit" __THEMSG); 492 } 493 #undef __THEMSG 494 CDSConfig::disable_dumping_dynamic_archive(); 495 } 496 } 497 498 void DynamicArchive::dump_at_exit(JavaThread* current, const char* archive_name) { 499 ExceptionMark em(current); 500 ResourceMark rm(current); 501 CDSConfig::DumperThreadMark dumper_thread_mark(current); 502 503 if (!CDSConfig::is_dumping_dynamic_archive() || archive_name == nullptr) { 504 return; 505 } 506 507 log_info(cds, dynamic)("Preparing for dynamic dump at exit in thread %s", current->name()); 508 509 JavaThread* THREAD = current; // For TRAPS processing related to link_shared_classes 510 MetaspaceShared::link_shared_classes(false/*not from jcmd*/, THREAD); 511 if (!HAS_PENDING_EXCEPTION) { 512 // copy shared path table to saved. 513 if (!HAS_PENDING_EXCEPTION) { 514 VM_PopulateDynamicDumpSharedSpace op(archive_name); 515 VMThread::execute(&op); 516 return; 517 } 518 } 519 520 // One of the prepatory steps failed 521 oop ex = current->pending_exception(); 522 log_error(cds)("Dynamic dump has failed"); 523 log_error(cds)("%s: %s", ex->klass()->external_name(), 524 java_lang_String::as_utf8_string(java_lang_Throwable::message(ex))); 525 CLEAR_PENDING_EXCEPTION; 526 CDSConfig::disable_dumping_dynamic_archive(); // Just for good measure 527 } 528 529 // This is called by "jcmd VM.cds dynamic_dump" 530 void DynamicArchive::dump_for_jcmd(const char* archive_name, TRAPS) { 531 CDSConfig::DumperThreadMark dumper_thread_mark(THREAD); 532 assert(CDSConfig::is_using_archive() && RecordDynamicDumpInfo, "already checked in arguments.cpp"); 533 assert(ArchiveClassesAtExit == nullptr, "already checked in arguments.cpp"); 534 assert(CDSConfig::is_dumping_dynamic_archive(), "already checked by check_for_dynamic_dump() during VM startup"); 535 MetaspaceShared::link_shared_classes(true/*from jcmd*/, CHECK); 536 // copy shared path table to saved. 537 VM_PopulateDynamicDumpSharedSpace op(archive_name); 538 VMThread::execute(&op); 539 } 540 541 bool DynamicArchive::validate(FileMapInfo* dynamic_info) { 542 assert(!dynamic_info->is_static(), "must be"); 543 // Check if the recorded base archive matches with the current one 544 FileMapInfo* base_info = FileMapInfo::current_info(); 545 DynamicArchiveHeader* dynamic_header = dynamic_info->dynamic_header(); 546 547 // Check the header crc 548 if (dynamic_header->base_header_crc() != base_info->crc()) { 549 log_warning(cds)("Dynamic archive cannot be used: static archive header checksum verification failed."); 550 return false; 551 } 552 553 // Check each space's crc 554 for (int i = 0; i < MetaspaceShared::n_regions; i++) { 555 if (dynamic_header->base_region_crc(i) != base_info->region_crc(i)) { 556 log_warning(cds)("Dynamic archive cannot be used: static archive region #%d checksum verification failed.", i); 557 return false; 558 } 559 } 560 561 return true; 562 } 563 564 void DynamicArchiveHeader::print(outputStream* st) { 565 ResourceMark rm; 566 567 st->print_cr("- base_header_crc: 0x%08x", base_header_crc()); 568 for (int i = 0; i < NUM_CDS_REGIONS; i++) { 569 st->print_cr("- base_region_crc[%d]: 0x%08x", i, base_region_crc(i)); 570 } 571 }