changeset 86:72f62e3d9eec

JMC-6206: Making the agent prototype work on OpenJDK 11 Reviewed-by: ghb
author hirt <marcus.hirt@oracle.com>
date Wed, 05 Dec 2018 16:00:33 +0100
parents c3fef197760c
children e24e4b6bb431
files core/org.openjdk.jmc.agent/README.md core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Agent.java core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformType.java core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRUtils.java core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java
diffstat 7 files changed, 138 insertions(+), 124 deletions(-) [+]
line wrap: on
line diff
--- a/core/org.openjdk.jmc.agent/README.md	Wed Dec 05 20:14:30 2018 +0530
+++ b/core/org.openjdk.jmc.agent/README.md	Wed Dec 05 16:00:33 2018 +0100
@@ -1,28 +1,34 @@
-# The JMC Agent
-The JMC agent is an agent currently under development to add JFR instrumentation declaratively to a running program. The agent can, for example, be used to add flight recorder events to third party code for which the source is not available.
-
-To build the agent you will need a JDK 7 or later. To run the agent, a JDK 7 or later will be needed as well.
-
-## Building the agent
-To build the agent, simply use maven in the agent folder. Since the agent is not ready for prime time yet, it is not built with the rest of the core libraries.
-
-```bash
-mvn clean package
-```
-
-## Running the agent
-The agent can be tried out using the included example program.
-
-Here is an example for running the example program with JDK 7 or JDK 8:
-
-```bash
-java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -javaagent:target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar=target/test-classes/org/openjdk/jmc/agent/test/jfrprobes_template.xml -cp target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar:target/test-classes/ org.openjdk.jmc.agent.test.InstrumentMe
-```
-
-## Known Issues
-* The full converter support is still to be merged into the open source repo
-* Support for emitting an event only on exception has yet to be implemented
-* Support for reflective access to fields has yet to be implemented
-* Support for emitting event even though an exception was raised in a called method (try-finally)
-* XML probe definition validation (schema)
+# The JMC Agent
+The JMC agent is an agent currently under development to add JFR instrumentation declaratively to a running program. The agent can, for example, be used to add flight recorder events to third party code for which the source is not available.
+
+To build the agent you will need a JDK 7 or later. To run the agent, a JDK 7 or later will be needed as well.
+
+## Building the agent
+To build the agent, simply use maven in the agent folder. Since the agent is not ready for prime time yet, it is not built with the rest of the core libraries.
+
+```bash
+mvn clean package
+```
+
+## Running the agent
+The agent can be tried out using the included example program.
+
+Here is an example for running the example program with Oracle JDK 7 to Oracle JDK 10:
+
+```bash
+java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -javaagent:target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar=target/test-classes/org/openjdk/jmc/agent/test/jfrprobes_template.xml -cp target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar:target/test-classes/ org.openjdk.jmc.agent.test.InstrumentMe
+```
+
+Here is an example for running the example program with OpenJDK 11+:
+
+```bash
+java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED -XX:+FlightRecorder -javaagent:target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar=target/test-classes/org/openjdk/jmc/agent/test/jfrprobes_template.xml -cp target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar:target/test-classes/ org.openjdk.jmc.agent.test.InstrumentMe
+```
+
+## Known Issues
+* The full converter support is still to be merged into the open source repo
+* Support for emitting an event only on exception has yet to be implemented
+* Support for reflective access to fields has yet to be implemented
+* Support for emitting event even though an exception was raised in a called method (try-finally)
+* XML probe definition validation (schema)
 * Support for redefinitions and controlling the agent over JMX is not yet completed
\ No newline at end of file
--- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Agent.java	Wed Dec 05 20:14:30 2018 +0530
+++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/Agent.java	Wed Dec 05 16:00:33 2018 +0100
@@ -53,7 +53,7 @@
 	/**
 	 * This should be generated as part of the build later.
 	 */
-	public final static String VERSION = "0.0.1"; //$NON-NLS-1$
+	public final static String VERSION = "0.0.2"; //$NON-NLS-1$
 	private final static String DEFAULT_CONFIG = "jfrprobes.xml"; //$NON-NLS-1$
 
 	@SuppressWarnings("unused")
@@ -135,6 +135,6 @@
 	}
 
 	private static void printVersion() {
-		Logger.getLogger(Agent.class.getName()).info(String.format("JMC BCI agent v%s", VERSION)); //$NON-NLS-1$
+		getLogger().info(String.format("JMC BCI agent v%s", VERSION)); //$NON-NLS-1$
 	}
 }
--- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/TransformType.java	Wed Dec 05 20:14:30 2018 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
- * 
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * The contents of this file are subject to the terms of either the Universal Permissive License
- * v 1.0 as shown at http://oss.oracle.com/licenses/upl
- *
- * or the following license:
- *
- * Redistribution and use in source and binary forms, with or without modification, are permitted
- * provided that the following conditions are met:
- * 
- * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
- * and the following disclaimer.
- * 
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
- * conditions and the following disclaimer in the documentation and/or other materials provided with
- * the distribution.
- * 
- * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
- * endorse or promote products derived from this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
- * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.openjdk.jmc.agent;
-
-public enum TransformType {
-	TEXT, JFR, UNSUPPORTED;
-
-	public static TransformType parse(String string) {
-		if (string == null) {
-			return UNSUPPORTED;
-		}
-		switch (string.trim().toLowerCase()) {
-		case "jfr": //$NON-NLS-1$
-			return JFR;
-		case "text": //$NON-NLS-1$
-			return TEXT;
-		}
-		return UNSUPPORTED;
-	}
-}
--- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java	Wed Dec 05 20:14:30 2018 +0530
+++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRClassVisitor.java	Wed Dec 05 16:00:33 2018 +0100
@@ -80,7 +80,7 @@
 
 	private Class<?> generateEventClass() throws Exception {
 		byte[] eventClass = JFREventClassGenerator.generateEventClass(transformDescriptor);
-		return TypeUtils.getUnsafe().defineClass(transformDescriptor.getEventClassName(), eventClass, 0,
+		return TypeUtils.defineClass(transformDescriptor.getEventClassName(), eventClass, 0,
 				eventClass.length, definingClassLoader, protectionDomain);
 	}
 
--- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRUtils.java	Wed Dec 05 20:14:30 2018 +0530
+++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfr/impl/JFRUtils.java	Wed Dec 05 16:00:33 2018 +0100
@@ -92,7 +92,7 @@
 			return producer;
 		} catch (Exception e) {
 			System.err.println(
-					"Failed to create producer for JDK7/8 JVM. Ensure that the JVM was started with -XX:+UnlockCommercialFeatures and -XX:+FlightRecorder.");
+					"Failed to create producer for Oracle JDK7/8 JVM. Ensure that the JVM was started with -XX:+UnlockCommercialFeatures and -XX:+FlightRecorder.");
 			System.err.println("No BCI generated JFR events will be available.");
 			e.printStackTrace();
 		}
--- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java	Wed Dec 05 20:14:30 2018 +0530
+++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/jfrnext/impl/JFRNextClassVisitor.java	Wed Dec 05 16:00:33 2018 +0100
@@ -87,7 +87,7 @@
 
 	private Class<?> generateEventClass() throws Exception {
 		byte[] eventClass = JFRNextEventClassGenerator.generateEventClass(transformDescriptor);
-		return TypeUtils.getUnsafe().defineClass(transformDescriptor.getEventClassName(), eventClass, 0,
+		return TypeUtils.defineClass(transformDescriptor.getEventClassName(), eventClass, 0,
 				eventClass.length, definingClassLoader, protectionDomain);
 	}
 }
--- a/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java	Wed Dec 05 20:14:30 2018 +0530
+++ b/core/org.openjdk.jmc.agent/src/main/java/org/openjdk/jmc/agent/util/TypeUtils.java	Wed Dec 05 16:00:33 2018 +0100
@@ -34,17 +34,19 @@
 
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.ProtectionDomain;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.openjdk.jmc.agent.Parameter;
-import org.openjdk.jmc.agent.jfr.impl.JFRUtils;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
-
-import sun.misc.Unsafe;
+import org.openjdk.jmc.agent.Agent;
+import org.openjdk.jmc.agent.Parameter;
+import org.openjdk.jmc.agent.jfr.impl.JFRUtils;
 
 /**
  * Helper methods for doing transforms.
@@ -61,6 +63,17 @@
 
 	public static final Object STRING_INTERNAL_NAME = "java/lang/String"; //$NON-NLS-1$
 
+	private final static String UNSAFE_JDK_7_CLASS = "sun.misc.Unsafe"; //$NON-NLS-1$
+	private final static String UNSAFE_JDK_11_CLASS = "jdk.internal.misc.Unsafe"; //$NON-NLS-1$
+
+	private static final Object UNSAFE;
+	private static final Method UNSAFE_DEFINE_CLASS_METHOD;
+
+	static {
+		UNSAFE = getUnsafe();
+		UNSAFE_DEFINE_CLASS_METHOD = getUnsafeDefineClassMethod(UNSAFE);
+	}
+
 	/**
 	 * The file extension for java source files (.java).
 	 */
@@ -108,29 +121,21 @@
 		return String.valueOf(o);
 	}
 
-	/**
-	 * Type agnostic array toString() which also handles primitive arrays.
-	 */
-	private static String toString(Object o, int length) {
-		int iMax = length - 1;
-		if (iMax == -1) {
-			return "[]"; //$NON-NLS-1$
+	public static Class<?> defineClass(String eventClassName, byte[] eventClass, int i, int length,
+			ClassLoader definingClassLoader, ProtectionDomain protectionDomain) {
+		try {
+			return (Class<?>) UNSAFE_DEFINE_CLASS_METHOD.invoke(UNSAFE, eventClassName, eventClass, i, length,
+					definingClassLoader, protectionDomain);
+		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+			Agent.getLogger().log(Level.SEVERE, "Failed to dynamically define the class " + eventClassName, e); //$NON-NLS-1$
 		}
-
-		StringBuilder b = new StringBuilder();
-		b.append('[');
-		for (int i = 0;; i++) {
-			b.append(Array.get(o, i));
-			if (i == iMax) {
-				return b.append(']').toString();
-			}
-			b.append(", "); //$NON-NLS-1$
-		}
+		return null;
 	}
 
 	/**
-	 * Ensure that the operand is on the stack before calling. If type is void, this is a noop, and
-	 * depending on your use case you may instead want to push Opcodes.ACONST_NULL.
+	 * Ensure that the operand is on the stack before calling. If type is void, this
+	 * is a noop, and depending on your use case you may instead want to push
+	 * Opcodes.ACONST_NULL.
 	 */
 	public static void visitBox(MethodVisitor mv, Type type) {
 		switch (type.getSort()) {
@@ -163,10 +168,6 @@
 		}
 	}
 
-	private static void emitBox(MethodVisitor mv, String desc) {
-		mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "box", desc, false); //$NON-NLS-1$
-	}
-
 	public static boolean isValidJavaIdentifier(String identifier) {
 		if (identifier == null || identifier.length() == 0) {
 			return false;
@@ -213,18 +214,6 @@
 		return fqcn;
 	}
 
-	public static Unsafe getUnsafe() {
-		// Lovely, but this seems to be the only way
-		try {
-			Field f = Unsafe.class.getDeclaredField("theUnsafe"); //$NON-NLS-1$
-			f.setAccessible(true);
-			return (Unsafe) f.get(null);
-		} catch (Exception e) {
-			Logger.getLogger(JFRUtils.class.getName()).log(Level.SEVERE, "Could not access Unsafe!", e); //$NON-NLS-1$
-		}
-		return null;
-	}
-
 	public static void stringify(MethodVisitor mv, Parameter param, Type argumentType) {
 		mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "toString", //$NON-NLS-1$
 				"(Ljava/lang/Object;)Ljava/lang/String;", false); //$NON-NLS-1$
@@ -247,13 +236,82 @@
 	}
 
 	/**
-	 * Transforms a FQN in internal form, so that it can be used in e.g. formal descriptors.
+	 * Transforms a FQN in internal form, so that it can be used in e.g. formal
+	 * descriptors.
 	 *
-	 * @param className
-	 *            the fully qualified class name in internal form.
+	 * @param className the fully qualified class name in internal form.
 	 * @return the transformed class name.
 	 */
 	public static String parameterize(String className) {
 		return "L" + className + ";"; //$NON-NLS-1$ //$NON-NLS-2$
 	}
+
+	/**
+	 * Type agnostic array toString() which also handles primitive arrays.
+	 */
+	private static String toString(Object o, int length) {
+		int iMax = length - 1;
+		if (iMax == -1) {
+			return "[]"; //$NON-NLS-1$
+		}
+
+		StringBuilder b = new StringBuilder();
+		b.append('[');
+		for (int i = 0;; i++) {
+			b.append(Array.get(o, i));
+			if (i == iMax) {
+				return b.append(']').toString();
+			}
+			b.append(", "); //$NON-NLS-1$
+		}
+	}
+
+	private static void emitBox(MethodVisitor mv, String desc) {
+		mv.visitMethodInsn(Opcodes.INVOKESTATIC, INAME, "box", desc, false); //$NON-NLS-1$
+	}
+
+	private static Object getUnsafe() {
+		// Lovely, but this seems to be the only way
+		Class<?> unsafeClass = getUnsafeClass();
+		try {
+			Field f = unsafeClass.getDeclaredField("theUnsafe"); //$NON-NLS-1$
+			f.setAccessible(true);
+			return f.get(null);
+		} catch (Exception e) {
+			Logger.getLogger(JFRUtils.class.getName()).log(Level.SEVERE, "Could not access Unsafe!", e); //$NON-NLS-1$
+		}
+		return null;
+	}
+
+	private static Method getUnsafeDefineClassMethod(Object unsafe) {
+		try {
+			return unsafe.getClass().getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class,
+					ClassLoader.class, ProtectionDomain.class);
+		} catch (NoSuchMethodException | SecurityException e) {
+			System.out.println(
+					"Could not find, or access, any defineClass method. The agent will not work. If on JDK 11, try adding  --add-exports java.base/jdk.internal.misc=ALL-UNNAMED"); //$NON-NLS-1$
+			e.printStackTrace();
+			System.out.flush();
+			System.exit(3);
+		}
+		return null;
+	}
+
+	private static Class<?> getUnsafeClass() {
+		Class<?> clazz = null;
+		try {
+			clazz = Class.forName(UNSAFE_JDK_11_CLASS);
+		} catch (ClassNotFoundException e) {
+			try {
+				clazz = Class.forName(UNSAFE_JDK_7_CLASS);
+			} catch (ClassNotFoundException e1) {
+				System.out.println(
+						"Could not find, or access, any Unsafe class. The agent will not work. If on JDK 11, try adding  --add-exports java.base/jdk.internal.misc=ALL-UNNAMED"); //$NON-NLS-1$
+				e1.printStackTrace();
+				System.out.flush();
+				System.exit(2);
+			}
+		}
+		return clazz;
+	}
 }