From 352a49bda3b0707507f6544ebe2cbe8c8709db0a Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 14 Sep 2021 14:39:01 +0300 Subject: [PATCH 1/4] Generate our own sun.misc.Unsafe if it is not available --- .../javaagent/bootstrap/AgentClassLoader.java | 6 +- javaagent-tooling/build.gradle.kts | 1 + .../javaagent-tooling-java9/build.gradle.kts | 19 + .../tooling/SunMiscUnsafeGenerator.java | 401 ++++++++++++++++++ .../javaagent/tooling/UnsafeInitializer.java | 77 ++++ .../src/test/groovy/UnsafeTest.groovy | 23 + .../javaagent/tooling/AgentInstaller.java | 9 + settings.gradle.kts | 1 + 8 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 javaagent-tooling/javaagent-tooling-java9/build.gradle.kts create mode 100644 javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java create mode 100644 javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java create mode 100644 javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoader.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoader.java index bbd355b3a757..e6c3b4661436 100644 --- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoader.java +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoader.java @@ -145,13 +145,17 @@ protected Class findClass(String name) throws ClassNotFoundException { } definePackageIfNeeded(name); - return defineClass(name, bytes, 0, bytes.length, codeSource); + return defineClass(name, bytes); } // find class from agent initializer jar return super.findClass(name); } + public Class defineClass(String name, byte[] bytes) { + return defineClass(name, bytes, 0, bytes.length, codeSource); + } + private byte[] getJarEntryBytes(JarEntry jarEntry) throws IOException { int size = (int) jarEntry.getSize(); byte[] buffer = new byte[size]; diff --git a/javaagent-tooling/build.gradle.kts b/javaagent-tooling/build.gradle.kts index 9b3fa340723e..0810df621f53 100644 --- a/javaagent-tooling/build.gradle.kts +++ b/javaagent-tooling/build.gradle.kts @@ -9,6 +9,7 @@ dependencies { implementation(project(":javaagent-bootstrap")) implementation(project(":javaagent-extension-api")) implementation(project(":javaagent-instrumentation-api")) + implementation(project(":javaagent-tooling:javaagent-tooling-java9")) implementation(project(":instrumentation-api")) implementation(project(":instrumentation-api-annotation-support")) implementation(project(":muzzle")) diff --git a/javaagent-tooling/javaagent-tooling-java9/build.gradle.kts b/javaagent-tooling/javaagent-tooling-java9/build.gradle.kts new file mode 100644 index 000000000000..976c8c96c349 --- /dev/null +++ b/javaagent-tooling/javaagent-tooling-java9/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") +} + +group = "io.opentelemetry.javaagent" + +dependencies { + implementation(project(":javaagent-bootstrap")) + + implementation("net.bytebuddy:byte-buddy") + implementation("org.slf4j:slf4j-api") + + testImplementation("net.bytebuddy:byte-buddy-agent") +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_1_9) +} diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java new file mode 100644 index 000000000000..95891381a506 --- /dev/null +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java @@ -0,0 +1,401 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling; + +import static net.bytebuddy.jar.asm.Opcodes.ACC_FINAL; +import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE; +import static net.bytebuddy.jar.asm.Opcodes.ACC_PUBLIC; +import static net.bytebuddy.jar.asm.Opcodes.ACC_STATIC; +import static net.bytebuddy.jar.asm.Opcodes.ACC_SUPER; +import static net.bytebuddy.jar.asm.Opcodes.ALOAD; +import static net.bytebuddy.jar.asm.Opcodes.DUP; +import static net.bytebuddy.jar.asm.Opcodes.GETSTATIC; +import static net.bytebuddy.jar.asm.Opcodes.ILOAD; +import static net.bytebuddy.jar.asm.Opcodes.INVOKESPECIAL; +import static net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC; +import static net.bytebuddy.jar.asm.Opcodes.INVOKEVIRTUAL; +import static net.bytebuddy.jar.asm.Opcodes.IRETURN; +import static net.bytebuddy.jar.asm.Opcodes.NEW; +import static net.bytebuddy.jar.asm.Opcodes.PUTSTATIC; +import static net.bytebuddy.jar.asm.Opcodes.RETURN; + +import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import net.bytebuddy.jar.asm.ClassVisitor; +import net.bytebuddy.jar.asm.ClassWriter; +import net.bytebuddy.jar.asm.FieldVisitor; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; + +/** + * Helper class for generating our own copy of sun.misc.Unsafe that delegates to + * jdk.internal.misc.Unsafe. Used when jdk provided sun.misc.Unsafe is not available which can + * happen when running modular application without jdk.unsupported module. + */ +class SunMiscUnsafeGenerator { + private static final String UNSAFE_NAME = "sun/misc/Unsafe"; + private static final String UNSAFE_DESC = "L" + UNSAFE_NAME + ";"; + + private final Class internalUnsafeClass; + private final List fields = new ArrayList<>(); + private final List methods = new ArrayList<>(); + + public SunMiscUnsafeGenerator(Class internalUnsafeClass) { + this.internalUnsafeClass = internalUnsafeClass; + + addFields(); + addMethods(); + } + + private void addFields() { + // fields that our unsafe class will contain + addField("INVALID_FIELD_OFFSET", int.class); + addField("ARRAY_BOOLEAN_BASE_OFFSET", int.class); + addField("ARRAY_BYTE_BASE_OFFSET", int.class); + addField("ARRAY_SHORT_BASE_OFFSET", int.class); + addField("ARRAY_CHAR_BASE_OFFSET", int.class); + addField("ARRAY_INT_BASE_OFFSET", int.class); + addField("ARRAY_LONG_BASE_OFFSET", int.class); + addField("ARRAY_FLOAT_BASE_OFFSET", int.class); + addField("ARRAY_DOUBLE_BASE_OFFSET", int.class); + addField("ARRAY_OBJECT_BASE_OFFSET", int.class); + addField("ARRAY_BOOLEAN_INDEX_SCALE", int.class); + addField("ARRAY_BYTE_INDEX_SCALE", int.class); + addField("ARRAY_SHORT_INDEX_SCALE", int.class); + addField("ARRAY_CHAR_INDEX_SCALE", int.class); + addField("ARRAY_INT_INDEX_SCALE", int.class); + addField("ARRAY_LONG_INDEX_SCALE", int.class); + addField("ARRAY_FLOAT_INDEX_SCALE", int.class); + addField("ARRAY_DOUBLE_INDEX_SCALE", int.class); + addField("ARRAY_OBJECT_INDEX_SCALE", int.class); + addField("ADDRESS_SIZE", int.class); + } + + private boolean hasSuitableField(String name, Class type) { + try { + Field field = internalUnsafeClass.getDeclaredField(name); + return Modifier.isPublic(field.getModifiers()) && field.getType() == type; + } catch (NoSuchFieldException exception) { + return false; + } + } + + private void addField(String name, Class type) { + if (!hasSuitableField(name, type)) { + throw new IllegalStateException( + "Could not find suitable field for " + name + " " + Type.getDescriptor(type)); + } + fields.add(new FieldDescriptor(name, type)); + } + + private void addMethods() { + // methods that our unsafe class will contain + addMethod( + "compareAndSwapObject", + boolean.class, + Object.class, + long.class, + Object.class, + Object.class); + addMethod("compareAndSwapInt", boolean.class, Object.class, long.class, int.class, int.class); + addMethod( + "compareAndSwapLong", boolean.class, Object.class, long.class, long.class, long.class); + addMethod("putOrderedObject", void.class, Object.class, long.class, Object.class); + addMethod("putOrderedInt", void.class, Object.class, long.class, int.class); + addMethod("putOrderedLong", void.class, Object.class, long.class, long.class); + addMethod("allocateInstance", Object.class, Class.class); + addMethod("loadFence", void.class); + addMethod("storeFence", void.class); + addMethod("fullFence", void.class); + addMethod("getObject", Object.class, Object.class, long.class); + addMethod("putObject", void.class, Object.class, long.class, Object.class); + addMethod("getBoolean", boolean.class, Object.class, long.class); + addMethod("putBoolean", void.class, Object.class, long.class, boolean.class); + addMethod("getByte", byte.class, long.class); + addMethod("getByte", byte.class, Object.class, long.class); + addMethod("putByte", void.class, long.class, byte.class); + addMethod("putByte", void.class, Object.class, long.class, byte.class); + addMethod("getShort", short.class, long.class); + addMethod("getShort", short.class, Object.class, long.class); + addMethod("putShort", void.class, long.class, short.class); + addMethod("putShort", void.class, Object.class, long.class, short.class); + addMethod("getChar", char.class, long.class); + addMethod("getChar", char.class, Object.class, long.class); + addMethod("putChar", void.class, Object.class, long.class, char.class); + addMethod("putChar", void.class, long.class, char.class); + addMethod("getInt", int.class, Object.class, long.class); + addMethod("getInt", int.class, long.class); + addMethod("putInt", void.class, long.class, int.class); + addMethod("putInt", void.class, Object.class, long.class, int.class); + addMethod("getLong", long.class, long.class); + addMethod("getLong", long.class, Object.class, long.class); + addMethod("putLong", void.class, long.class, long.class); + addMethod("putLong", void.class, Object.class, long.class, long.class); + addMethod("getFloat", float.class, long.class); + addMethod("getFloat", float.class, Object.class, long.class); + addMethod("putFloat", void.class, Object.class, long.class, float.class); + addMethod("putFloat", void.class, long.class, float.class); + addMethod("getDouble", double.class, Object.class, long.class); + addMethod("getDouble", double.class, long.class); + addMethod("putDouble", void.class, Object.class, long.class, double.class); + addMethod("putDouble", void.class, long.class, double.class); + addMethod("getObjectVolatile", Object.class, Object.class, long.class); + addMethod("putObjectVolatile", void.class, Object.class, long.class, Object.class); + addMethod("getBooleanVolatile", boolean.class, Object.class, long.class); + addMethod("putBooleanVolatile", void.class, Object.class, long.class, boolean.class); + addMethod("getByteVolatile", byte.class, Object.class, long.class); + addMethod("putByteVolatile", void.class, Object.class, long.class, byte.class); + addMethod("getShortVolatile", short.class, Object.class, long.class); + addMethod("putShortVolatile", void.class, Object.class, long.class, short.class); + addMethod("getCharVolatile", char.class, Object.class, long.class); + addMethod("putCharVolatile", void.class, Object.class, long.class, char.class); + addMethod("getIntVolatile", int.class, Object.class, long.class); + addMethod("putIntVolatile", void.class, Object.class, long.class, int.class); + addMethod("getLongVolatile", long.class, Object.class, long.class); + addMethod("putLongVolatile", void.class, Object.class, long.class, long.class); + addMethod("getFloatVolatile", float.class, Object.class, long.class); + addMethod("putFloatVolatile", void.class, Object.class, long.class, float.class); + addMethod("getDoubleVolatile", double.class, Object.class, long.class); + addMethod("putDoubleVolatile", void.class, Object.class, long.class, double.class); + addMethod("getAndAddInt", int.class, Object.class, long.class, int.class); + addMethod("getAndAddLong", long.class, Object.class, long.class, long.class); + addMethod("getAndSetInt", int.class, Object.class, long.class, int.class); + addMethod("getAndSetLong", long.class, Object.class, long.class, long.class); + addMethod("getAndSetObject", Object.class, Object.class, long.class, Object.class); + addMethod("park", void.class, boolean.class, long.class); + addMethod("unpark", void.class, Object.class); + addMethod("throwException", void.class, Throwable.class); + addMethod("objectFieldOffset", long.class, Field.class); + addMethod("staticFieldBase", Object.class, Field.class); + addMethod("staticFieldOffset", long.class, Field.class); + addMethod("shouldBeInitialized", boolean.class, Class.class); + addMethod("ensureClassInitialized", void.class, Class.class); + addMethod("getAddress", long.class, long.class); + addMethod("putAddress", void.class, long.class, long.class); + addMethod("allocateMemory", long.class, long.class); + addMethod("reallocateMemory", long.class, long.class, long.class); + addMethod("setMemory", void.class, long.class, long.class, byte.class); + addMethod("setMemory", void.class, Object.class, long.class, long.class, byte.class); + addMethod("copyMemory", void.class, long.class, long.class, long.class); + addMethod( + "copyMemory", void.class, Object.class, long.class, Object.class, long.class, long.class); + addMethod("freeMemory", void.class, long.class); + addMethod("arrayBaseOffset", int.class, Class.class); + addMethod("arrayIndexScale", int.class, Class.class); + addMethod("addressSize", int.class); + addMethod("pageSize", int.class); + addMethod("defineAnonymousClass", Class.class, Class.class, byte[].class, Object[].class); + addMethod("getLoadAverage", int.class, double[].class, int.class); + addMethod("invokeCleaner", void.class, ByteBuffer.class); + } + + private static List getNameCandidates(String name) { + if (name.startsWith("compareAndSwap")) { + name = name.replace("compareAndSwap", "compareAndSet"); + } else if (name.startsWith("putOrdered")) { + name = name.replace("putOrdered", "put") + "Release"; + } + if (name.contains("Object")) { + String alternativeName = name.replace("Object", "Reference"); + return Arrays.asList(name, alternativeName); + } + + return Collections.singletonList(name); + } + + private void addMethod(String name, Class returnType, Class... parameterTypes) { + addMethod(name, getNameCandidates(name), returnType, parameterTypes); + } + + private void addMethod( + String name, + List targetNameCandidates, + Class returnType, + Class... parameterTypes) { + String targetName = null; + for (String candidate : targetNameCandidates) { + if (hasSuitableMethod(candidate, returnType, parameterTypes)) { + targetName = candidate; + break; + } + } + if (targetName == null) { + throw new IllegalStateException( + "Could not find suitable method for " + + name + + " " + + Type.getMethodDescriptor(Type.getType(returnType), toTypes(parameterTypes))); + } + methods.add(new MethodDescriptor(name, targetName, returnType, parameterTypes)); + } + + private boolean hasSuitableMethod(String name, Class returnType, Class... parameterTypes) { + try { + Method method = internalUnsafeClass.getDeclaredMethod(name, parameterTypes); + return Modifier.isPublic(method.getModifiers()) && method.getReturnType() == returnType; + } catch (NoSuchMethodException e) { + return false; + } + } + + private static Type[] toTypes(Class... classes) { + Type[] result = new Type[classes.length]; + for (int i = 0; i < classes.length; i++) { + result[i] = Type.getType(classes[i]); + } + return result; + } + + private static class FieldDescriptor { + final String name; + final Class type; + + FieldDescriptor(String name, Class type) { + this.name = name; + this.type = type; + } + } + + private static class MethodDescriptor { + final String name; + final String targetName; + final Class returnType; + final Class[] parameterTypes; + + MethodDescriptor( + String name, String targetName, Class returnType, Class[] parameterTypes) { + this.name = name; + this.targetName = targetName; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + } + } + + private byte[] getBytes() { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + ClassVisitor cv = cw; + cv.visit( + Opcodes.V1_5, + ACC_PUBLIC | ACC_FINAL | ACC_SUPER, + UNSAFE_NAME, + null, + "java/lang/Object", + null); + + { + FieldVisitor fv = + cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "theUnsafe", UNSAFE_DESC, null, null); + fv.visitEnd(); + } + { + FieldVisitor fv = + cv.visitField( + ACC_PRIVATE | ACC_STATIC | ACC_FINAL, + "theInternalUnsafe", + Type.getDescriptor(internalUnsafeClass), + null, + null); + fv.visitEnd(); + } + + { + MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + for (FieldDescriptor field : fields) { + FieldVisitor fv = + cv.visitField( + ACC_PUBLIC | ACC_STATIC | ACC_FINAL, + field.name, + Type.getDescriptor(field.type), + null, + null); + fv.visitEnd(); + } + + for (MethodDescriptor method : methods) { + Type[] parameters = toTypes(method.parameterTypes); + Type returnType = Type.getType(method.returnType); + String descriptor = Type.getMethodDescriptor(returnType, parameters); + MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, method.name, descriptor, null, null); + mv.visitCode(); + mv.visitFieldInsn( + GETSTATIC, UNSAFE_NAME, "theInternalUnsafe", Type.getDescriptor(internalUnsafeClass)); + int slot = 1; + for (Type parameter : parameters) { + mv.visitVarInsn(parameter.getOpcode(ILOAD), slot); + slot += parameter.getSize(); + } + mv.visitMethodInsn( + INVOKEVIRTUAL, + Type.getInternalName(internalUnsafeClass), + method.targetName, + descriptor, + false); + mv.visitInsn(returnType.getOpcode(IRETURN)); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + { + MethodVisitor mv = cv.visitMethod(ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + // private static final sun.misc.Unsafe theUnsafe = new Unsafe(); + mv.visitTypeInsn(NEW, UNSAFE_NAME); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, UNSAFE_NAME, "", "()V", false); + mv.visitFieldInsn(PUTSTATIC, UNSAFE_NAME, "theUnsafe", UNSAFE_DESC); + + // private static final jdk.internal.misc.Unsafe theInternalUnsafe = + // jdk.internal.misc.Unsafe.getUnsafe(); + mv.visitMethodInsn( + INVOKESTATIC, + Type.getInternalName(internalUnsafeClass), + "getUnsafe", + "()" + Type.getDescriptor(internalUnsafeClass), + false); + mv.visitFieldInsn( + PUTSTATIC, UNSAFE_NAME, "theInternalUnsafe", Type.getDescriptor(internalUnsafeClass)); + + // initialize field value from corresponding field in internal unsafe class + for (FieldDescriptor field : fields) { + mv.visitFieldInsn( + GETSTATIC, + Type.getInternalName(internalUnsafeClass), + field.name, + Type.getDescriptor(field.type)); + mv.visitFieldInsn(PUTSTATIC, UNSAFE_NAME, field.name, Type.getDescriptor(field.type)); + } + + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + cv.visitEnd(); + return cw.toByteArray(); + } + + public static void generateUnsafe( + Class internalUnsafeClass, AgentClassLoader agentClassLoader) { + SunMiscUnsafeGenerator generator = new SunMiscUnsafeGenerator(internalUnsafeClass); + agentClassLoader.defineClass("sun.misc.Unsafe", generator.getBytes()); + } +} diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java new file mode 100644 index 000000000000..5f4b2215b5c7 --- /dev/null +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling; + +import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; +import java.lang.instrument.Instrumentation; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UnsafeInitializer { + private static final Logger logger = LoggerFactory.getLogger(UnsafeInitializer.class); + + static void initialize(Instrumentation instrumentation, ClassLoader classLoader) { + initialize(instrumentation, classLoader, true); + } + + private static void initialize( + Instrumentation instrumentation, ClassLoader classLoader, boolean testUnsafePresent) { + Class unsafeClass; + try { + unsafeClass = Class.forName("jdk.internal.misc.Unsafe"); + } catch (ClassNotFoundException exception) { + return; + } + + Map> exports = new HashMap<>(); + // expose jdk.internal.misc.Unsafe to our agent + // this is used to generate our replacement sun.misc.Unsafe and also by grpc/netty to call + // jdk.internal.misc.Unsafe.allocateUninitializedArray + exports.put( + unsafeClass.getPackage().getName(), + Collections.singleton(UnsafeInitializer.class.getModule())); + Map> opens = new HashMap<>(); + // allow reflection in java.nio so that grpc/netty can access + // java.nio.DirectByteBuffer.(long, int) + opens.put("java.nio", Collections.singleton(UnsafeInitializer.class.getModule())); + instrumentation.redefineModule( + unsafeClass.getModule(), + Collections.emptySet(), + exports, + opens, + Collections.emptySet(), + Collections.emptyMap()); + + if (!testUnsafePresent || !hasSunMiscUnsafe()) { + if (!(classLoader instanceof AgentClassLoader)) { + // some tests don't pass AgentClassLoader, ignore them + return; + } + try { + SunMiscUnsafeGenerator.generateUnsafe(unsafeClass, (AgentClassLoader) classLoader); + } catch (Throwable throwable) { + logger.warn("Unsafe generation failed", throwable); + return; + } + } + + // tell grpc/netty that it is ok to use setAccessible(true) so it can access DirectByteBuffer + System.setProperty("io.grpc.netty.shaded.io.netty.tryReflectionSetAccessible", "true"); + } + + private static boolean hasSunMiscUnsafe() { + try { + Class.forName("sun.misc.Unsafe"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy b/javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy new file mode 100644 index 000000000000..e7c2f0e19ffd --- /dev/null +++ b/javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.javaagent.bootstrap.AgentClassLoader +import io.opentelemetry.javaagent.tooling.UnsafeInitializer +import net.bytebuddy.agent.ByteBuddyAgent +import spock.lang.Specification + +class UnsafeTest extends Specification { + + def "test generate sun.misc.Unsafe"() { + setup: + ByteBuddyAgent.install() + URL testJarLocation = AgentClassLoader.getProtectionDomain().getCodeSource().getLocation() + AgentClassLoader loader = new AgentClassLoader(new File(testJarLocation.toURI()), "", null) + UnsafeInitializer.initialize(ByteBuddyAgent.getInstrumentation(), loader, false) + + expect: + loader.loadClass("sun.misc.Unsafe").getClassLoader() == loader + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index b9b8efcd00be..4acd8cdd78bc 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -106,6 +106,7 @@ public static void installBytebuddyAgent(Instrumentation inst) { logVersionInfo(); Config config = Config.get(); if (config.getBoolean(JAVAAGENT_ENABLED_CONFIG, true)) { + setupUnsafe(inst); List agentListeners = loadOrdered(AgentListener.class); installBytebuddyAgent(inst, agentListeners); } else { @@ -181,6 +182,14 @@ public static ResettableClassFileTransformer installBytebuddyAgent( return resettableClassFileTransformer; } + private static void setupUnsafe(Instrumentation inst) { + try { + UnsafeInitializer.initialize(inst, AgentInstaller.class.getClassLoader()); + } catch (UnsupportedClassVersionError exception) { + // ignore + } + } + private static void setBootstrapPackages(Config config) { BootstrapPackagesBuilderImpl builder = new BootstrapPackagesBuilderImpl(); for (BootstrapPackagesConfigurer configurer : load(BootstrapPackagesConfigurer.class)) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 5363573733ff..3edd70f41630 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -59,6 +59,7 @@ include(":javaagent-bootstrap") include(":javaagent-exporters") include(":javaagent-extension-api") include(":javaagent-tooling") +include(":javaagent-tooling:javaagent-tooling-java9") include(":javaagent") include(":bom-alpha") From 6ebc8fb986ec3a7881d29e31929cf751109f6d1c Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 14 Sep 2021 16:30:14 +0300 Subject: [PATCH 2/4] skip generating invokeCleaner method --- .../opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java index 95891381a506..f055641d96db 100644 --- a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java @@ -26,7 +26,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -197,7 +196,6 @@ private void addMethods() { addMethod("pageSize", int.class); addMethod("defineAnonymousClass", Class.class, Class.class, byte[].class, Object[].class); addMethod("getLoadAverage", int.class, double[].class, int.class); - addMethod("invokeCleaner", void.class, ByteBuffer.class); } private static List getNameCandidates(String name) { From 5c263f8e69e3b2151a01793278077effa17eea1a Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 15 Sep 2021 12:21:07 +0300 Subject: [PATCH 3/4] grpc isn't shaded so setting system property doesn't affect only our bundled copy --- .../javaagent/tooling/UnsafeInitializer.java | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java index 5f4b2215b5c7..cbafc7e852cd 100644 --- a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java @@ -37,33 +37,27 @@ private static void initialize( exports.put( unsafeClass.getPackage().getName(), Collections.singleton(UnsafeInitializer.class.getModule())); - Map> opens = new HashMap<>(); - // allow reflection in java.nio so that grpc/netty can access - // java.nio.DirectByteBuffer.(long, int) - opens.put("java.nio", Collections.singleton(UnsafeInitializer.class.getModule())); instrumentation.redefineModule( unsafeClass.getModule(), Collections.emptySet(), exports, - opens, + Collections.emptyMap(), Collections.emptySet(), Collections.emptyMap()); - if (!testUnsafePresent || !hasSunMiscUnsafe()) { - if (!(classLoader instanceof AgentClassLoader)) { - // some tests don't pass AgentClassLoader, ignore them - return; - } - try { - SunMiscUnsafeGenerator.generateUnsafe(unsafeClass, (AgentClassLoader) classLoader); - } catch (Throwable throwable) { - logger.warn("Unsafe generation failed", throwable); - return; - } + if (testUnsafePresent && hasSunMiscUnsafe()) { + return; + } + if (!(classLoader instanceof AgentClassLoader)) { + // some tests don't pass AgentClassLoader, ignore them + return; } - // tell grpc/netty that it is ok to use setAccessible(true) so it can access DirectByteBuffer - System.setProperty("io.grpc.netty.shaded.io.netty.tryReflectionSetAccessible", "true"); + try { + SunMiscUnsafeGenerator.generateUnsafe(unsafeClass, (AgentClassLoader) classLoader); + } catch (Throwable throwable) { + logger.warn("Unsafe generation failed", throwable); + } } private static boolean hasSunMiscUnsafe() { From fd66fb7af0d6e9982311b5004127b357e6c78b53 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 17 Sep 2021 15:46:09 +0300 Subject: [PATCH 4/4] generate invokeCleaner when it is present in internal unsafe --- .../javaagent/tooling/SunMiscUnsafeGenerator.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java index f055641d96db..5aa690e56d00 100644 --- a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java @@ -26,6 +26,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -196,6 +197,8 @@ private void addMethods() { addMethod("pageSize", int.class); addMethod("defineAnonymousClass", Class.class, Class.class, byte[].class, Object[].class); addMethod("getLoadAverage", int.class, double[].class, int.class); + // this method is missing from internal unsafe in some jdk11 versions + addOptionalMethod("invokeCleaner", void.class, ByteBuffer.class); } private static List getNameCandidates(String name) { @@ -212,12 +215,17 @@ private static List getNameCandidates(String name) { return Collections.singletonList(name); } + private void addOptionalMethod(String name, Class returnType, Class... parameterTypes) { + addMethod(name, true, getNameCandidates(name), returnType, parameterTypes); + } + private void addMethod(String name, Class returnType, Class... parameterTypes) { - addMethod(name, getNameCandidates(name), returnType, parameterTypes); + addMethod(name, false, getNameCandidates(name), returnType, parameterTypes); } private void addMethod( String name, + boolean optional, List targetNameCandidates, Class returnType, Class... parameterTypes) { @@ -229,6 +237,9 @@ private void addMethod( } } if (targetName == null) { + if (optional) { + return; + } throw new IllegalStateException( "Could not find suitable method for " + name