/* * Copyright (c) 2016, 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 Verify handling of SIGBUS on truncated mapped files * @modules java.base/jdk.internal.misc * @library /testlibrary * @run main/othervm -Diters=100 -Xint MappedTruncated * @run main/othervm -Diters=20000 MappedTruncated */ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import static java.nio.channels.FileChannel.*; import jdk.internal.misc.Unsafe; import jdk.test.lib.Platform; public class MappedTruncated { private static final boolean DEBUG = Boolean.getBoolean("MappedTruncated.DEBUG"); private static final int ITERS = Integer.getInteger("iters", 1); private static final int FILE_SIZE; private static final int TRUNCATED_SIZE; private static final int THE_OFFSET; private static final Unsafe U; private static final Method mappingOffset; private static final Method mappingAddress; // Volatile to further trick the compiler into really performing the memory accesses public volatile File f; public volatile MappedByteBuffer buf; public volatile FileChannel fc; public volatile long baseAddress; /** * Helper class to trick the compiler into not eliminating memory accesses */ static class VolatileTrap { volatile boolean z; volatile byte b; volatile char c; volatile short s; volatile int i; volatile float f; volatile long l; volatile double d; volatile Object o; } static VolatileTrap vt = new VolatileTrap(); static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); U = (Unsafe) f.get(null); // Page size here is relevant because the kernel only // raises SIGBUS on memory accesses on *pages* which are // not mapped. That is, if the file is truncated to X // bytes, even if a memory access is made at [X,X+n) // (outside of the new file limit), that access may still // succeed (not raise a SIGBUS) if the data is on the same // page as X (or really, X-1). FILE_SIZE = 4 * U.pageSize(); TRUNCATED_SIZE = 2 * U.pageSize(); THE_OFFSET = TRUNCATED_SIZE; // Note: this is ugly: in order to test the raw Unsafe // primitives the buffer underlying the Direct buffer // needs to be extracted, and since the actual address // isn't stored anywhere it needs to be calculated in the // exact same way as in mappingOffset = MappedByteBuffer.class.getDeclaredMethod("mappingOffset"); mappingOffset.setAccessible(true); mappingAddress = MappedByteBuffer.class.getDeclaredMethod("mappingAddress", new Class[] { long.class }); mappingAddress.setAccessible(true); } catch (Exception e) { throw new RuntimeException(e); } } private static void debug(String s) { if (DEBUG) { System.err.println(s); } } public MappedTruncated(File f) throws Exception { this.f = f; debug("Memory mapping file data"); this.fc = new RandomAccessFile(f, "rw").getChannel(); this.buf = fc.map(FileChannel.MapMode.READ_WRITE, 0, FILE_SIZE); this.baseAddress = getBufferAddr(buf); debug("Direct buffer is at address: 0x" + Long.toHexString(baseAddress)); } /** * Create a file suitable for memory mapping * * @param size size of file to create */ private static File createFile(int size) throws IOException { debug("Creating file of length " + FILE_SIZE); File f = File.createTempFile("memorymapped", ".bin"); byte[] zeros = new byte[size]; FileOutputStream fos = new FileOutputStream(f); fos.write(zeros); fos.close(); return f; } /** * Extract (native) base address of MappedByteBuffer buffer * * @param mem the MappedByteBuffer to extract the base address from * * @return the native base address of the buffer */ private long getBufferAddr(MappedByteBuffer mem) throws Exception { long offset = (long)mappingOffset.invoke(mem); return (long)mappingAddress.invoke(mem, offset); } /** * Ensure that any pending asynchronous exceptions in the thread are processed/thrown */ private static void forcePendingAsyncExceptionProcessing() { // Any JNI call here will do - the VM processes pending async exceptions on // returning from JNI. Thread.yield(); } /** * Perform an operation, optionally verifying/ignoring thrown InternalError * * @param ignoreIE true if the operation is expected to throw an InternalError * @param r the operation to perform * * @throws Exception if ignoreIE is true, and the operation did *not* throw an InternalError */ private void doAccess(boolean ignoreIE, Runnable r) throws Exception { if (ignoreIE) { try { doAccess(false, r); throw new Exception("MappedByteBuffer operation unexpectedly succeeded"); } catch (InternalError e) { // IE thrown as expected, ignore } } else { r.run(); // The operation may have raised a pending async // exception, make sure it's actually thrown forcePendingAsyncExceptionProcessing(); } } /** * Perform various memory accesses * * @param ignoreIE true if the operations are expected to throw InternalErrors * @param offset the offset (from baseAddress) at which to perform the memory access * * @throws Exception if ignoreIE is true, and the operation did *not* throw an InternalError */ private void doAccesses(boolean ignoreIE, int offset) throws Exception { // First a bunch of MappedBuffer accesses doAccess(ignoreIE, () -> { vt.b = buf.get(offset); }); doAccess(ignoreIE, () -> { vt.s = buf.getShort(offset); }); doAccess(ignoreIE, () -> { vt.c = buf.getChar(offset); }); doAccess(ignoreIE, () -> { vt.i = buf.getInt(offset); }); doAccess(ignoreIE, () -> { vt.f = buf.getFloat(offset); }); doAccess(ignoreIE, () -> { vt.l = buf.getLong(offset); }); doAccess(ignoreIE, () -> { vt.d = buf.getDouble(offset); }); doAccess(ignoreIE, () -> { buf.put(offset, (byte)42); }); doAccess(ignoreIE, () -> { buf.putShort(offset, (short)42); }); doAccess(ignoreIE, () -> { buf.putChar(offset, (char)42); }); doAccess(ignoreIE, () -> { buf.putInt(offset, 42); }); doAccess(ignoreIE, () -> { buf.putFloat(offset, 42); }); doAccess(ignoreIE, () -> { buf.putLong(offset, 42); }); doAccess(ignoreIE, () -> { buf.putDouble(offset, 42); }); // Now for direct/raw Unsafe accesses long addr = baseAddress + offset; doAccess(ignoreIE, () -> { vt.z = U.getBoolean(null, addr); }); doAccess(ignoreIE, () -> { vt.b = U.getByte(null, addr); }); doAccess(ignoreIE, () -> { vt.s = U.getShort(null, addr); }); doAccess(ignoreIE, () -> { vt.c = U.getChar(null, addr); }); doAccess(ignoreIE, () -> { vt.i = U.getInt(null, addr); }); doAccess(ignoreIE, () -> { vt.f = U.getFloat(null, addr); }); doAccess(ignoreIE, () -> { vt.l = U.getLong(null, addr); }); doAccess(ignoreIE, () -> { vt.d = U.getDouble(null, addr); }); doAccess(ignoreIE, () -> { vt.l = U.getAddress(null, addr); }); doAccess(ignoreIE, () -> { U.putBoolean(null, addr, true); }); doAccess(ignoreIE, () -> { U.putByte(null, addr, (byte)42); }); doAccess(ignoreIE, () -> { U.putShort(null, addr, (short)42); }); doAccess(ignoreIE, () -> { U.putChar(null, addr, (char)42); }); doAccess(ignoreIE, () -> { U.putInt(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putFloat(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putLong(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putDouble(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putAddress(null, addr, 42); }); doAccess(ignoreIE, () -> { vt.z = U.getBooleanVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.b = U.getByteVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.s = U.getShortVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.c = U.getCharVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.i = U.getIntVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.f = U.getFloatVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.l = U.getLongVolatile(null, addr); }); doAccess(ignoreIE, () -> { vt.d = U.getDoubleVolatile(null, addr); }); doAccess(ignoreIE, () -> { U.putBooleanVolatile(null, addr, true); }); doAccess(ignoreIE, () -> { U.putByteVolatile(null, addr, (byte)42); }); doAccess(ignoreIE, () -> { U.putShortVolatile(null, addr, (short)42); }); doAccess(ignoreIE, () -> { U.putCharVolatile(null, addr, (char)42); }); doAccess(ignoreIE, () -> { U.putIntVolatile(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putFloatVolatile(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putLongVolatile(null, addr, 42); }); doAccess(ignoreIE, () -> { U.putDoubleVolatile(null, addr, 42); }); } /** * Test memory accesses in truncated, memory mapped file * * The method will first perform some sanity checks, then truncate * the file and do the "real" testing of accesses in the truncated * part of a memory mapped file. */ private void testAccesses() throws Exception { debug("Performing sanity checks"); doAccesses(false, THE_OFFSET); debug("Truncating file to " + TRUNCATED_SIZE + " bytes"); if (Platform.isWindows()) { // Windows will throw an exception if trying to truncate a mapped file, // so this test only checks that the exception is thrown correctly try { fc.truncate(TRUNCATED_SIZE); } catch (IOException e) { return; } throw new Exception("Truncate unexpectedly succeeded"); } else { fc.truncate(TRUNCATED_SIZE); } debug("* Entering main loop"); for (int i = 0; i < ITERS; i++) { doAccesses(true, THE_OFFSET); } debug("* Main loop done, exiting..."); } public static void main(String[] args) throws Exception { File f = null; try { f = createFile(FILE_SIZE); MappedTruncated mt = new MappedTruncated(f); mt.testAccesses(); } finally { if (f != null) { f.delete(); } } } }