1 /* 2 * Copyright (c) 2018, 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.event.io; 27 28 import java.util.Arrays; 29 import java.util.Set; 30 import java.util.HashSet; 31 import java.io.File; 32 import java.security.ProtectionDomain; 33 import java.lang.instrument.ClassFileTransformer; 34 import java.lang.instrument.Instrumentation; 35 import java.lang.instrument.IllegalClassFormatException; 36 37 import jdk.internal.org.objectweb.asm.ClassReader; 38 import jdk.internal.org.objectweb.asm.ClassVisitor; 39 import jdk.internal.org.objectweb.asm.MethodVisitor; 40 import jdk.internal.org.objectweb.asm.ClassWriter; 41 import jdk.internal.org.objectweb.asm.Opcodes; 42 import jdk.internal.org.objectweb.asm.Type; 43 import jdk.testlibrary.OutputAnalyzer; 44 import jdk.testlibrary.ProcessTools; 45 import jdk.testlibrary.jfr.IOEvent; 46 import jdk.testlibrary.jfr.InstrumentationCallback; 47 48 /* 49 * @test 50 * @summary Test that will instrument the same classes that JFR will also instrument. 51 * @key jfr 52 * 53 * @library /lib/testlibrary 54 * @modules java.base/jdk.internal.org.objectweb.asm 55 * java.instrument 56 * jdk.jartool/sun.tools.jar 57 * jdk.jfr 58 * 59 * @run main/othervm jdk.jfr.event.io.TestInstrumentation 60 */ 61 62 // Test that will instrument the same classes that JFR will also instrument. 63 // 64 // The methods that will be instrumented, for example java.io.RandomAccessFile.write, 65 // will add the following code at the start of the method: 66 // InstrumentationCallback.callback("<classname>::<methodname>"); 67 // 68 // The class InstrumentationCallback will log all keys added by the callback() function. 69 // 70 // With this instrumentation in place, we will run some existing jfr.io tests 71 // to verify that our instrumentation has not broken the JFR instrumentation. 72 // 73 // After the tests have been run, we verify that the callback() function have been 74 // called from all instrumented classes and methods. This will verify that JFR has not 75 // broken our instrumentation. 76 // 77 // To use instrumentation, the test must be run in a new java process with 78 // the -javaagent option. 79 // We must also create two jars: 80 // TestInstrumentation.jar: The javaagent for the instrumentation. 81 // InstrumentationCallback.jar: This is a separate jar with the instrumentation 82 // callback() function. It is in a separate jar because it must be added to 83 // the bootclasspath to be called from java.io classes. 84 // 85 // The test contains 3 parts: 86 // Setup part that will create jars and launch the new test instance. 87 // Agent part that contains the instrumentation code. 88 // The actual test part is in the TestMain class. 89 // 90 public class TestInstrumentation implements ClassFileTransformer { 91 92 private static Instrumentation instrumentation = null; 93 private static TestInstrumentation testTransformer = null; 94 95 // All methods that will be instrumented. 96 private static final String[] instrMethodKeys = { 97 "java/io/RandomAccessFile::seek::(J)V", 98 "java/io/RandomAccessFile::read::()I", 99 "java/io/RandomAccessFile::read::([B)I", 100 "java/io/RandomAccessFile::write::([B)V", 101 "java/io/RandomAccessFile::write::(I)V", 102 "java/io/RandomAccessFile::close::()V", 103 "java/io/FileInputStream::read::([BII)I", 104 "java/io/FileInputStream::read::([B)I", 105 "java/io/FileInputStream::read::()I", 106 "java/io/FileOutputStream::write::(I)V", 107 "java/io/FileOutputStream::write::([B)V", 108 "java/io/FileOutputStream::write::([BII)V", 109 "java/net/SocketInputStream::read::()I", 110 "java/net/SocketInputStream::read::([B)I", 111 "java/net/SocketInputStream::read::([BII)I", 112 "java/net/SocketInputStream::close::()V", 113 "java/net/SocketOutputStream::write::(I)V", 114 "java/net/SocketOutputStream::write::([B)V", 115 "java/net/SocketOutputStream::write::([BII)V", 116 "java/net/SocketOutputStream::close::()V", 117 "java/nio/channels/FileChannel::read::([Ljava/nio/ByteBuffer;)J", 118 "java/nio/channels/FileChannel::write::([Ljava/nio/ByteBuffer;)J", 119 "java/nio/channels/SocketChannel::open::()Ljava/nio/channels/SocketChannel;", 120 "java/nio/channels/SocketChannel::open::(Ljava/net/SocketAddress;)Ljava/nio/channels/SocketChannel;", 121 "java/nio/channels/SocketChannel::read::([Ljava/nio/ByteBuffer;)J", 122 "java/nio/channels/SocketChannel::write::([Ljava/nio/ByteBuffer;)J", 123 "sun/nio/ch/FileChannelImpl::read::(Ljava/nio/ByteBuffer;)I", 124 "sun/nio/ch/FileChannelImpl::write::(Ljava/nio/ByteBuffer;)I", 125 }; 126 127 private static String getInstrMethodKey(String className, String methodName, String signature) { 128 // This key is used to identify a class and method. It is sent to callback(key) 129 return className + "::" + methodName + "::" + signature; 130 } 131 132 private static String getClassFromMethodKey(String methodKey) { 133 return methodKey.split("::")[0]; 134 } 135 136 // Set of all classes targeted for instrumentation. 137 private static Set<String> instrClassesTarget = null; 138 139 // Set of all classes where instrumentation has been completed. 140 private static Set<String> instrClassesDone = null; 141 142 static { 143 // Split class names from InstrMethodKeys. 144 instrClassesTarget = new HashSet<String>(); 145 instrClassesDone = new HashSet<String>(); 146 for (String s : instrMethodKeys) { 147 String className = getClassFromMethodKey(s); 148 instrClassesTarget.add(className); 149 } 150 } 151 152 private static void log(String msg) { 153 System.out.println("TestTransformation: " + msg); 154 } 155 156 157 //////////////////////////////////////////////////////////////////// 158 // This is the actual test part. 159 // A batch of jfr io tests will be run twice with a 160 // retransfromClasses() in between. After each test batch we verify 161 // that all callbacks have been called. 162 //////////////////////////////////////////////////////////////////// 163 164 public static class TestMain { 165 166 private enum TransformStatus { Transformed, Retransformed, Removed } 167 168 public static void main(String[] args) throws Throwable { 169 runAllTests(TransformStatus.Transformed); 170 171 // Retransform all classes and then repeat tests 172 Set<Class<?>> classes = new HashSet<Class<?>>(); 173 for (String className : instrClassesTarget) { 174 Class<?> clazz = Class.forName(className.replaceAll("/", ".")); 175 classes.add(clazz); 176 log("Will retransform " + clazz.getName()); 177 } 178 instrumentation.retransformClasses(classes.toArray(new Class<?>[0])); 179 180 // Clear all callback keys so we don't read keys from the previous test run. 181 InstrumentationCallback.clear(); 182 runAllTests(TransformStatus.Retransformed); 183 184 // Remove my test transformer and run tests again. Should not get any callbacks. 185 instrumentation.removeTransformer(testTransformer); 186 instrumentation.retransformClasses(classes.toArray(new Class<?>[0])); 187 InstrumentationCallback.clear(); 188 runAllTests(TransformStatus.Removed); 189 } 190 191 // This is not all available jfr io tests, but a reasonable selection. 192 public static void runAllTests(TransformStatus status) throws Throwable { 193 log("runAllTests, TransformStatus: " + status); 194 try { 195 String[] noArgs = new String[0]; 196 TestRandomAccessFileEvents.main(noArgs); 197 TestSocketEvents.main(noArgs); 198 TestSocketChannelEvents.main(noArgs); 199 TestFileChannelEvents.main(noArgs); 200 TestFileStreamEvents.main(noArgs); 201 TestDisabledEvents.main(noArgs); 202 203 // Verify that all expected callbacks have been called. 204 Set<String> callbackKeys = InstrumentationCallback.getKeysCopy(); 205 for (String key : instrMethodKeys) { 206 boolean gotCallback = callbackKeys.contains(key); 207 boolean expectsCallback = isClassInstrumented(status, key); 208 String msg = String.format("key:%s, expects:%b", key, expectsCallback); 209 if (gotCallback != expectsCallback) { 210 throw new Exception("Wrong callback() for " + msg); 211 } else { 212 log("Correct callback() for " + msg); 213 } 214 } 215 } catch (Throwable t) { 216 log("Test failed in phase " + status); 217 t.printStackTrace(); 218 throw t; 219 } 220 } 221 222 private static boolean isClassInstrumented(TransformStatus status, String key) throws Throwable { 223 switch (status) { 224 case Retransformed: 225 return true; 226 case Removed: 227 return false; 228 case Transformed: 229 String className = getClassFromMethodKey(key); 230 return instrClassesDone.contains(className); 231 } 232 throw new Exception("Test error: Unknown TransformStatus: " + status); 233 } 234 } 235 236 237 //////////////////////////////////////////////////////////////////// 238 // This is the setup part. It will create needed jars and 239 // launch a new java instance that will run the internal class TestMain. 240 // This setup step is needed because we must use a javaagent jar to 241 // transform classes. 242 //////////////////////////////////////////////////////////////////// 243 244 public static void main(String[] args) throws Throwable { 245 buildJar("TestInstrumentation", true); 246 buildJar("InstrumentationCallback", false); 247 launchTest(); 248 } 249 250 private static void buildJar(String jarName, boolean withManifest) throws Throwable { 251 final String slash = File.separator; 252 final String packageName = "jdk/jfr/event/io".replace("/", slash); 253 System.out.println("buildJar packageName: " + packageName); 254 255 String testClasses = System.getProperty("test.classes", "?"); 256 String testSrc = System.getProperty("test.src", "?"); 257 String jarPath = testClasses + slash + jarName + ".jar"; 258 String manifestPath = testSrc + slash + jarName + ".mf"; 259 String className = packageName + slash + jarName + ".class"; 260 261 String[] args = null; 262 if (withManifest) { 263 args = new String[] {"-cfm", jarPath, manifestPath, "-C", testClasses, className}; 264 } else { 265 args = new String[] {"-cf", jarPath, "-C", testClasses, className}; 266 } 267 268 log("Running jar " + Arrays.toString(args)); 269 sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar"); 270 if (!jarTool.run(args)) { 271 throw new Exception("jar failed: args=" + Arrays.toString(args)); 272 } 273 } 274 275 // Launch the test instance. Will run the internal class TestMain. 276 private static void launchTest() throws Throwable { 277 final String slash = File.separator; 278 279 // Need to add jdk/lib/tools.jar to classpath. 280 String classpath = 281 System.getProperty("test.class.path", "") + File.pathSeparator + 282 System.getProperty("test.jdk", ".") + slash + "lib" + slash + "tools.jar"; 283 String testClassDir = System.getProperty("test.classes", "") + slash; 284 285 String[] args = { 286 "-Xbootclasspath/a:" + testClassDir + "InstrumentationCallback.jar", 287 "--add-exports", "java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED", 288 "-classpath", classpath, 289 "-javaagent:" + testClassDir + "TestInstrumentation.jar", 290 "jdk.jfr.event.io.TestInstrumentation$TestMain" }; 291 OutputAnalyzer output = ProcessTools.executeTestJvm(args); 292 output.shouldHaveExitValue(0); 293 } 294 295 296 //////////////////////////////////////////////////////////////////// 297 // This is the java agent part. Used to transform classes. 298 // 299 // Each transformed method will add this call: 300 // InstrumentationCallback.callback("<classname>::<methodname>"); 301 //////////////////////////////////////////////////////////////////// 302 303 public static void premain(String args, Instrumentation inst) throws Exception { 304 instrumentation = inst; 305 testTransformer = new TestInstrumentation(); 306 inst.addTransformer(testTransformer, true); 307 } 308 309 public byte[] transform( 310 ClassLoader classLoader, String className, Class<?> classBeingRedefined, 311 ProtectionDomain pd, byte[] bytes) throws IllegalClassFormatException { 312 // Check if this class should be instrumented. 313 if (!instrClassesTarget.contains(className)) { 314 return null; 315 } 316 317 boolean isRedefinition = classBeingRedefined != null; 318 log("instrument class(" + className + ") " + (isRedefinition ? "redef" : "load")); 319 320 ClassReader reader = new ClassReader(bytes); 321 ClassWriter writer = new ClassWriter( 322 reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 323 CallbackClassVisitor classVisitor = new CallbackClassVisitor(writer); 324 reader.accept(classVisitor, 0); 325 instrClassesDone.add(className); 326 return writer.toByteArray(); 327 } 328 329 private static class CallbackClassVisitor extends ClassVisitor { 330 private String className; 331 332 public CallbackClassVisitor(ClassVisitor cv) { 333 super(Opcodes.ASM5, cv); 334 } 335 336 @Override 337 public void visit( 338 int version, int access, String name, String signature, 339 String superName, String[] interfaces) { 340 cv.visit(version, access, name, signature, superName, interfaces); 341 className = name; 342 } 343 344 @Override 345 public MethodVisitor visitMethod( 346 int access, String methodName, String desc, String signature, String[] exceptions) { 347 String methodKey = getInstrMethodKey(className, methodName, desc); 348 boolean isInstrumentedMethod = Arrays.asList(instrMethodKeys).contains(methodKey); 349 MethodVisitor mv = cv.visitMethod(access, methodName, desc, signature, exceptions); 350 if (isInstrumentedMethod && mv != null) { 351 mv = new CallbackMethodVisitor(mv, methodKey); 352 log("instrumented " + methodKey); 353 } 354 return mv; 355 } 356 } 357 358 public static class CallbackMethodVisitor extends MethodVisitor { 359 private String logMessage; 360 361 public CallbackMethodVisitor(MethodVisitor mv, String logMessage) { 362 super(Opcodes.ASM5, mv); 363 this.logMessage = logMessage; 364 } 365 366 @Override 367 public void visitCode() { 368 mv.visitCode(); 369 String methodDescr = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); 370 String className = InstrumentationCallback.class.getName().replace('.', '/'); 371 mv.visitLdcInsn(logMessage); 372 mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, "callback", methodDescr); 373 } 374 } 375 376 }