/* * Copyright (c) 2020, 2024, 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 * @bug 8241071 * @summary The same JDK build should always generate the same archive file (no randomness). * @requires vm.cds & vm.flagless * @library /test/lib * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI DeterministicDump */ import jdk.test.lib.cds.CDSArchiveUtils; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.Platform; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; public class DeterministicDump { static long HEADER_SIZE; // Size of header in bytes static int HEADER_LEN = 106; // Number of lines in CDS map file header static int LINE_OFFSET = 22; // Offset from address to first word of data static int NUM_LINES = 5; // Number of lines to be printed static int WORD_LEN = 16 + 1; // Length of word in map file public static void main(String[] args) throws Exception { doTest(false); if (Platform.is64bit()) { // There's no oop/klass compression on 32-bit. doTest(true); } } public static void doTest(boolean compressed) throws Exception { ArrayList baseArgs = new ArrayList<>(); // Try to reduce indeterminism of GC heap sizing and evacuation. baseArgs.add("-Xmx128M"); baseArgs.add("-Xms128M"); baseArgs.add("-Xmn120M"); if (Platform.is64bit()) { // This option is available only on 64-bit. String sign = (compressed) ? "+" : "-"; baseArgs.add("-XX:" + sign + "UseCompressedOops"); } String baseArchive = dump(baseArgs); File baseArchiveFile = new File(baseArchive + ".jsa"); HEADER_SIZE = CDSArchiveUtils.fileHeaderSize(baseArchiveFile); // (1) Dump with the same args. Should produce the same archive. String baseArchive2 = dump(baseArgs); compare(baseArchive, baseArchive2, baseArchiveFile); // (2) This will cause the archive to be relocated during dump time. We should // still get the same bits. This simulates relocation that happens when // Address Space Layout Randomization prevents the archive space to // be mapped at the default location. String relocatedArchive = dump(baseArgs, "-XX:+UnlockDiagnosticVMOptions", "-XX:ArchiveRelocationMode=1"); compare(baseArchive, relocatedArchive, baseArchiveFile); } static int id = 0; static String dump(ArrayList args, String... more) throws Exception { String logName = "SharedArchiveFile" + (id++); String archiveName = logName + ".jsa"; String mapName = logName + ".map"; CDSOptions opts = (new CDSOptions()) .addPrefix("-Xint") // Override any -Xmixed/-Xcomp flags from jtreg -vmoptions .addPrefix("-Xlog:cds=debug,gc=debug") .addPrefix("-Xlog:cds+map*=trace:file=" + mapName + ":none:filesize=0") .setArchiveName(archiveName) .addSuffix(args) .addSuffix(more); CDSTestUtils.createArchiveAndCheck(opts); return logName; } static void compare(String file0, String file1, File archiveFile) throws Exception { byte[] buff0 = new byte[4096]; byte[] buff1 = new byte[4096]; try (FileInputStream in0 = new FileInputStream(file0 + ".jsa"); FileInputStream in1 = new FileInputStream(file1 + ".jsa")) { int total = 0; while (true) { int n0 = read(in0, buff0); int n1 = read(in1, buff1); if (n0 != n1) { throw new RuntimeException("File contents (file sizes?) are different after " + total + " bytes; n0 = " + n0 + ", n1 = " + n1); } if (n0 == 0) { System.out.println("File contents are the same: " + total + " bytes"); break; } for (int i = 0; i < n0; i++) { byte b0 = buff0[i]; byte b1 = buff1[i]; if (b0 != b1) { // The checksums are stored in the header so it should be skipped // since we want to see the first meaningful diff between the archives if (total + i > HEADER_SIZE) { print_diff(file0 + ".map", file1 + ".map", archiveFile, total + i); throw new RuntimeException("File content different at byte #" + (total + i) + ", b0 = " + b0 + ", b1 = " + b1); } } } total += n0; } } } static int read(FileInputStream in, byte[] buff) throws IOException { int total = 0; while (total < buff.length) { int n = in.read(buff, total, buff.length - total); if (n <= 0) { return total; } total += n; } return total; } // CDS map file doesn't print the alignment bytes so they need to be considered // when mapping the byte number in the archive to the word in the map file static int archiveByteToMapWord(File archiveFile, int location) throws Exception { int totalSize = 0; int word = location; long len = HEADER_SIZE; long aligned = CDSArchiveUtils.fileHeaderSizeAligned(archiveFile); for (int i = 0; i < CDSArchiveUtils.num_regions(); i++) { if (i != 0) { len = CDSArchiveUtils.usedRegionSize(archiveFile, i); aligned = CDSArchiveUtils.usedRegionSizeAligned(archiveFile, i); } totalSize += len; if (location > totalSize) { word -= (aligned - len - 16); } } return word/8; } // Read the mapfile and print out the lines associated with the location static void print_diff(String mapName0, String mapName1, File archiveFile, int location) throws Exception { FileReader f0 = new FileReader(mapName0); BufferedReader b0 = new BufferedReader(f0); FileReader f1 = new FileReader(mapName1); BufferedReader b1 = new BufferedReader(f1); int word = archiveByteToMapWord(archiveFile, location); int wordOffset = word % 4; // Each line in the map file prints four words String region = ""; // Skip header text and go to first line for (int i = 0; i < HEADER_LEN; i++) { b0.readLine(); b1.readLine(); } int line_num = HEADER_LEN; String s0 = ""; String s1 = ""; int count = 0; // Store lines before and including the diff ArrayDeque prefix0 = new ArrayDeque(); ArrayDeque prefix1 = new ArrayDeque(); // A line may contain 1-4 words so we iterate by word do { s0 = b0.readLine(); s1 = b1.readLine(); line_num++; if (prefix0.size() >= NUM_LINES / 2 + 1) { prefix0.removeFirst(); prefix1.removeFirst(); } prefix0.addLast(s0); prefix1.addLast(s1); // Skip lines with headers when counting words e.g. // [rw region 0x0000000800000000 - 0x00000008005a1f88 5906312 bytes] // or // 0x0000000800000b28: @@ TypeArrayU1 16 if (!s0.contains(": @@") && !s0.contains("bytes]")) { int words = (s0.length() - LINE_OFFSET - 70) / 8; count += words; } else if (s0.contains("bytes]")) { region = s0; } } while (count < word); // Print the diff with the region name above it System.out.println("[First diff: map file #1 (" + mapName0 + ")]"); System.out.println(region); String diff0 = print_diff_helper(b0, wordOffset, prefix0); System.out.println("\n[First diff: map file #2 (" + mapName1 + ")]"); System.out.println(region); String diff1 = print_diff_helper(b1, wordOffset, prefix1); System.out.printf("\nByte #%d at line #%d word #%d:\n", location, line_num, wordOffset); System.out.printf("%s: %s\n%s: %s\n", mapName0, diff0, mapName1, diff1); f0.close(); f1.close(); } static String print_diff_helper(BufferedReader b, int wordOffset, ArrayDeque prefix) throws Exception { int start = LINE_OFFSET + WORD_LEN * wordOffset; int end = start + WORD_LEN; String line = prefix.getLast(); String diff = line.substring(start, end); // Print previous lines for (String s : prefix) { if (s.equals(line)) { System.out.println(">" + s); } else { System.out.println(" " + s); } } // Print extra lines for (int i = 0; i < NUM_LINES / 2; i++) { System.out.println(" " + b.readLine()); } return diff; } }