Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make rmi instrumentation indy-compatible + add module opener #12585

Merged
merged 20 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions instrumentation/rmi/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,5 @@ tasks {
}
withType<Test>().configureEach {
jvmArgs("-Djava.rmi.server.hostname=127.0.0.1")

// Can only export on Java 9+
val testJavaVersion =
gradle.startParameter.projectProperties.get("testJavaVersion")?.let(JavaVersion::toVersion)
?: JavaVersion.current()
if (testJavaVersion.isJava9Compatible) {
jvmArgs("--add-exports=java.rmi/sun.rmi.server=ALL-UNNAMED")
jvmArgs("--add-exports=java.rmi/sun.rmi.transport=ALL-UNNAMED")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,31 @@
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation;
import io.opentelemetry.javaagent.instrumentation.rmi.context.server.RmiServerContextInstrumentation;
import java.rmi.Remote;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.bytebuddy.utility.JavaModule;

@AutoService(InstrumentationModule.class)
public class RmiContextPropagationInstrumentationModule extends InstrumentationModule {
public class RmiContextPropagationInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public RmiContextPropagationInstrumentationModule() {
super("rmi", "rmi-context-propagation");
}

@Override
public boolean isIndyModule() {
// java.lang.IllegalAccessError: class
// io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation$StreamRemoteCallConstructorAdvice (in unnamed module @0x740ee00f) cannot access class sun.rmi.transport.Connection (in module java.rmi) because module java.rmi does not export sun.rmi.transport to unnamed module @0x740ee00f
return false;
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
public Map<JavaModule, List<String>> jpmsModulesToOpen() {
return Collections.singletonMap(
JavaModule.ofType(Remote.class), Arrays.asList("sun.rmi.server", "sun.rmi.transport"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,13 @@
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.instrument.Instrumentation;
import java.rmi.server.ObjID;
import java.util.Collections;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.utility.JavaModule;
import sun.rmi.transport.Connection;

/**
Expand Down Expand Up @@ -63,30 +57,6 @@ public void transform(TypeTransformer transformer) {
.and(takesArgument(0, named("sun.rmi.transport.Connection")))
.and(takesArgument(1, named("java.rmi.server.ObjID"))),
getClass().getName() + "$StreamRemoteCallConstructorAdvice");

// expose sun.rmi.transport.StreamRemoteCall to helper classes
transformer.applyTransformer(
(builder, typeDescription, classLoader, javaModule, protectionDomain) -> {
if (JavaModule.isSupported()
&& classLoader == null
&& "sun.rmi.transport.StreamRemoteCall".equals(typeDescription.getName())
&& javaModule != null) {
Instrumentation instrumentation = InstrumentationHolder.getInstrumentation();
ClassInjector.UsingInstrumentation.redefineModule(
instrumentation,
javaModule,
Collections.emptySet(),
Collections.emptyMap(),
Collections.singletonMap(
"sun.rmi.transport",
// AgentClassLoader is in unnamed module of the bootstrap class loader which is
// where helper classes are also
Collections.singleton(JavaModule.ofType(AgentClassLoader.class))),
Collections.emptySet(),
Collections.emptyMap());
}
return builder;
});
}

@SuppressWarnings("unused")
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.bytebuddy.utility.JavaModule;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
Expand Down Expand Up @@ -61,4 +63,16 @@ default String getModuleGroup() {
default List<String> agentPackagesToHide() {
return Collections.emptyList();
}

/**
* Some instrumentation need to access JPMS modules that are not accessible by default, this
* method provides a way to access those classes like the "--add-opens" JVM command.
*
* @return map of module to open as key, list of packages as value.
*/
// TODO: when moving this method outside of experimental API, we need to decide using JavaModule
// instance or a class FQN in the map entry, as it could lead to some limitations
default Map<JavaModule, List<String>> jpmsModulesToOpen() {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.tooling;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;

import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
import java.lang.instrument.Instrumentation;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import net.bytebuddy.description.type.PackageDescription;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.utility.JavaModule;

/**
* Module opener provides ability to open JPMS modules and allows instrumentation classloader to
* access module contents without requiring JVM arguments modification. <br>
* Usage of this class must be guarded with an {@code net.bytebuddy.utility.JavaModule#isSupported}
* check as it's compiled for Java 9+, otherwise an {@link UnsupportedClassVersionError} will be
* thrown for java 8.
*/
public class ModuleOpener {
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved

private static final Logger logger = Logger.getLogger(ModuleOpener.class.getName());

// AgentClassLoader is in unnamed module of the bootstrap loader
private static final JavaModule UNNAMED_BOOT_MODULE = JavaModule.ofType(AgentClassLoader.class);

private ModuleOpener() {}

/**
* Opens JPMS module to a class loader unnamed module
*
* @param targetModule target module
* @param openTo class loader to open module for, {@literal null} to use the unnamed module of
* bootstrap classloader.
* @param packagesToOpen packages to open
*/
public static void open(
Instrumentation instrumentation,
JavaModule targetModule,
@Nullable ClassLoader openTo,
Collection<String> packagesToOpen) {

JavaModule openToModule =
openTo != null ? JavaModule.of(openTo.getUnnamedModule()) : UNNAMED_BOOT_MODULE;
Set<JavaModule> openToModuleSet = Collections.singleton(openToModule);
Map<String, Set<JavaModule>> missingOpens = new HashMap<>();
for (String packageName : packagesToOpen) {
if (!targetModule.isOpened(new PackageDescription.Simple(packageName), openToModule)) {
missingOpens.put(packageName, openToModuleSet);
logger.log(
FINE,
"Exposing package '{0}' in module '{1}' to module '{2}'",
new Object[] {packageName, targetModule, openToModule});
}
}
if (missingOpens.isEmpty()) {
return;
}

try {
ClassInjector.UsingInstrumentation.redefineModule(
instrumentation,
targetModule,
Collections.emptySet(),
Collections.emptyMap(),
missingOpens,
Collections.emptySet(),
Collections.emptyMap());
} catch (Exception e) {
logger.log(WARNING, "Failed to redefine module '" + targetModule.getActualName() + "'", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
import io.opentelemetry.javaagent.tooling.HelperClassDefinition;
import io.opentelemetry.javaagent.tooling.HelperInjector;
import io.opentelemetry.javaagent.tooling.ModuleOpener;
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher;
Expand All @@ -36,11 +37,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.annotation.AnnotationSource;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.utility.JavaModule;

public final class InstrumentationModuleInstaller {

Expand Down Expand Up @@ -202,13 +205,32 @@ private AgentBuilder installInjectingModule(
VirtualFieldImplementationInstaller contextProvider =
virtualFieldInstallerFactory.create(instrumentationModule);

AtomicBoolean openerRun = new AtomicBoolean();
AgentBuilder agentBuilder = parentAgentBuilder;
for (TypeInstrumentation typeInstrumentation : typeInstrumentations) {

AgentBuilder.Identified.Extendable extendableAgentBuilder =
setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation)
.and(muzzleMatcher)
.transform(ConstantAdjuster.instance())
.transform(
(builder, typeDescription, classLoader, module, protectionDomain) -> {
if (JavaModule.isSupported()
&& instrumentationModule instanceof ExperimentalInstrumentationModule
&& !openerRun.get()) {
ExperimentalInstrumentationModule experimentalModule =
(ExperimentalInstrumentationModule) instrumentationModule;
experimentalModule
.jpmsModulesToOpen()
.forEach(
(javaModule, packages) -> {
ModuleOpener.open(
instrumentation, javaModule, classLoader, packages);
});
openerRun.set(true);
}

return builder;
})
.transform(helperInjector);
extendableAgentBuilder = contextProvider.injectHelperClasses(extendableAgentBuilder);
extendableAgentBuilder = contextProvider.rewriteVirtualFieldsCalls(extendableAgentBuilder);
Expand Down
Loading
Loading