1 /* 2 * Copyright (c) 2016, 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 import jdk.internal.misc.Unsafe; 25 import java.lang.reflect.Field; 26 27 /* 28 * @test 29 * @summary Test Unsafe.copyMemory 30 * @modules java.base/jdk.internal.misc 31 */ 32 public class CopyMemory { 33 private static final boolean DEBUG = Boolean.getBoolean("CopyMemory.DEBUG"); 34 35 public static final long KB = 1024; 36 public static final long MB = KB * 1024; 37 public static final long GB = MB * 1024; 38 39 private static final Unsafe UNSAFE; 40 private static final int SMALL_COPY_SIZE = 32; 41 private static final int BASE_ALIGNMENT = 16; 42 43 static { 44 try { 45 Field f = jdk.internal.misc.Unsafe.class.getDeclaredField("theUnsafe"); 46 f.setAccessible(true); 47 UNSAFE = (jdk.internal.misc.Unsafe) f.get(null); 48 } catch (Exception e) { 49 throw new RuntimeException("Unable to get Unsafe instance.", e); 50 } 51 } 52 53 private static long alignDown(long value, long alignment) { 54 return value & ~(alignment - 1); 55 } 56 57 private static long alignUp(long value, long alignment) { 58 return (value + alignment - 1) & ~(alignment - 1); 59 } 60 61 private static boolean isAligned(long value, long alignment) { 62 return value == alignDown(value, alignment); 63 } 64 65 private CopyMemory() { 66 } 67 68 /** 69 * Generate verification data for a given offset 70 * 71 * The verification data is used to verify that the correct bytes 72 * have indeed been copied and byte swapped. 73 * 74 * The data is generated based on the offset (in bytes) into the 75 * source buffer. For a native buffer the offset is relative to 76 * the base pointer. For a heap array it is relative to the 77 * address of the first array element. 78 * 79 * This method will return the result of doing an elementSize byte 80 * read starting at offset (in bytes). 81 * 82 * @param offset offset into buffer 83 * @param elemSize size (in bytes) of the element 84 * 85 * @return the verification data, only the least significant 86 * elemSize*8 bits are set, zero extended 87 */ 88 private long getVerificationDataForOffset(long offset, long elemSize) { 89 byte[] bytes = new byte[(int)elemSize]; 90 91 for (long i = 0; i < elemSize; i++) { 92 bytes[(int)i] = (byte)(offset + i); 93 } 94 95 long o = UNSAFE.arrayBaseOffset(byte[].class); 96 97 switch ((int)elemSize) { 98 case 1: return Byte.toUnsignedLong(UNSAFE.getByte(bytes, o)); 99 case 2: return Short.toUnsignedLong(UNSAFE.getShortUnaligned(bytes, o)); 100 case 4: return Integer.toUnsignedLong(UNSAFE.getIntUnaligned(bytes, o)); 101 case 8: return UNSAFE.getLongUnaligned(bytes, o); 102 default: throw new IllegalArgumentException("Invalid element size: " + elemSize); 103 } 104 } 105 106 /** 107 * Verify byte swapped data 108 * 109 * @param ptr the data to verify 110 * @param srcOffset the srcOffset (in bytes) from which the copy started, 111 * used as key to regenerate the verification data 112 * @param dstOffset the offset (in bytes) in the array at which to start 113 * the verification, relative to the first element in the array 114 * @param size size (in bytes) of data to to verify 115 * @param elemSize size (in bytes) of the individual array elements 116 * 117 * @throws RuntimeException if an error is found 118 */ 119 private void verifySwappedData(GenericPointer ptr, long srcOffset, long dstOffset, long size, long elemSize) { 120 for (long offset = 0; offset < size; offset += elemSize) { 121 long expectedUnswapped = getVerificationDataForOffset(srcOffset + offset, elemSize); 122 long expected = byteSwap(expectedUnswapped, elemSize); 123 124 long actual = getArrayElem(ptr, dstOffset + offset, elemSize); 125 126 if (expected != actual) { 127 throw new RuntimeException("srcOffset: 0x" + Long.toHexString(srcOffset) + 128 " dstOffset: 0x" + Long.toHexString(dstOffset) + 129 " size: 0x" + Long.toHexString(size) + 130 " offset: 0x" + Long.toHexString(offset) + 131 " expectedUnswapped: 0x" + Long.toHexString(expectedUnswapped) + 132 " expected: 0x" + Long.toHexString(expected) + 133 " != actual: 0x" + Long.toHexString(actual)); 134 } 135 } 136 } 137 138 /** 139 * Initialize an array with verification friendly data 140 * 141 * @param ptr pointer to the data to initialize 142 * @param size size (in bytes) of the data 143 * @param elemSize size (in bytes) of the individual elements 144 */ 145 private void initVerificationData(GenericPointer ptr, long size, long elemSize) { 146 for (long offset = 0; offset < size; offset++) { 147 byte data = (byte)getVerificationDataForOffset(offset, 1); 148 149 if (ptr.isOnHeap()) { 150 UNSAFE.putByte(ptr.getObject(), ptr.getOffset() + offset, data); 151 } else { 152 UNSAFE.putByte(ptr.getOffset() + offset, data); 153 } 154 } 155 } 156 157 /** 158 * Allocate a primitive array 159 * 160 * @param size size (in bytes) of all the array elements (elemSize * length) 161 * @param elemSize the size of the array elements 162 * 163 * @return a newly allocated primitive array 164 */ 165 Object allocArray(long size, long elemSize) { 166 int length = (int)(size / elemSize); 167 168 switch ((int)elemSize) { 169 case 1: return new byte[length]; 170 case 2: return new short[length]; 171 case 4: return new int[length]; 172 case 8: return new long[length]; 173 default: 174 throw new IllegalArgumentException("Invalid element size: " + elemSize); 175 } 176 } 177 178 /** 179 * Get the value of a primitive array entry 180 * 181 * @param ptr pointer to the data 182 * @param offset offset (in bytes) of the array element, relative to the first element in the array 183 * 184 * @return the array element, as an unsigned long 185 */ 186 private long getArrayElem(GenericPointer ptr, long offset, long elemSize) { 187 if (ptr.isOnHeap()) { 188 Object o = ptr.getObject(); 189 int index = (int)(offset / elemSize); 190 191 if (o instanceof short[]) { 192 short[] arr = (short[])o; 193 return Short.toUnsignedLong(arr[index]); 194 } else if (o instanceof int[]) { 195 int[] arr = (int[])o; 196 return Integer.toUnsignedLong(arr[index]); 197 } else if (o instanceof long[]) { 198 long[] arr = (long[])o; 199 return arr[index]; 200 } else { 201 throw new IllegalArgumentException("Invalid object type: " + o.getClass().getName()); 202 } 203 } else { 204 long addr = ptr.getOffset() + offset; 205 206 switch ((int)elemSize) { 207 case 1: return Byte.toUnsignedLong(UNSAFE.getByte(addr)); 208 case 2: return Short.toUnsignedLong(UNSAFE.getShortUnaligned(null, addr)); 209 case 4: return Integer.toUnsignedLong(UNSAFE.getIntUnaligned(null, addr)); 210 case 8: return UNSAFE.getLongUnaligned(null, addr); 211 default: throw new IllegalArgumentException("Invalid element size: " + elemSize); 212 } 213 } 214 } 215 216 private void putValue(long addr, long elemSize, long value) { 217 switch ((int)elemSize) { 218 case 1: UNSAFE.putByte(addr, (byte)value); break; 219 case 2: UNSAFE.putShortUnaligned(null, addr, (short)value); break; 220 case 4: UNSAFE.putIntUnaligned(null, addr, (int)value); break; 221 case 8: UNSAFE.putLongUnaligned(null, addr, value); break; 222 default: throw new IllegalArgumentException("Invalid element size: " + elemSize); 223 } 224 } 225 226 /** 227 * Get the size of the elements for an array 228 * 229 * @param o a primitive heap array 230 * 231 * @return the size (in bytes) of the individual array elements 232 */ 233 private long getArrayElemSize(Object o) { 234 if (o instanceof short[]) { 235 return 2; 236 } else if (o instanceof int[]) { 237 return 4; 238 } else if (o instanceof long[]) { 239 return 8; 240 } else { 241 throw new IllegalArgumentException("Invalid object type: " + o.getClass().getName()); 242 } 243 } 244 245 /** 246 * Byte swap a value 247 * 248 * @param value the value to swap, only the bytes*8 least significant bits are used 249 * @param size size (in bytes) of the value 250 * 251 * @return the byte swapped value in the bytes*8 least significant bits 252 */ 253 private long byteSwap(long value, long size) { 254 switch ((int)size) { 255 case 2: return Short.toUnsignedLong(Short.reverseBytes((short)value)); 256 case 4: return Integer.toUnsignedLong(Integer.reverseBytes((int)value)); 257 case 8: return Long.reverseBytes(value); 258 default: throw new IllegalArgumentException("Invalid element size: " + size); 259 } 260 } 261 262 /** 263 * Verify data which has *not* been byte swapped 264 * 265 * @param ptr the data to verify 266 * @param startOffset the offset (in bytes) at which to start the verification 267 * @param size size (in bytes) of the data to verify 268 * 269 * @throws RuntimeException if an error is found 270 */ 271 private void verifyUnswappedData(GenericPointer ptr, long startOffset, long srcOffset, long size) { 272 for (long i = 0; i < size; i++) { 273 byte expected = (byte)getVerificationDataForOffset(srcOffset + i, 1); 274 275 byte actual; 276 if (ptr.isOnHeap()) { 277 actual = UNSAFE.getByte(ptr.getObject(), ptr.getOffset() + startOffset + i); 278 } else { 279 actual = UNSAFE.getByte(ptr.getOffset() + startOffset + i); 280 } 281 282 if (expected != actual) { 283 throw new RuntimeException("startOffset: 0x" + Long.toHexString(startOffset) + 284 " srcOffset: 0x" + Long.toHexString(srcOffset) + 285 " size: 0x" + Long.toHexString(size) + 286 " i: 0x" + Long.toHexString(i) + 287 " expected: 0x" + Long.toHexString(expected) + 288 " != actual: 0x" + Long.toHexString(actual)); 289 } 290 } 291 } 292 293 294 /** 295 * Copy and byte swap data from the source to the destination 296 * 297 * This method will pre-populate the whole source and destination 298 * buffers with verification friendly data. It will then use 299 * copypMemory to fill part of the destination buffer with 300 * data from the source. Some space (padding) will be 301 * left before and after the data in the destination buffer, which 302 * should not be touched/overwritten by the copy call. 303 * 304 * Note: Both source and destination buffers will be overwritten! 305 * 306 * @param src source buffer to copy from 307 * @param srcOffset the offset (in bytes) in the source buffer, relative to 308 * the first array element, at which to start reading data 309 * @param dst destination buffer to copy to 310 * @param dstOffset the offset (in bytes) in the destination 311 * buffer, relative to the first array element, at which to 312 * start writing data 313 * @param bufSize the size (in bytes) of the src and dst arrays 314 * @param copyBytes the size (in bytes) of the copy to perform, 315 * must be a multiple of elemSize 316 * 317 * @throws RuntimeException if an error is found 318 */ 319 private void testCopy(GenericPointer src, long srcOffset, 320 GenericPointer dst, long dstOffset, 321 long bufSize, long copyBytes) { 322 if (srcOffset + copyBytes > bufSize) { 323 throw new IllegalArgumentException( 324 "srcOffset (" + srcOffset + ") + copyBytes (" + copyBytes + ") > bufSize (" + bufSize + ")"); 325 } 326 if (dstOffset + copyBytes > bufSize) { 327 throw new IllegalArgumentException( 328 "dstOffset (" + dstOffset + ") + copyBytes (" + copyBytes + ") > bufSize (" + bufSize + ")"); 329 } 330 331 // Initialize the whole source buffer with a verification friendly pattern (no 0x00 bytes) 332 initVerificationData(src, bufSize, 1); 333 if (!src.equals(dst)) { 334 initVerificationData(dst, bufSize, 1); 335 } 336 337 if (DEBUG) { 338 System.out.println("===before==="); 339 for (int offset = 0; offset < bufSize; offset++) { 340 long srcValue = getArrayElem(src, offset, 1); 341 long dstValue = getArrayElem(dst, offset, 1); 342 343 System.out.println("offs=0x" + Long.toHexString(Integer.toUnsignedLong(offset)) + 344 " src=0x" + Long.toHexString(srcValue) + 345 " dst=0x" + Long.toHexString(dstValue)); 346 } 347 } 348 349 // Copy & swap data into the middle of the destination buffer 350 UNSAFE.copyMemory(src.getObject(), 351 src.getOffset() + srcOffset, 352 dst.getObject(), 353 dst.getOffset() + dstOffset, 354 copyBytes); 355 356 if (DEBUG) { 357 System.out.println("===after==="); 358 for (int offset = 0; offset < bufSize; offset++) { 359 long srcValue = getArrayElem(src, offset, 1); 360 long dstValue = getArrayElem(dst, offset, 1); 361 362 System.out.println("offs=0x" + Long.toHexString(Integer.toUnsignedLong(offset)) + 363 " src=0x" + Long.toHexString(srcValue) + 364 " dst=0x" + Long.toHexString(dstValue)); 365 } 366 } 367 368 // Verify the the front padding is unchanged 369 verifyUnswappedData(dst, 0, 0, dstOffset); 370 371 // Verify copied data 372 verifyUnswappedData(dst, dstOffset, srcOffset, copyBytes); 373 374 // Verify that the back back padding is unchanged 375 long frontAndDataBytes = dstOffset + copyBytes; 376 long trailingBytes = bufSize - frontAndDataBytes; 377 verifyUnswappedData(dst, frontAndDataBytes, frontAndDataBytes, trailingBytes); 378 } 379 380 /** 381 * Test various configurations copying from one buffer to the other 382 * 383 * @param src the source buffer to copy from 384 * @param dst the destination buffer to copy to 385 * @param size size (in bytes) of the buffers 386 * @param elemSize size (in bytes) of the individual elements 387 * 388 * @throws RuntimeException if an error is found 389 */ 390 public void testBufferPair(GenericPointer src, GenericPointer dst, long size, long elemSize) { 391 // offset in source from which to start reading data 392 for (long srcOffset = 0; srcOffset < size; srcOffset += (src.isOnHeap() ? elemSize : 1)) { 393 394 // offset in destination at which to start writing data 395 for (int dstOffset = 0; dstOffset < size; dstOffset += (dst.isOnHeap() ? elemSize : 1)) { 396 397 // number of bytes to copy 398 long maxCopyBytes = Math.min(size - srcOffset, size - dstOffset); 399 for (long copyBytes = 0; copyBytes < maxCopyBytes; copyBytes += elemSize) { 400 try { 401 testCopy(src, srcOffset, dst, dstOffset, size, copyBytes); 402 } catch (RuntimeException e) { 403 // Wrap the exception in another exception to catch the relevant configuration data 404 throw new RuntimeException("testBufferPair: " + 405 "src=" + src + 406 " dst=" + dst + 407 " elemSize=0x" + Long.toHexString(elemSize) + 408 " copyBytes=0x" + Long.toHexString(copyBytes) + 409 " srcOffset=0x" + Long.toHexString(srcOffset) + 410 " dstOffset=0x" + Long.toHexString(dstOffset), 411 e); 412 } 413 } 414 } 415 } 416 } 417 418 /** 419 * Test copying between various permutations of buffers 420 * 421 * @param buffers buffers to permute (src x dst) 422 * @param size size (in bytes) of buffers 423 * @param elemSize size (in bytes) of individual elements 424 * 425 * @throws RuntimeException if an error is found 426 */ 427 public void testPermuteBuffers(GenericPointer[] buffers, long size, long elemSize) { 428 for (int srcIndex = 0; srcIndex < buffers.length; srcIndex++) { 429 for (int dstIndex = 0; dstIndex < buffers.length; dstIndex++) { 430 testBufferPair(buffers[srcIndex], buffers[dstIndex], size, elemSize); 431 } 432 } 433 } 434 435 /** 436 * Test copying of a specific element size 437 * 438 * @param size size (in bytes) of buffers to allocate 439 * @param elemSize size (in bytes) of individual elements 440 * 441 * @throws RuntimeException if an error is found 442 */ 443 private void testElemSize(long size, long elemSize) { 444 long buf1Raw = 0; 445 long buf2Raw = 0; 446 447 try { 448 buf1Raw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT); 449 long buf1 = alignUp(buf1Raw, BASE_ALIGNMENT); 450 451 buf2Raw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT); 452 long buf2 = alignUp(buf2Raw, BASE_ALIGNMENT); 453 454 GenericPointer[] buffers = { 455 new GenericPointer(buf1), 456 new GenericPointer(buf2), 457 new GenericPointer(allocArray(size, elemSize)), 458 new GenericPointer(allocArray(size, elemSize)) 459 }; 460 461 testPermuteBuffers(buffers, size, elemSize); 462 } finally { 463 if (buf1Raw != 0) { 464 UNSAFE.freeMemory(buf1Raw); 465 } 466 if (buf2Raw != 0) { 467 UNSAFE.freeMemory(buf2Raw); 468 } 469 } 470 } 471 472 /** 473 * Verify that small copies work 474 */ 475 private void testSmallCopy() { 476 int smallBufSize = SMALL_COPY_SIZE; 477 478 testElemSize(smallBufSize, 1); 479 } 480 481 482 /** 483 * Verify that large copies work 484 */ 485 private void testLargeCopy() { 486 long size = 2 * GB + 8; 487 long bufRaw = 0; 488 489 // Check that a large native copy succeeds 490 try { 491 try { 492 bufRaw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT); 493 } catch (OutOfMemoryError e) { 494 // Accept failure, skip test 495 return; 496 } 497 498 long buf = alignUp(bufRaw, BASE_ALIGNMENT); 499 500 UNSAFE.copyMemory(null, buf, null, buf, size); 501 } catch (Exception e) { 502 throw new RuntimeException("copyMemory of large buffer failed"); 503 } finally { 504 if (bufRaw != 0) { 505 UNSAFE.freeMemory(bufRaw); 506 } 507 } 508 } 509 510 /** 511 * Run positive tests 512 * 513 * @throws RuntimeException if an error is found 514 */ 515 private void testPositive() { 516 testSmallCopy(); 517 testLargeCopy(); 518 } 519 520 /** 521 * Run negative tests, testing corner cases and the various exceptions 522 * 523 * @throws RuntimeException if an error is found 524 */ 525 private void testNegative() { 526 long bufRaw = 0; 527 528 try { 529 bufRaw = UNSAFE.allocateMemory(1024); 530 long buf = alignUp(bufRaw, BASE_ALIGNMENT); 531 short[] arr = new short[16]; 532 533 // Check illegal sizes 534 System.out.println("Testing negative size"); 535 try { 536 UNSAFE.copyMemory(null, buf, null, buf, -1); 537 throw new RuntimeException("copyMemory failed to throw IAE for size=-1"); 538 } catch (IllegalArgumentException e) { 539 // good 540 } 541 542 System.out.println("Testing negative srcOffset"); 543 try { 544 // Check that negative srcOffset throws an IAE 545 UNSAFE.copyMemory(arr, -1, arr, UNSAFE.arrayBaseOffset(arr.getClass()), 16); 546 throw new RuntimeException("copyMemory failed to throw IAE for srcOffset=-1"); 547 } catch (IllegalArgumentException e) { 548 // good 549 } 550 551 System.out.println("Testing negative destOffset"); 552 try { 553 // Check that negative dstOffset throws an IAE 554 UNSAFE.copyMemory(arr, UNSAFE.arrayBaseOffset(arr.getClass()), arr, -1, 16); 555 throw new RuntimeException("copyMemory failed to throw IAE for destOffset=-1"); 556 } catch (IllegalArgumentException e) { 557 // good 558 } 559 560 System.out.println("Testing reference array"); 561 try { 562 // Check that a reference array destination throws IAE 563 UNSAFE.copyMemory(null, buf, new Object[16], UNSAFE.arrayBaseOffset(Object[].class), 16); 564 throw new RuntimeException("copyMemory failed to throw IAE"); 565 } catch (IllegalArgumentException e) { 566 // good 567 } 568 569 // Check that invalid source & dest pointers throw IAEs (only relevant on 32-bit platforms) 570 if (UNSAFE.addressSize() == 4) { 571 long invalidPtr = (long)1 << 35; // Pick a random bit in upper 32 bits 572 573 try { 574 // Check that an invalid (not 32-bit clean) source pointer throws IAE 575 UNSAFE.copyMemory(null, invalidPtr, null, buf, 16); 576 throw new RuntimeException("copyMemory failed to throw IAE for srcOffset 0x" + 577 Long.toHexString(invalidPtr)); 578 } catch (IllegalArgumentException e) { 579 // good 580 } 581 582 try { 583 // Check that an invalid (not 32-bit clean) source pointer throws IAE 584 UNSAFE.copyMemory(null, buf, null, invalidPtr, 16); 585 throw new RuntimeException("copyMemory failed to throw IAE for destOffset 0x" + 586 Long.toHexString(invalidPtr)); 587 } catch (IllegalArgumentException e) { 588 // good 589 } 590 } 591 } finally { 592 if (bufRaw != 0) { 593 UNSAFE.freeMemory(bufRaw); 594 } 595 } 596 } 597 598 /** 599 * Run all tests 600 * 601 * @throws RuntimeException if an error is found 602 */ 603 private void test() { 604 testPositive(); 605 testNegative(); 606 } 607 608 public static void main(String[] args) { 609 CopyMemory cs = new CopyMemory(); 610 cs.test(); 611 } 612 613 /** 614 * Helper class to represent a "pointer" - either a heap array or 615 * a pointer to a native buffer. 616 * 617 * In the case of a native pointer, the Object is null and the offset is 618 * the absolute address of the native buffer. 619 * 620 * In the case of a heap object, the Object is a primitive array, and 621 * the offset will be set to the base offset to the first element, meaning 622 * the object and the offset together form a double-register pointer. 623 */ 624 static class GenericPointer { 625 private final Object o; 626 private final long offset; 627 628 private GenericPointer(Object o, long offset) { 629 this.o = o; 630 this.offset = offset; 631 } 632 633 public String toString() { 634 return "GenericPointer(o={" + o + "}, offset=0x" + Long.toHexString(offset) + ")"; 635 } 636 637 public boolean equals(Object other) { 638 if (!(other instanceof GenericPointer)) { 639 return false; 640 } 641 642 GenericPointer otherp = (GenericPointer)other; 643 644 return o == otherp.o && offset == otherp.offset; 645 } 646 647 GenericPointer(Object o) { 648 this(o, UNSAFE.arrayBaseOffset(o.getClass())); 649 } 650 651 GenericPointer(long offset) { 652 this(null, offset); 653 } 654 655 public boolean isOnHeap() { 656 return o != null; 657 } 658 659 public Object getObject() { 660 return o; 661 } 662 663 public long getOffset() { 664 return offset; 665 } 666 } 667 }