1 /* 2 * Copyright (c) 2020, 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 * @test 26 * @bug 8241071 27 * @summary The same JDK build should always generate the same archive file (no randomness). 28 * @requires vm.cds & vm.flagless 29 * @library /test/lib 30 * @build jdk.test.whitebox.WhiteBox 31 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox 32 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions 33 * -XX:+WhiteBoxAPI DeterministicDump 34 */ 35 36 import jdk.test.lib.cds.CDSArchiveUtils; 37 import jdk.test.lib.cds.CDSOptions; 38 import jdk.test.lib.cds.CDSTestUtils; 39 import jdk.test.lib.Platform; 40 import java.io.BufferedReader; 41 import java.io.File; 42 import java.io.FileInputStream; 43 import java.io.FileReader; 44 import java.io.IOException; 45 import java.util.ArrayDeque; 46 import java.util.ArrayList; 47 48 public class DeterministicDump { 49 50 static long HEADER_SIZE; // Size of header in bytes 51 static int HEADER_LEN = 106; // Number of lines in CDS map file header 52 static int LINE_OFFSET = 22; // Offset from address to first word of data 53 static int NUM_LINES = 5; // Number of lines to be printed 54 static int WORD_LEN = 16 + 1; // Length of word in map file 55 56 public static void main(String[] args) throws Exception { 57 doTest(false); 58 59 if (Platform.is64bit()) { 60 // There's no oop/klass compression on 32-bit. 61 doTest(true); 62 } 63 } 64 65 public static void doTest(boolean compressed) throws Exception { 66 ArrayList<String> baseArgs = new ArrayList<>(); 67 68 // Try to reduce indeterminism of GC heap sizing and evacuation. 69 baseArgs.add("-Xmx128M"); 70 baseArgs.add("-Xms128M"); 71 baseArgs.add("-Xmn120M"); 72 73 if (Platform.is64bit()) { 74 // This option is available only on 64-bit. 75 String sign = (compressed) ? "+" : "-"; 76 baseArgs.add("-XX:" + sign + "UseCompressedOops"); 77 baseArgs.add("-XX:+UnlockExperimentalVMOptions"); 78 baseArgs.add("-XX:-UseCompactObjectHeaders"); 79 } 80 81 String baseArchive = dump(baseArgs); 82 File baseArchiveFile = new File(baseArchive + ".jsa"); 83 HEADER_SIZE = CDSArchiveUtils.fileHeaderSize(baseArchiveFile); 84 85 // (1) Dump with the same args. Should produce the same archive. 86 String baseArchive2 = dump(baseArgs); 87 compare(baseArchive, baseArchive2, baseArchiveFile); 88 89 // (2) This will cause the archive to be relocated during dump time. We should 90 // still get the same bits. This simulates relocation that happens when 91 // Address Space Layout Randomization prevents the archive space to 92 // be mapped at the default location. 93 String relocatedArchive = dump(baseArgs, "-XX:+UnlockDiagnosticVMOptions", "-XX:ArchiveRelocationMode=1"); 94 compare(baseArchive, relocatedArchive, baseArchiveFile); 95 } 96 97 static int id = 0; 98 static String dump(ArrayList<String> args, String... more) throws Exception { 99 String logName = "SharedArchiveFile" + (id++); 100 String archiveName = logName + ".jsa"; 101 String mapName = logName + ".map"; 102 CDSOptions opts = (new CDSOptions()) 103 .addPrefix("-Xint") // Override any -Xmixed/-Xcomp flags from jtreg -vmoptions 104 .addPrefix("-Xlog:cds=debug,gc=debug") 105 .addPrefix("-Xlog:cds+map*=trace:file=" + mapName + ":none:filesize=0") 106 .setArchiveName(archiveName) 107 .addSuffix(args) 108 .addSuffix(more); 109 CDSTestUtils.createArchiveAndCheck(opts); 110 111 return logName; 112 } 113 114 static void compare(String file0, String file1, File archiveFile) throws Exception { 115 byte[] buff0 = new byte[4096]; 116 byte[] buff1 = new byte[4096]; 117 try (FileInputStream in0 = new FileInputStream(file0 + ".jsa"); 118 FileInputStream in1 = new FileInputStream(file1 + ".jsa")) { 119 int total = 0; 120 while (true) { 121 int n0 = read(in0, buff0); 122 int n1 = read(in1, buff1); 123 if (n0 != n1) { 124 throw new RuntimeException("File contents (file sizes?) are different after " + total + " bytes; n0 = " 125 + n0 + ", n1 = " + n1); 126 } 127 if (n0 == 0) { 128 System.out.println("File contents are the same: " + total + " bytes"); 129 break; 130 } 131 for (int i = 0; i < n0; i++) { 132 byte b0 = buff0[i]; 133 byte b1 = buff1[i]; 134 if (b0 != b1) { 135 // The checksums are stored in the header so it should be skipped 136 // since we want to see the first meaningful diff between the archives 137 if (total + i > HEADER_SIZE) { 138 print_diff(file0 + ".map", file1 + ".map", archiveFile, total + i); 139 throw new RuntimeException("File content different at byte #" + (total + i) + ", b0 = " + b0 + ", b1 = " + b1); 140 } 141 } 142 } 143 total += n0; 144 } 145 } 146 } 147 148 static int read(FileInputStream in, byte[] buff) throws IOException { 149 int total = 0; 150 while (total < buff.length) { 151 int n = in.read(buff, total, buff.length - total); 152 if (n <= 0) { 153 return total; 154 } 155 total += n; 156 } 157 158 return total; 159 } 160 161 // CDS map file doesn't print the alignment bytes so they need to be considered 162 // when mapping the byte number in the archive to the word in the map file 163 static int archiveByteToMapWord(File archiveFile, int location) throws Exception { 164 int totalSize = 0; 165 int word = location; 166 167 long len = HEADER_SIZE; 168 long aligned = CDSArchiveUtils.fileHeaderSizeAligned(archiveFile); 169 for (int i = 0; i < CDSArchiveUtils.num_regions(); i++) { 170 if (i != 0) { 171 len = CDSArchiveUtils.usedRegionSize(archiveFile, i); 172 aligned = CDSArchiveUtils.usedRegionSizeAligned(archiveFile, i); 173 } 174 totalSize += len; 175 if (location > totalSize) { 176 word -= (aligned - len - 16); 177 } 178 } 179 return word/8; 180 } 181 182 // Read the mapfile and print out the lines associated with the location 183 static void print_diff(String mapName0, String mapName1, File archiveFile, int location) throws Exception { 184 FileReader f0 = new FileReader(mapName0); 185 BufferedReader b0 = new BufferedReader(f0); 186 187 FileReader f1 = new FileReader(mapName1); 188 BufferedReader b1 = new BufferedReader(f1); 189 190 int word = archiveByteToMapWord(archiveFile, location); 191 int wordOffset = word % 4; // Each line in the map file prints four words 192 String region = ""; 193 194 // Skip header text and go to first line 195 for (int i = 0; i < HEADER_LEN; i++) { 196 b0.readLine(); 197 b1.readLine(); 198 } 199 200 int line_num = HEADER_LEN; 201 String s0 = ""; 202 String s1 = ""; 203 int count = 0; 204 205 // Store lines before and including the diff 206 ArrayDeque<String> prefix0 = new ArrayDeque<String>(); 207 ArrayDeque<String> prefix1 = new ArrayDeque<String>(); 208 209 // A line may contain 1-4 words so we iterate by word 210 do { 211 s0 = b0.readLine(); 212 s1 = b1.readLine(); 213 line_num++; 214 215 if (prefix0.size() >= NUM_LINES / 2 + 1) { 216 prefix0.removeFirst(); 217 prefix1.removeFirst(); 218 } 219 prefix0.addLast(s0); 220 prefix1.addLast(s1); 221 222 // Skip lines with headers when counting words e.g. 223 // [rw region 0x0000000800000000 - 0x00000008005a1f88 5906312 bytes] 224 // or 225 // 0x0000000800000b28: @@ TypeArrayU1 16 226 if (!s0.contains(": @@") && !s0.contains("bytes]")) { 227 int words = (s0.length() - LINE_OFFSET - 70) / 8; 228 count += words; 229 } else if (s0.contains("bytes]")) { 230 region = s0; 231 } 232 } while (count < word); 233 234 // Print the diff with the region name above it 235 System.out.println("[First diff: map file #1 (" + mapName0 + ")]"); 236 System.out.println(region); 237 String diff0 = print_diff_helper(b0, wordOffset, prefix0); 238 239 System.out.println("\n[First diff: map file #2 (" + mapName1 + ")]"); 240 System.out.println(region); 241 String diff1 = print_diff_helper(b1, wordOffset, prefix1); 242 243 System.out.printf("\nByte #%d at line #%d word #%d:\n", location, line_num, wordOffset); 244 System.out.printf("%s: %s\n%s: %s\n", mapName0, diff0, mapName1, diff1); 245 246 f0.close(); 247 f1.close(); 248 } 249 250 static String print_diff_helper(BufferedReader b, int wordOffset, ArrayDeque<String> prefix) throws Exception { 251 int start = LINE_OFFSET + WORD_LEN * wordOffset; 252 int end = start + WORD_LEN; 253 String line = prefix.getLast(); 254 String diff = line.substring(start, end); 255 256 // Print previous lines 257 for (String s : prefix) { 258 if (s.equals(line)) { 259 System.out.println(">" + s); 260 } else { 261 System.out.println(" " + s); 262 } 263 } 264 265 // Print extra lines 266 for (int i = 0; i < NUM_LINES / 2; i++) { 267 System.out.println(" " + b.readLine()); 268 } 269 return diff; 270 } 271 }