/* * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ /* * @test * @summary SharedArchiveConsistency * @requires vm.cds * @library /test/lib * @modules jdk.jartool/sun.tools.jar * @build sun.hotspot.WhiteBox * @compile test-classes/Hello.java * @run driver ClassFileInstaller sun.hotspot.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI SharedArchiveConsistency */ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.Utils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import java.nio.file.StandardOpenOption; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import sun.hotspot.WhiteBox; public class SharedArchiveConsistency { public static WhiteBox wb; public static int offset_magic; // CDSFileMapHeaderBase::_magic public static int offset_version; // CDSFileMapHeaderBase::_version public static int offset_jvm_ident; // FileMapHeader::_jvm_ident public static int sp_offset_crc; // CDSFileMapRegion::_crc public static int file_header_size = -1;// total size of header, variant, need calculation public static int CDSFileMapRegion_size; // size of CDSFileMapRegion public static int sp_offset; // offset of CDSFileMapRegion public static int sp_used_offset; // offset of CDSFileMapRegion::_used public static int size_t_size; // size of size_t public static int int_size; // size of int public static File jsa; // will be updated during test public static File orgJsaFile; // kept the original file not touched. // The following should be consistent with the enum in the C++ MetaspaceShared class public static String[] shared_region_name = { "mc", // MiscCode "rw", // ReadWrite "ro", // ReadOnly "md", // MiscData "first_closed_archive", "last_closed_archive", "first_open_archive", "last_open_archive" }; public static int num_regions = shared_region_name.length; public static String[] matchMessages = { "Unable to use shared archive", "An error has occurred while processing the shared archive file.", "Checksum verification failed.", "The shared archive file has been truncated." }; public static void getFileOffsetInfo() throws Exception { wb = WhiteBox.getWhiteBox(); offset_magic = wb.getOffsetForName("FileMapHeader::_magic"); offset_version = wb.getOffsetForName("FileMapHeader::_version"); offset_jvm_ident = wb.getOffsetForName("FileMapHeader::_jvm_ident"); sp_offset_crc = wb.getOffsetForName("CDSFileMapRegion::_crc"); try { int nonExistOffset = wb.getOffsetForName("FileMapHeader::_non_exist_offset"); System.exit(-1); // should fail } catch (Exception e) { // success } sp_offset = wb.getOffsetForName("FileMapHeader::_space[0]") - offset_magic; sp_used_offset = wb.getOffsetForName("CDSFileMapRegion::_used") - sp_offset_crc; size_t_size = wb.getOffsetForName("size_t_size"); CDSFileMapRegion_size = wb.getOffsetForName("CDSFileMapRegion_size"); } public static int getFileHeaderSize(FileChannel fc) throws Exception { if (file_header_size != -1) { return file_header_size; } // this is not real header size, it is struct size int_size = wb.getOffsetForName("int_size"); file_header_size = wb.getOffsetForName("file_header_size"); System.out.println("file_header_size = " + file_header_size); file_header_size = (int)align_up_page(file_header_size); System.out.println("file_header_size (aligned to page) = " + file_header_size); return file_header_size; } public static long align_up_page(long l) throws Exception { // wb is obtained in getFileOffsetInfo() which is called first in main() else we should call // WhiteBox.getWhiteBox() here first. int pageSize = wb.getVMPageSize(); return (l + pageSize -1) & (~ (pageSize - 1)); } private static long getRandomBetween(long start, long end) throws Exception { if (start > end) { throw new IllegalArgumentException("start must be less than end"); } Random aRandom = Utils.getRandomInstance(); int d = aRandom.nextInt((int)(end - start)); if (d < 1) { d = 1; } return start + d; } public static long readInt(FileChannel fc, long offset, int nbytes) throws Exception { ByteBuffer bb = ByteBuffer.allocate(nbytes); bb.order(ByteOrder.nativeOrder()); fc.position(offset); fc.read(bb); return (nbytes > 4 ? bb.getLong(0) : bb.getInt(0)); } public static void writeData(FileChannel fc, long offset, ByteBuffer bb) throws Exception { fc.position(offset); fc.write(bb); } public static FileChannel getFileChannel(File jsaFile) throws Exception { List arry = new ArrayList(); arry.add(READ); arry.add(WRITE); return FileChannel.open(jsaFile.toPath(), new HashSet(arry)); } public static void modifyJsaContentRandomly(File jsaFile) throws Exception { FileChannel fc = getFileChannel(jsaFile); // corrupt random area in the data areas long[] used = new long[num_regions]; // record used bytes long start0, start, end, off; int used_offset, path_info_size; int bufSize; System.out.printf("%-24s%12s%12s%16s\n", "Space Name", "Used bytes", "Reg Start", "Random Offset"); start0 = getFileHeaderSize(fc); for (int i = 0; i < num_regions; i++) { used[i] = get_region_used_size_aligned(fc, i); start = start0; for (int j = 0; j < i; j++) { start += align_up_page(used[j]); } end = start + used[i]; if (start == end) { continue; // Ignore empty regions } off = getRandomBetween(start, end); System.out.printf("%-24s%12d%12d%16d\n", shared_region_name[i], used[i], start, off); if (end - off < 1024) { bufSize = (int)(end - off + 1); } else { bufSize = 1024; } ByteBuffer bbuf = ByteBuffer.wrap(new byte[bufSize]); writeData(fc, off, bbuf); } if (fc.isOpen()) { fc.close(); } } static long get_region_used_size_aligned(FileChannel fc, int region) throws Exception { long n = sp_offset + CDSFileMapRegion_size * region + sp_used_offset; long alignment = WhiteBox.getWhiteBox().metaspaceReserveAlignment(); long used = readInt(fc, n, size_t_size); used = (used + alignment - 1) & ~(alignment - 1); return used; } public static boolean modifyJsaContent(int region, File jsaFile) throws Exception { FileChannel fc = getFileChannel(jsaFile); byte[] buf = new byte[4096]; ByteBuffer bbuf = ByteBuffer.wrap(buf); long total = 0L; long[] used = new long[num_regions]; System.out.printf("%-24s%12s\n", "Space name", "Used bytes"); for (int i = 0; i < num_regions; i++) { used[i] = get_region_used_size_aligned(fc, i); System.out.printf("%-24s%12d\n", shared_region_name[i], used[i]); total += used[i]; } System.out.printf("%-24s%12d\n", "Total: ", total); long header_size = getFileHeaderSize(fc); long region_start_offset = header_size; for (int i=0; i