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 } 78 79 String baseArchive = dump(baseArgs); 80 File baseArchiveFile = new File(baseArchive + ".jsa"); 81 HEADER_SIZE = CDSArchiveUtils.fileHeaderSize(baseArchiveFile); 82 83 // (1) Dump with the same args. Should produce the same archive. 84 String baseArchive2 = dump(baseArgs); 85 compare(baseArchive, baseArchive2, baseArchiveFile); 86 87 // (2) This will cause the archive to be relocated during dump time. We should 88 // still get the same bits. This simulates relocation that happens when 89 // Address Space Layout Randomization prevents the archive space to 90 // be mapped at the default location. 91 String relocatedArchive = dump(baseArgs, "-XX:+UnlockDiagnosticVMOptions", "-XX:ArchiveRelocationMode=1"); 92 compare(baseArchive, relocatedArchive, baseArchiveFile); 93 } 94 95 static int id = 0; 96 static String dump(ArrayList<String> args, String... more) throws Exception { 97 String logName = "SharedArchiveFile" + (id++); 98 String archiveName = logName + ".jsa"; 99 String mapName = logName + ".map"; 100 CDSOptions opts = (new CDSOptions()) 101 .addPrefix("-Xint") // Override any -Xmixed/-Xcomp flags from jtreg -vmoptions 102 .addPrefix("-Xlog:cds=debug,gc=debug") 103 .addPrefix("-Xlog:cds+map*=trace:file=" + mapName + ":none:filesize=0") 104 .setArchiveName(archiveName) 105 .addSuffix(args) 106 .addSuffix(more); 107 CDSTestUtils.createArchiveAndCheck(opts); 108 109 return logName; 110 } 111 112 static void compare(String file0, String file1, File archiveFile) throws Exception { 113 byte[] buff0 = new byte[4096]; 114 byte[] buff1 = new byte[4096]; 115 try (FileInputStream in0 = new FileInputStream(file0 + ".jsa"); 116 FileInputStream in1 = new FileInputStream(file1 + ".jsa")) { 117 int total = 0; 118 while (true) { 119 int n0 = read(in0, buff0); 120 int n1 = read(in1, buff1); 121 if (n0 != n1) { 122 throw new RuntimeException("File contents (file sizes?) are different after " + total + " bytes; n0 = " 123 + n0 + ", n1 = " + n1); 124 } 125 if (n0 == 0) { 126 System.out.println("File contents are the same: " + total + " bytes"); 127 break; 128 } 129 for (int i = 0; i < n0; i++) { 130 byte b0 = buff0[i]; 131 byte b1 = buff1[i]; 132 if (b0 != b1) { 133 // The checksums are stored in the header so it should be skipped 134 // since we want to see the first meaningful diff between the archives 135 if (total + i > HEADER_SIZE) { 136 print_diff(file0 + ".map", file1 + ".map", archiveFile, total + i); 137 throw new RuntimeException("File content different at byte #" + (total + i) + ", b0 = " + b0 + ", b1 = " + b1); 138 } 139 } 140 } 141 total += n0; 142 } 143 } 144 } 145 146 static int read(FileInputStream in, byte[] buff) throws IOException { 147 int total = 0; 148 while (total < buff.length) { 149 int n = in.read(buff, total, buff.length - total); 150 if (n <= 0) { 151 return total; 152 } 153 total += n; 154 } 155 156 return total; 157 } 158 159 // CDS map file doesn't print the alignment bytes so they need to be considered 160 // when mapping the byte number in the archive to the word in the map file 161 static int archiveByteToMapWord(File archiveFile, int location) throws Exception { 162 int totalSize = 0; 163 int word = location; 164 165 long len = HEADER_SIZE; 166 long aligned = CDSArchiveUtils.fileHeaderSizeAligned(archiveFile); 167 for (int i = 0; i < CDSArchiveUtils.num_regions(); i++) { 168 if (i != 0) { 169 len = CDSArchiveUtils.usedRegionSize(archiveFile, i); 170 aligned = CDSArchiveUtils.usedRegionSizeAligned(archiveFile, i); 171 } 172 totalSize += len; 173 if (location > totalSize) { 174 word -= (aligned - len - 16); 175 } 176 } 177 return word/8; 178 } 179 180 // Read the mapfile and print out the lines associated with the location 181 static void print_diff(String mapName0, String mapName1, File archiveFile, int location) throws Exception { 182 FileReader f0 = new FileReader(mapName0); 183 BufferedReader b0 = new BufferedReader(f0); 184 185 FileReader f1 = new FileReader(mapName1); 186 BufferedReader b1 = new BufferedReader(f1); 187 188 int word = archiveByteToMapWord(archiveFile, location); 189 int wordOffset = word % 4; // Each line in the map file prints four words 190 String region = ""; 191 192 // Skip header text and go to first line 193 for (int i = 0; i < HEADER_LEN; i++) { 194 b0.readLine(); 195 b1.readLine(); 196 } 197 198 int line_num = HEADER_LEN; 199 String s0 = ""; 200 String s1 = ""; 201 int count = 0; 202 203 // Store lines before and including the diff 204 ArrayDeque<String> prefix0 = new ArrayDeque<String>(); 205 ArrayDeque<String> prefix1 = new ArrayDeque<String>(); 206 207 // A line may contain 1-4 words so we iterate by word 208 do { 209 s0 = b0.readLine(); 210 s1 = b1.readLine(); 211 line_num++; 212 213 if (prefix0.size() >= NUM_LINES / 2 + 1) { 214 prefix0.removeFirst(); 215 prefix1.removeFirst(); 216 } 217 prefix0.addLast(s0); 218 prefix1.addLast(s1); 219 220 // Skip lines with headers when counting words e.g. 221 // [rw region 0x0000000800000000 - 0x00000008005a1f88 5906312 bytes] 222 // or 223 // 0x0000000800000b28: @@ TypeArrayU1 16 224 if (!s0.contains(": @@") && !s0.contains("bytes]")) { 225 int words = (s0.length() - LINE_OFFSET - 70) / 8; 226 count += words; 227 } else if (s0.contains("bytes]")) { 228 region = s0; 229 } 230 } while (count < word); 231 232 // Print the diff with the region name above it 233 System.out.println("[First diff: map file #1 (" + mapName0 + ")]"); 234 System.out.println(region); 235 String diff0 = print_diff_helper(b0, wordOffset, prefix0); 236 237 System.out.println("\n[First diff: map file #2 (" + mapName1 + ")]"); 238 System.out.println(region); 239 String diff1 = print_diff_helper(b1, wordOffset, prefix1); 240 241 System.out.printf("\nByte #%d at line #%d word #%d:\n", location, line_num, wordOffset); 242 System.out.printf("%s: %s\n%s: %s\n", mapName0, diff0, mapName1, diff1); 243 244 f0.close(); 245 f1.close(); 246 } 247 248 static String print_diff_helper(BufferedReader b, int wordOffset, ArrayDeque<String> prefix) throws Exception { 249 int start = LINE_OFFSET + WORD_LEN * wordOffset; 250 int end = start + WORD_LEN; 251 String line = prefix.getLast(); 252 String diff = line.substring(start, end); 253 254 // Print previous lines 255 for (String s : prefix) { 256 if (s.equals(line)) { 257 System.out.println(">" + s); 258 } else { 259 System.out.println(" " + s); 260 } 261 } 262 263 // Print extra lines 264 for (int i = 0; i < NUM_LINES / 2; i++) { 265 System.out.println(" " + b.readLine()); 266 } 267 return diff; 268 } 269 }