From c2c3e236bbdfadbfc5f505b29e317823f3511f7a Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Wed, 26 Jun 2024 19:05:29 +0800 Subject: [PATCH 1/8] Support agent by attaching it at build time GraalVM now can apply the class transformation in the native image by attaching the java agent at build time. User can attach the java agent to native-image with -javaagent option. --- substratevm/mx.substratevm/mx_substratevm.py | 28 ++++++---- substratevm/mx.substratevm/suite.py | 1 + .../com/oracle/graal/pointsto/api/HostVM.java | 4 ++ .../infrastructure/WrappedConstantPool.java | 24 ++++++++- .../com/oracle/svm/core/SubstrateOptions.java | 7 +++ .../com/oracle/svm/driver/NativeImage.java | 9 ++++ .../oracle/svm/hosted/InstrumentFeature.java | 51 ++++++++++++------- .../src/com/oracle/svm/hosted/SVMHost.java | 13 +++++ .../test/javaagent/AgentPremainHelper.java | 12 +---- .../oracle/svm/test/javaagent/AgentTest.java | 10 ++++ .../test/javaagent/agent1/TestJavaAgent1.java | 50 ++++++++++++++---- .../test/javaagent/agent2/TestJavaAgent2.java | 7 +-- 12 files changed, 163 insertions(+), 53 deletions(-) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 551ceff88ae3..73f445d56ea5 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1820,10 +1820,15 @@ def cinterfacetutorial(args): @mx.command(suite.name, 'javaagenttest', 'Runs tests for java agent with native image') def java_agent_test(args): def build_and_run(args, binary_path, native_image, agents, agents_arg): - test_cp = os.pathsep.join([classpath('com.oracle.svm.test')] + agents) + mx.log('Run agent with JVM as baseline') + test_cp = os.pathsep.join([classpath('com.oracle.svm.test')]) + java_run_cp = os.pathsep.join([test_cp, mx.dependency('org.graalvm.nativeimage').classpath_repr()]) + mx.run_java( agents_arg + ['--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED', + '-cp', java_run_cp, 'com.oracle.svm.test.javaagent.AgentTest']) + test_cp = os.pathsep.join([test_cp] + agents) native_agent_premain_options = ['-XXpremain:com.oracle.svm.test.javaagent.agent1.TestJavaAgent1:test.agent1=true', '-XXpremain:com.oracle.svm.test.javaagent.agent2.TestJavaAgent2:test.agent2=true'] - image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-H:Class=com.oracle.svm.test.javaagent.AgentTest'] - native_image(image_args + svm_experimental_options(['-H:PremainClasses=' + agents_arg]) + ['-o', binary_path] + args) + image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-J--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED', '-H:Class=com.oracle.svm.test.javaagent.AgentTest'] + native_image(image_args + svm_experimental_options(agents_arg) + ['-o', binary_path] + args) mx.run([binary_path] + native_agent_premain_options) def build_and_test_java_agent_image(native_image, args): @@ -1840,18 +1845,23 @@ def build_and_test_java_agent_image(native_image, args): # Note: we are not using MX here to avoid polluting the suite.py and requiring extra build flags mx.log("Building agent jars from " + test_classpath) agents = [] - for i in range(1, 2): + for i in range(1, 3): agent = join(tmp_dir, "testagent%d.jar" % (i)) - agent_test_classpath = join(test_classpath, 'com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i)) - class_list = [join(test_classpath, 'com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i), f) for f in os.listdir(agent_test_classpath) if os.path.isfile(os.path.join(agent_test_classpath, f)) and f.endswith(".class")] - mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = tmp_dir) + current_dir = os.getcwd() + # Change to test classpath to create agent jar file + os.chdir(test_classpath) + agent_test_classpath = join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i)) + class_list = [join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i), f) for f in os.listdir(agent_test_classpath) if os.path.isfile(os.path.join(agent_test_classpath, f)) and f.endswith(".class")] + class_list.append(join('com', 'oracle', 'svm', 'test', 'javaagent', 'AgentPremainHelper.class')) + mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = test_classpath) agents.append(agent) + os.chdir(current_dir) mx.log("Building images with different agent orders ") - build_and_run(args, join(tmp_dir, 'agenttest1'), native_image, agents,'com.oracle.svm.test.javaagent.agent1.TestJavaAgent1,com.oracle.svm.test.javaagent.agent2.TestJavaAgent2') + build_and_run(args, join(tmp_dir, 'agenttest1'), native_image, agents,[f'-javaagent:{agents[0]}=test.agent1=true', f'-javaagent:{agents[1]}=test.agent2=true']) # Switch the premain sequence of agent1 and agent2 - build_and_run(args, join(tmp_dir, 'agenttest2'), native_image, agents, 'com.oracle.svm.test.javaagent.agent2.TestJavaAgent2,com.oracle.svm.test.javaagent.agent1.TestJavaAgent1') + build_and_run(args, join(tmp_dir, 'agenttest2'), native_image, agents, [f'-javaagent:{agents[1]}=test.agent2=true', f'-javaagent:{agents[0]}=test.agent1=true']) native_image_context_run(build_and_test_java_agent_image, args) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index b035719dcd8b..f7847717e589 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1076,6 +1076,7 @@ "java.base" : [ "jdk.internal.misc", "sun.security.jca", + "jdk.internal.org.objectweb.asm" ], }, "checkstyle": "com.oracle.svm.test", diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 09a89f1f05e0..027693eb0a75 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -391,6 +391,10 @@ public boolean enableReachableInCurrentLayer() { return false; } + public boolean isFromJavaAgent(@SuppressWarnings("unused") Class clazz) { + return false; + } + /** * Helpers to determine what analysis actions should be taken for a given Multi-Method version. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java index f0c6282a9823..9c41a83b2906 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java @@ -26,10 +26,12 @@ import static jdk.vm.ci.common.JVMCIError.unimplemented; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import com.oracle.graal.pointsto.constraints.UnresolvedElementException; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.util.GraalAccess; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; @@ -41,6 +43,7 @@ import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaMethod; public class WrappedConstantPool implements ConstantPool, ConstantPoolPatch { @@ -117,7 +120,26 @@ public JavaMethod lookupMethod(int cpi, int opcode) { @Override public JavaMethod lookupMethod(int cpi, int opcode, ResolvedJavaMethod caller) { try { - return universe.lookupAllowUnresolved(wrapped.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(caller))); + JavaMethod ret = universe.lookupAllowUnresolved(wrapped.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(caller))); + /** + * The java agent classes are loaded by appClassloader, but their dependencies could be + * loaded by NativeImageClassloader. So if the required method could not be resolved, we + * look further into classes loaded by nativeImageClassloader. + */ + if (ret instanceof UnresolvedJavaMethod && universe.hostVM().isFromJavaAgent(OriginalClassProvider.getJavaClass(caller.getDeclaringClass()))) { + UnresolvedJavaMethod unresolvedResult = (UnresolvedJavaMethod) ret; + String className = unresolvedResult.format("%H"); + String methodNameWithSignature = unresolvedResult.format("%n(%P)"); + try { + Class loadedClass = ((AnalysisUniverse) universe).getConcurrentAnalysisAccess().findClassByName(className); + ResolvedJavaType resolvedType = ((AnalysisUniverse) universe).getOriginalMetaAccess().lookupJavaType(loadedClass); + ResolvedJavaMethod resolvedMethod = Arrays.stream(resolvedType.getDeclaredMethods(false)).filter(m -> m.format("%n(%P)").equals(methodNameWithSignature)).findFirst().get(); + return universe.lookupAllowUnresolved(resolvedMethod); + } catch (Exception e) { + // Could not get the resolved method, get to the unresolved path + } + } + return ret; } catch (Throwable ex) { Throwable cause = ex; if (ex instanceof ExceptionInInitializerError && ex.getCause() != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 1eab692d7cfe..588fc30ce1c1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -224,6 +224,13 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol public static OptionEnabledHandler imageLayerEnabledHandler; public static OptionEnabledHandler imageLayerCreateEnabledHandler; + @APIOption(name = "-javaagent", valueSeparator = ':')// + @Option(help = "Enable the specified java agent in native image. Usage: -javaagent:[=]. " + + "The java agent will run at image build time to take its effects in the output native image. " + + "Be noticed: The java agent's premain method will be re-executed at native image runtime. " + + "The agent should isolate the executions according to runtime environment.", type = User, stability = OptionStability.EXPERIMENTAL)// + public static final HostedOptionKey JavaAgent = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + @Fold public static boolean getSourceLevelDebug() { return SourceLevelDebug.getValue(); diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index eb2cd05ea3b6..94bf50241510 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -287,6 +287,8 @@ private static String oR(OptionKey option) { final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval); final String oHLayerCreate = oH(SubstrateOptions.LayerCreate); + final String oHJavaAgent = oH(SubstrateOptions.JavaAgent); + final Map imageBuilderEnvironment = new HashMap<>(); private final ArrayList imageBuilderArgs = new ArrayList<>(); private final Set imageBuilderUniqueLeftoverArgs = Collections.newSetFromMap(new IdentityHashMap<>()); @@ -1528,6 +1530,7 @@ private List getAgentArguments() { String agentOptions = ""; List traceClassInitializationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceClassInitialization); List traceObjectInstantiationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceObjectInstantiation); + List javaAgentOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHJavaAgent); if (!traceClassInitializationOpts.isEmpty()) { agentOptions = getAgentOptions(traceClassInitializationOpts, "c"); } @@ -1546,6 +1549,12 @@ private List getAgentArguments() { args.add("-agentlib:native-image-diagnostics-agent=" + agentOptions); } + if (!javaAgentOpts.isEmpty()) { + for (ArgumentEntry javaAgentOpt : javaAgentOpts) { + args.add("-javaagent:" + javaAgentOpt.value); + } + } + return args; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java index fd65ed4c2363..fabb29416fb9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java @@ -35,33 +35,26 @@ import org.graalvm.nativeimage.hosted.Feature; import com.oracle.svm.core.PreMainSupport; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; -import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.reflect.ReflectionFeature; -import jdk.graal.compiler.options.Option; -import jdk.graal.compiler.options.OptionStability; +import java.io.IOException; +import java.util.jar.JarFile; /** * This feature supports instrumentation in native image. */ @AutomaticallyRegisteredFeature public class InstrumentFeature implements InternalFeature { - public static class Options { - @Option(help = "Specify premain-class list. Multiple classes are separated by comma, and order matters. This is an experimental option.", stability = OptionStability.EXPERIMENTAL)// - public static final HostedOptionKey PremainClasses = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - - } @Override public boolean isInConfiguration(IsInConfigurationAccess access) { - return !Options.PremainClasses.getValue().values().isEmpty(); + return !SubstrateOptions.JavaAgent.getValue().values().isEmpty(); } @Override @@ -77,28 +70,50 @@ public void afterRegistration(AfterRegistrationAccess access) { PreMainSupport support = new PreMainSupport(); ImageSingletons.add(PreMainSupport.class, support); - List premainClasses = Options.PremainClasses.getValue().values(); - for (String clazz : premainClasses) { - addPremainClass(support, cl, clazz); + List agentOptions = SubstrateOptions.JavaAgent.getValue().values(); + for (String agentOption : agentOptions) { + addPremainClass(support, cl, agentOption); } } - private static void addPremainClass(PreMainSupport support, ClassLoader cl, String premainClass) { + private static void addPremainClass(PreMainSupport support, ClassLoader cl, String javaagentOption) { + int separatorIndex = javaagentOption.indexOf("="); + String agent; + String premainClass = null; + String options = ""; + // Get the agent file + if (separatorIndex == -1) { + agent = javaagentOption; + } else { + agent = javaagentOption.substring(0, separatorIndex); + options = javaagentOption.substring(separatorIndex + 1); + } + // Read MANIFEST in agent jar + try { + JarFile agentJarFile = new JarFile(agent); + premainClass = agentJarFile.getManifest().getMainAttributes().getValue("Premain-Class"); + } catch (IOException e) { + // This should never happen because the image build process (HotSpot) already loaded the + // agent during startup. + throw UserError.abort(e, "Can't read the agent jar %s. Please check option %s", agent, + SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, "")); + } + try { Class clazz = Class.forName(premainClass, false, cl); Method premain = findPremainMethod(premainClass, clazz); List args = new ArrayList<>(); /* The first argument contains the premain options, which will be set at runtime. */ - args.add(""); + args.add(options); if (premain.getParameterCount() == 2) { args.add(new PreMainSupport.NativeImageNoOpRuntimeInstrumentation()); } support.registerPremainMethod(premainClass, premain, args.toArray(new Object[0])); } catch (ClassNotFoundException e) { - UserError.abort("Could not register agent premain method because class %s was not found. Please check your %s setting.", premainClass, - SubstrateOptionsParser.commandArgument(Options.PremainClasses, "")); + throw UserError.abort("Could not register agent premain method because class %s was not found. Please check your %s setting.", premainClass, + SubstrateOptionsParser.commandArgument(SubstrateOptions.JavaAgent, "")); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index b3ce043bd522..9c32a69a689a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -1196,4 +1196,17 @@ public boolean allowConstantFolding(AnalysisMethod method) { public SimulateClassInitializerSupport createSimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess) { return new SimulateClassInitializerSupport(aMetaAccess, this); } + + @Override + public boolean isFromJavaAgent(Class clazz) { + if (SubstrateOptions.JavaAgent.hasBeenSet()) { + try { + String classLocation = clazz.getProtectionDomain().getCodeSource().getLocation().getFile(); + return SubstrateOptions.JavaAgent.getValue().values().stream().map(s -> s.split("=")[0]).anyMatch(s -> s.equals(classLocation)); + } catch (Exception e) { + return false; + } + } + return false; + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentPremainHelper.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentPremainHelper.java index c66e115c6545..b66d84dd8ea5 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentPremainHelper.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentPremainHelper.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024, 2024, Alibaba Group Holding Limited. All rights reserved. + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2025, Alibaba Group Holding Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,14 +36,6 @@ public static void load(Class agentClass) { } } - public static String getFirst() { - return System.getProperty("first.load.agent"); - } - - public static String getSecond() { - return System.getProperty("second.load.agent"); - } - public static void parseOptions(String agentArgs) { if (agentArgs != null && !agentArgs.isBlank()) { String[] argPairs = agentArgs.split(","); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java index 10d8dc4d6d80..f8ad8f32f281 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java @@ -58,10 +58,20 @@ private static void testPremainSequence() { } } + private static void testInstrumentation() { + // The return value of getCounter() should be changed by agent + Assert.assertEquals(11, getCounter()); + } + + private static int getCounter() { + return 10; + } + public static void main(String[] args) { testPremain(); testAgentOptions(); testPremainSequence(); + testInstrumentation(); System.out.println("Finished running Agent test."); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java index 2d9844a6832c..8847384ff459 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java @@ -34,17 +34,23 @@ import java.security.ProtectionDomain; import java.util.Collections; import java.util.Set; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; public class TestJavaAgent1 { + public static void premain( String agentArgs, Instrumentation inst) { AgentPremainHelper.parseOptions(agentArgs); System.setProperty("instrument.enable", "true"); + AgentPremainHelper.load(TestJavaAgent1.class); if (!ImageInfo.inImageRuntimeCode()) { - DemoTransformer dt = new DemoTransformer("com.oracle.svm.test.javaagent.TestJavaAgent1"); + DemoTransformer dt = new DemoTransformer(); inst.addTransformer(dt, true); } else { - AgentPremainHelper.load(TestJavaAgent1.class); /** * Test {@code inst} is {@link NativeImageNoOpRuntimeInstrumentation} and behaves as * defined. @@ -127,12 +133,15 @@ public static void premain( } } + /** + * Change the return value of {@code AgentTest#getCounter()} from 10 to 11 in the agent. + */ static class DemoTransformer implements ClassFileTransformer { private String internalClassName; - DemoTransformer(String name) { - internalClassName = name.replaceAll("\\.", "/"); + DemoTransformer() { + internalClassName = "com/oracle/svm/test/javaagent/AgentTest"; } @Override @@ -142,13 +151,36 @@ public byte[] transform( Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { - byte[] byteCode = classfileBuffer; - if (internalClassName.equals(className)) { - System.out.println("Let's do transformation for " + className); - // Do class transformation here + ClassReader cr = new ClassReader(classfileBuffer); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + if ("getCounter".equals(name) && "()I".equals(descriptor)) { + return new MethodVisitor(api, mv) { + @Override + public void visitInsn(int opcode) { + if (opcode == Opcodes.IRETURN) { + super.visitLdcInsn(11); + } + super.visitInsn(opcode); + } + }; + } + return mv; + } + }; + + cr.accept(cv, 0); + + return cw.toByteArray(); } - return byteCode; + + return null; } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent2/TestJavaAgent2.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent2/TestJavaAgent2.java index bc6fbb83513c..cdf73cd2fab0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent2/TestJavaAgent2.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent2/TestJavaAgent2.java @@ -27,16 +27,11 @@ package com.oracle.svm.test.javaagent.agent2; import com.oracle.svm.test.javaagent.AgentPremainHelper; -import org.graalvm.nativeimage.ImageInfo; public class TestJavaAgent2 { public static void premain(String agentArgs) { AgentPremainHelper.parseOptions(agentArgs); System.setProperty("instrument.enable", "true"); - if (!ImageInfo.inImageRuntimeCode()) { - // do class transformation - } else { - AgentPremainHelper.load(TestJavaAgent2.class); - } + AgentPremainHelper.load(TestJavaAgent2.class); } } From 7e87af17928a333f5be60155e4a8021a3feaac6f Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Tue, 19 Nov 2024 16:48:07 +0800 Subject: [PATCH 2/8] Traverse all methods in lambda class for stable name --- .../src/jdk/graal/compiler/java/LambdaUtils.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java index ded4c65e9b37..712fe3d555b9 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java @@ -32,7 +32,9 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -79,10 +81,15 @@ private LambdaUtils() { public static String findStableLambdaName(ResolvedJavaType lambdaType) { ResolvedJavaMethod[] lambdaProxyMethods = Arrays.stream(lambdaType.getDeclaredMethods(false)).filter(m -> !m.isBridge() && m.isPublic()).toArray(ResolvedJavaMethod[]::new); /* - * Take only the first method to find invoked methods, because the result would be the same - * for all other methods. + * Traverse all methods in lambda class for invokes because it is possible a javaagent may + * inject new methods into the lambda class. For example, Byte-buddy used by OTele can + * transform all classes that implements {@link java.util.concurrent.Callable} by injecting + * new methods that may not have any invokes. */ - List invokedMethods = findInvokedMethods(lambdaProxyMethods[0]); + Set invokedMethods = new HashSet<>(); + for (int i = 0; i < lambdaProxyMethods.length; i++) { + invokedMethods.addAll(findInvokedMethods(lambdaProxyMethods[i])); + } if (invokedMethods.isEmpty()) { StringBuilder sb = new StringBuilder(); sb.append("Lambda without a target invoke: ").append(lambdaType.toClassName()); @@ -141,7 +148,7 @@ public static boolean isLambdaName(String name) { return isLambdaClassName(name) && lambdaMatcher(name).find(); } - private static String createStableLambdaName(ResolvedJavaType lambdaType, List targetMethods) { + private static String createStableLambdaName(ResolvedJavaType lambdaType, Set targetMethods) { final String lambdaName = lambdaType.getName(); assert lambdaMatcher(lambdaName).find() : "Stable name should be created for lambda types: " + lambdaName; From 592d02706ff9f7db5536b292962051890be6a44f Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Wed, 5 Mar 2025 18:57:08 +0800 Subject: [PATCH 3/8] Use class-file API instead of ASM --- substratevm/mx.substratevm/mx_substratevm.py | 5 +- substratevm/mx.substratevm/suite.py | 1 - .../test/javaagent/agent1/TestJavaAgent1.java | 61 ++++++++----------- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 73f445d56ea5..e02aa6a54685 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1823,11 +1823,10 @@ def build_and_run(args, binary_path, native_image, agents, agents_arg): mx.log('Run agent with JVM as baseline') test_cp = os.pathsep.join([classpath('com.oracle.svm.test')]) java_run_cp = os.pathsep.join([test_cp, mx.dependency('org.graalvm.nativeimage').classpath_repr()]) - mx.run_java( agents_arg + ['--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED', - '-cp', java_run_cp, 'com.oracle.svm.test.javaagent.AgentTest']) + mx.run_java( agents_arg + ['-cp', java_run_cp, 'com.oracle.svm.test.javaagent.AgentTest']) test_cp = os.pathsep.join([test_cp] + agents) native_agent_premain_options = ['-XXpremain:com.oracle.svm.test.javaagent.agent1.TestJavaAgent1:test.agent1=true', '-XXpremain:com.oracle.svm.test.javaagent.agent2.TestJavaAgent2:test.agent2=true'] - image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-J--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED', '-H:Class=com.oracle.svm.test.javaagent.AgentTest'] + image_args = ['-cp', test_cp, '-J-ea', '-J-esa', '-H:+ReportExceptionStackTraces', '-H:Class=com.oracle.svm.test.javaagent.AgentTest'] native_image(image_args + svm_experimental_options(agents_arg) + ['-o', binary_path] + args) mx.run([binary_path] + native_agent_premain_options) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index f7847717e589..b035719dcd8b 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1076,7 +1076,6 @@ "java.base" : [ "jdk.internal.misc", "sun.security.jca", - "jdk.internal.org.objectweb.asm" ], }, "checkstyle": "com.oracle.svm.test", diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java index 8847384ff459..942de5885a29 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java @@ -29,21 +29,19 @@ import org.graalvm.nativeimage.ImageInfo; import org.junit.Assert; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.Collections; import java.util.Set; -import jdk.internal.org.objectweb.asm.ClassReader; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.ClassVisitor; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; public class TestJavaAgent1 { public static void premain( - String agentArgs, Instrumentation inst) { + String agentArgs, Instrumentation inst) { AgentPremainHelper.parseOptions(agentArgs); System.setProperty("instrument.enable", "true"); AgentPremainHelper.load(TestJavaAgent1.class); @@ -146,40 +144,29 @@ static class DemoTransformer implements ClassFileTransformer { @Override public byte[] transform( - ClassLoader loader, - String className, - Class classBeingRedefined, - ProtectionDomain protectionDomain, - byte[] classfileBuffer) { + ClassLoader loader, + String className, + Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) { if (internalClassName.equals(className)) { - ClassReader cr = new ClassReader(classfileBuffer); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - - ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, - String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); - if ("getCounter".equals(name) && "()I".equals(descriptor)) { - return new MethodVisitor(api, mv) { - @Override - public void visitInsn(int opcode) { - if (opcode == Opcodes.IRETURN) { - super.visitLdcInsn(11); - } - super.visitInsn(opcode); - } - }; - } - return mv; + ClassFile classFile = ClassFile.of(); + ClassModel classModel = classFile.parse(classfileBuffer); + + return classFile.transformClass(classModel, (classbuilder, ce) -> { + if (ce instanceof MethodModel mm && mm.methodName().equalsString("getCounter") + && mm.methodType().equalsString("()I")) { + classbuilder.transformMethod(mm, (mb, me) -> { + mb.withCode(cb -> { + cb.loadConstant(11); + cb.ireturn(); + }); + }); + } else { + classbuilder.with(ce); } - }; - - cr.accept(cv, 0); - - return cw.toByteArray(); + }); } - return null; } } From ff192167264a89a3c3ab61d35efac6aa4ae2ea22 Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Thu, 6 Mar 2025 11:29:50 +0800 Subject: [PATCH 4/8] Fix test agent dependes on target's library The agent should not depend on library loaded by its target program. --- substratevm/mx.substratevm/mx_substratevm.py | 1 + .../com/oracle/graal/pointsto/api/HostVM.java | 4 -- .../infrastructure/WrappedConstantPool.java | 24 +------ .../src/com/oracle/svm/hosted/SVMHost.java | 13 ---- .../oracle/svm/test/javaagent/AgentTest.java | 4 +- .../svm/test/javaagent/AssertInAgent.java | 69 +++++++++++++++++++ .../test/javaagent/agent1/TestJavaAgent1.java | 42 +++++------ 7 files changed, 94 insertions(+), 63 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index e02aa6a54685..17df41040bd8 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1852,6 +1852,7 @@ def build_and_test_java_agent_image(native_image, args): agent_test_classpath = join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i)) class_list = [join('com', 'oracle', 'svm', 'test', 'javaagent', 'agent' + str(i), f) for f in os.listdir(agent_test_classpath) if os.path.isfile(os.path.join(agent_test_classpath, f)) and f.endswith(".class")] class_list.append(join('com', 'oracle', 'svm', 'test', 'javaagent', 'AgentPremainHelper.class')) + class_list.append(join('com', 'oracle', 'svm', 'test', 'javaagent', 'AssertInAgent.class')) mx.run([mx.get_jdk().jar, 'cmf', join(test_classpath, 'resources', 'javaagent' + str(i), 'MANIFEST.MF'), agent] + class_list, cwd = test_classpath) agents.append(agent) os.chdir(current_dir) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 027693eb0a75..09a89f1f05e0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -391,10 +391,6 @@ public boolean enableReachableInCurrentLayer() { return false; } - public boolean isFromJavaAgent(@SuppressWarnings("unused") Class clazz) { - return false; - } - /** * Helpers to determine what analysis actions should be taken for a given Multi-Method version. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java index 9c41a83b2906..f0c6282a9823 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/WrappedConstantPool.java @@ -26,12 +26,10 @@ import static jdk.vm.ci.common.JVMCIError.unimplemented; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import com.oracle.graal.pointsto.constraints.UnresolvedElementException; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.util.GraalAccess; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; @@ -43,7 +41,6 @@ import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.meta.UnresolvedJavaMethod; public class WrappedConstantPool implements ConstantPool, ConstantPoolPatch { @@ -120,26 +117,7 @@ public JavaMethod lookupMethod(int cpi, int opcode) { @Override public JavaMethod lookupMethod(int cpi, int opcode, ResolvedJavaMethod caller) { try { - JavaMethod ret = universe.lookupAllowUnresolved(wrapped.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(caller))); - /** - * The java agent classes are loaded by appClassloader, but their dependencies could be - * loaded by NativeImageClassloader. So if the required method could not be resolved, we - * look further into classes loaded by nativeImageClassloader. - */ - if (ret instanceof UnresolvedJavaMethod && universe.hostVM().isFromJavaAgent(OriginalClassProvider.getJavaClass(caller.getDeclaringClass()))) { - UnresolvedJavaMethod unresolvedResult = (UnresolvedJavaMethod) ret; - String className = unresolvedResult.format("%H"); - String methodNameWithSignature = unresolvedResult.format("%n(%P)"); - try { - Class loadedClass = ((AnalysisUniverse) universe).getConcurrentAnalysisAccess().findClassByName(className); - ResolvedJavaType resolvedType = ((AnalysisUniverse) universe).getOriginalMetaAccess().lookupJavaType(loadedClass); - ResolvedJavaMethod resolvedMethod = Arrays.stream(resolvedType.getDeclaredMethods(false)).filter(m -> m.format("%n(%P)").equals(methodNameWithSignature)).findFirst().get(); - return universe.lookupAllowUnresolved(resolvedMethod); - } catch (Exception e) { - // Could not get the resolved method, get to the unresolved path - } - } - return ret; + return universe.lookupAllowUnresolved(wrapped.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(caller))); } catch (Throwable ex) { Throwable cause = ex; if (ex instanceof ExceptionInInitializerError && ex.getCause() != null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 9c32a69a689a..b3ce043bd522 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -1196,17 +1196,4 @@ public boolean allowConstantFolding(AnalysisMethod method) { public SimulateClassInitializerSupport createSimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess) { return new SimulateClassInitializerSupport(aMetaAccess, this); } - - @Override - public boolean isFromJavaAgent(Class clazz) { - if (SubstrateOptions.JavaAgent.hasBeenSet()) { - try { - String classLocation = clazz.getProtectionDomain().getCodeSource().getLocation().getFile(); - return SubstrateOptions.JavaAgent.getValue().values().stream().map(s -> s.split("=")[0]).anyMatch(s -> s.equals(classLocation)); - } catch (Exception e) { - return false; - } - } - return false; - } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java index f8ad8f32f281..af6a020aa0e2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentTest.java @@ -42,8 +42,8 @@ private static void testAgentOptions() { } private static void testPremainSequence() { - String first = AgentPremainHelper.getFirst(); - String second = AgentPremainHelper.getSecond(); + String first = System.getProperty("first.load.agent"); + String second = System.getProperty("second.load.agent"); Assert.assertNotNull(first); if (second != null) { String agentName = TestJavaAgent1.class.getName(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java new file mode 100644 index 000000000000..85d8ddb4fe65 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2025, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.javaagent; + +/** + * Assertions used inside agent when not using JUNIT + */ +public class AssertInAgent { + public static void assertNotNull(Object o) { + if (o == null) { + throw new RuntimeException("Object input is null, but expected to be non-null"); + } + } + + public static void assertEquals(boolean expected, boolean actual) { + if (expected != actual) { + throw new RuntimeException(String.format("Expected(%s) is not equal to actual(%s)", expected, actual)); + } + } + + public static void assertEquals(long expected, long actual) { + if (expected != actual) { + throw new RuntimeException(String.format("Expected(%s) is not equal to actual(%s)", expected, actual)); + } + } + + public static void assertEquals(Object expected, Object actual) { + if (expected != actual) { + if (expected != null) { + assertNotNull(actual); + if (!expected.equals(actual)) { + throw new RuntimeException(String.format("Expected(%s) is not equal to actual(%s)", expected, actual)); + } + } else { + throw new RuntimeException(String.format("Expected(null) is not equal to actual(%s)", actual)); + } + } + } + + + + public static void assertTrue(boolean actual) { + assertEquals(true, actual); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java index 942de5885a29..378b0fa41f51 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java @@ -26,8 +26,8 @@ package com.oracle.svm.test.javaagent.agent1; import com.oracle.svm.test.javaagent.AgentPremainHelper; +import com.oracle.svm.test.javaagent.AssertInAgent; import org.graalvm.nativeimage.ImageInfo; -import org.junit.Assert; import java.lang.classfile.ClassFile; import java.lang.classfile.ClassModel; @@ -53,22 +53,22 @@ public static void premain( * Test {@code inst} is {@link NativeImageNoOpRuntimeInstrumentation} and behaves as * defined. */ - Assert.assertNotNull(inst); - Assert.assertEquals(false, inst.isRetransformClassesSupported()); - Assert.assertEquals(false, inst.removeTransformer(null)); - Assert.assertEquals(false, inst.isRedefineClassesSupported()); + AssertInAgent.assertNotNull(inst); + AssertInAgent.assertEquals(false, inst.isRetransformClassesSupported()); + AssertInAgent.assertEquals(false, inst.removeTransformer(null)); + AssertInAgent.assertEquals(false, inst.isRedefineClassesSupported()); - Assert.assertEquals(false, inst.isModifiableClass(null)); + AssertInAgent.assertEquals(false, inst.isModifiableClass(null)); Class[] allClasses = inst.getAllLoadedClasses(); - Assert.assertTrue(allClasses.length > 0); + AssertInAgent.assertTrue(allClasses.length > 0); Class currentAgentClassFromAllLoaded = null; for (Class c : allClasses) { if (c.equals(TestJavaAgent1.class)) { currentAgentClassFromAllLoaded = c; } } - Assert.assertNotNull(currentAgentClassFromAllLoaded); + AssertInAgent.assertNotNull(currentAgentClassFromAllLoaded); // redefineClasses should throw UnsupportedOperationException Exception exception = null; @@ -77,8 +77,8 @@ public static void premain( } catch (Exception e) { exception = e; } - Assert.assertNotNull(exception); - Assert.assertEquals(UnsupportedOperationException.class, exception.getClass()); + AssertInAgent.assertNotNull(exception); + AssertInAgent.assertEquals(UnsupportedOperationException.class, exception.getClass()); // getInitiatedClasses should throw UnsupportedOperationException exception = null; @@ -87,8 +87,8 @@ public static void premain( } catch (Exception e) { exception = e; } - Assert.assertNotNull(exception); - Assert.assertEquals(UnsupportedOperationException.class, exception.getClass()); + AssertInAgent.assertNotNull(exception); + AssertInAgent.assertEquals(UnsupportedOperationException.class, exception.getClass()); // retransformClasses should throw UnsupportedOperationException exception = null; @@ -97,8 +97,8 @@ public static void premain( } catch (Exception e) { exception = e; } - Assert.assertNotNull(exception); - Assert.assertEquals(UnsupportedOperationException.class, exception.getClass()); + AssertInAgent.assertNotNull(exception); + AssertInAgent.assertEquals(UnsupportedOperationException.class, exception.getClass()); // appendToBootstrapClassLoaderSearch should throw UnsupportedOperationException exception = null; @@ -107,8 +107,8 @@ public static void premain( } catch (Exception e) { exception = e; } - Assert.assertNotNull(exception); - Assert.assertEquals(UnsupportedOperationException.class, exception.getClass()); + AssertInAgent.assertNotNull(exception); + AssertInAgent.assertEquals(UnsupportedOperationException.class, exception.getClass()); // appendToSystemClassLoaderSearch should throw UnsupportedOperationException exception = null; @@ -117,14 +117,14 @@ public static void premain( } catch (Exception e) { exception = e; } - Assert.assertNotNull(exception); - Assert.assertEquals(UnsupportedOperationException.class, exception.getClass()); + AssertInAgent.assertNotNull(exception); + AssertInAgent.assertEquals(UnsupportedOperationException.class, exception.getClass()); - Assert.assertEquals(-1, inst.getObjectSize(null)); - Assert.assertEquals(false, inst.isNativeMethodPrefixSupported()); + AssertInAgent.assertEquals(-1, inst.getObjectSize(null)); + AssertInAgent.assertEquals(false, inst.isNativeMethodPrefixSupported()); Module currentModule = TestJavaAgent1.class.getModule(); - Assert.assertEquals(true, inst.isModifiableModule(currentModule)); + AssertInAgent.assertEquals(true, inst.isModifiableModule(currentModule)); // redefineModule only does checks, no actual actions. inst.redefineModule(currentModule, Set.of(Class.class.getModule()), Collections.emptyMap(), null, null, null); From c4900b2f6975cecc1523ba4d9c6b7e66afbf5f82 Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Fri, 7 Mar 2025 10:23:29 +0800 Subject: [PATCH 5/8] add lambda instrumenting in agent test --- substratevm/mx.substratevm/mx_substratevm.py | 4 +- .../svm/test/javaagent/AgentLambdaTest.java | 46 ++++++++++++ .../test/javaagent/agent3/TestJavaAgent3.java | 72 +++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 17df41040bd8..796e3ed53553 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1844,7 +1844,7 @@ def build_and_test_java_agent_image(native_image, args): # Note: we are not using MX here to avoid polluting the suite.py and requiring extra build flags mx.log("Building agent jars from " + test_classpath) agents = [] - for i in range(1, 3): + for i in range(1, 4): agent = join(tmp_dir, "testagent%d.jar" % (i)) current_dir = os.getcwd() # Change to test classpath to create agent jar file @@ -1857,6 +1857,8 @@ def build_and_test_java_agent_image(native_image, args): agents.append(agent) os.chdir(current_dir) + build_and_run(args, join(tmp_dir, 'agenttest3'), native_image, agents,[f'-javaagent:{agents[2]}']) + mx.log("Building images with different agent orders ") build_and_run(args, join(tmp_dir, 'agenttest1'), native_image, agents,[f'-javaagent:{agents[0]}=test.agent1=true', f'-javaagent:{agents[1]}=test.agent2=true']) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java new file mode 100644 index 000000000000..4968f4faf325 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2025, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.javaagent; + +import java.lang.reflect.Method; + +public class AgentLambdaTest { + public static void main(String[] args) { + // Test instrument lambda method + Runnable r = ()->{System.out.println("I'm in thread");}; + Thread t = new Thread(r); + t.start(); + + Class c = r.getClass(); + System.out.println(c.getName()); + Method[] ms = c.getMethods(); + System.out.println("Have " + ms.length + " methods."); + if(Runnable.class.isAssignableFrom(c)){ + System.out.println("Is subtype of Runnable"); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java new file mode 100644 index 000000000000..b8f8fc3d883b --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2025, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.javaagent.agent3; + +import org.graalvm.nativeimage.ImageInfo; + +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.constant.MethodTypeDesc; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +/** + * This agent inserts new method into a lambda class. + */ +public class TestJavaAgent3 { + public static void premain( + String agentArgs, Instrumentation inst) { + if (!ImageInfo.inImageRuntimeCode()) { + InjectLambdaTransformer transformer = new InjectLambdaTransformer(); + inst.addTransformer(transformer, true); + } + } + + static class InjectLambdaTransformer implements ClassFileTransformer { + @Override + public byte[] transform( + ClassLoader loader, + String className, + Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) { + if (className.contains("$Lambda$") && classBeingRedefined != null && Runnable.class.isAssignableFrom(classBeingRedefined)) { + ClassFile classFile = ClassFile.of(); + ClassModel classModel = classFile.parse(classfileBuffer); + return classFile.transformClass(classModel, (cb, ce) -> { + cb.with(ce).withMethod("newAdd", MethodTypeDesc.ofDescriptor("()I"), 1, (methodBuilder) -> { + methodBuilder.withCode(codeBuilder -> { + codeBuilder.iload(1).ireturn(); + }); + }); + }); + } + return null; + } + } +} From 3cc16af2d07bb884fbc153fa024de62d7ea01c74 Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Tue, 18 Mar 2025 13:53:52 +0800 Subject: [PATCH 6/8] Revert "Traverse all methods in lambda class for stable name" This reverts commit 7e87af17928a333f5be60155e4a8021a3feaac6f. --- .../src/jdk/graal/compiler/java/LambdaUtils.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java index 712fe3d555b9..ded4c65e9b37 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java @@ -32,9 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -81,15 +79,10 @@ private LambdaUtils() { public static String findStableLambdaName(ResolvedJavaType lambdaType) { ResolvedJavaMethod[] lambdaProxyMethods = Arrays.stream(lambdaType.getDeclaredMethods(false)).filter(m -> !m.isBridge() && m.isPublic()).toArray(ResolvedJavaMethod[]::new); /* - * Traverse all methods in lambda class for invokes because it is possible a javaagent may - * inject new methods into the lambda class. For example, Byte-buddy used by OTele can - * transform all classes that implements {@link java.util.concurrent.Callable} by injecting - * new methods that may not have any invokes. + * Take only the first method to find invoked methods, because the result would be the same + * for all other methods. */ - Set invokedMethods = new HashSet<>(); - for (int i = 0; i < lambdaProxyMethods.length; i++) { - invokedMethods.addAll(findInvokedMethods(lambdaProxyMethods[i])); - } + List invokedMethods = findInvokedMethods(lambdaProxyMethods[0]); if (invokedMethods.isEmpty()) { StringBuilder sb = new StringBuilder(); sb.append("Lambda without a target invoke: ").append(lambdaType.toClassName()); @@ -148,7 +141,7 @@ public static boolean isLambdaName(String name) { return isLambdaClassName(name) && lambdaMatcher(name).find(); } - private static String createStableLambdaName(ResolvedJavaType lambdaType, Set targetMethods) { + private static String createStableLambdaName(ResolvedJavaType lambdaType, List targetMethods) { final String lambdaName = lambdaType.getName(); assert lambdaMatcher(lambdaName).find() : "Stable name should be created for lambda types: " + lambdaName; From 9ae8190239be568279ff0f5954db54990fff4a94 Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Tue, 18 Mar 2025 13:54:07 +0800 Subject: [PATCH 7/8] Revert "add lambda instrumenting in agent test" This reverts commit c4900b2f6975cecc1523ba4d9c6b7e66afbf5f82. --- substratevm/mx.substratevm/mx_substratevm.py | 4 +- .../svm/test/javaagent/AgentLambdaTest.java | 46 ------------ .../test/javaagent/agent3/TestJavaAgent3.java | 72 ------------------- 3 files changed, 1 insertion(+), 121 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java delete mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 796e3ed53553..17df41040bd8 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1844,7 +1844,7 @@ def build_and_test_java_agent_image(native_image, args): # Note: we are not using MX here to avoid polluting the suite.py and requiring extra build flags mx.log("Building agent jars from " + test_classpath) agents = [] - for i in range(1, 4): + for i in range(1, 3): agent = join(tmp_dir, "testagent%d.jar" % (i)) current_dir = os.getcwd() # Change to test classpath to create agent jar file @@ -1857,8 +1857,6 @@ def build_and_test_java_agent_image(native_image, args): agents.append(agent) os.chdir(current_dir) - build_and_run(args, join(tmp_dir, 'agenttest3'), native_image, agents,[f'-javaagent:{agents[2]}']) - mx.log("Building images with different agent orders ") build_and_run(args, join(tmp_dir, 'agenttest1'), native_image, agents,[f'-javaagent:{agents[0]}=test.agent1=true', f'-javaagent:{agents[1]}=test.agent2=true']) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java deleted file mode 100644 index 4968f4faf325..000000000000 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AgentLambdaTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2025, 2025, Alibaba Group Holding Limited. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.test.javaagent; - -import java.lang.reflect.Method; - -public class AgentLambdaTest { - public static void main(String[] args) { - // Test instrument lambda method - Runnable r = ()->{System.out.println("I'm in thread");}; - Thread t = new Thread(r); - t.start(); - - Class c = r.getClass(); - System.out.println(c.getName()); - Method[] ms = c.getMethods(); - System.out.println("Have " + ms.length + " methods."); - if(Runnable.class.isAssignableFrom(c)){ - System.out.println("Is subtype of Runnable"); - } - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java deleted file mode 100644 index b8f8fc3d883b..000000000000 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent3/TestJavaAgent3.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2025, 2025, Alibaba Group Holding Limited. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.test.javaagent.agent3; - -import org.graalvm.nativeimage.ImageInfo; - -import java.lang.classfile.ClassFile; -import java.lang.classfile.ClassModel; -import java.lang.constant.MethodTypeDesc; -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.Instrumentation; -import java.security.ProtectionDomain; - -/** - * This agent inserts new method into a lambda class. - */ -public class TestJavaAgent3 { - public static void premain( - String agentArgs, Instrumentation inst) { - if (!ImageInfo.inImageRuntimeCode()) { - InjectLambdaTransformer transformer = new InjectLambdaTransformer(); - inst.addTransformer(transformer, true); - } - } - - static class InjectLambdaTransformer implements ClassFileTransformer { - @Override - public byte[] transform( - ClassLoader loader, - String className, - Class classBeingRedefined, - ProtectionDomain protectionDomain, - byte[] classfileBuffer) { - if (className.contains("$Lambda$") && classBeingRedefined != null && Runnable.class.isAssignableFrom(classBeingRedefined)) { - ClassFile classFile = ClassFile.of(); - ClassModel classModel = classFile.parse(classfileBuffer); - return classFile.transformClass(classModel, (cb, ce) -> { - cb.with(ce).withMethod("newAdd", MethodTypeDesc.ofDescriptor("()I"), 1, (methodBuilder) -> { - methodBuilder.withCode(codeBuilder -> { - codeBuilder.iload(1).ireturn(); - }); - }); - }); - } - return null; - } - } -} From 260d4f46bec6d4647c260a2fd58bb84c10ddb19d Mon Sep 17 00:00:00 2001 From: Ziyi Lin Date: Tue, 18 Mar 2025 14:36:40 +0800 Subject: [PATCH 8/8] format code --- .../oracle/svm/test/javaagent/AssertInAgent.java | 4 +--- .../svm/test/javaagent/agent1/TestJavaAgent1.java | 15 +++++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java index 85d8ddb4fe65..b436392006e1 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/AssertInAgent.java @@ -27,7 +27,7 @@ package com.oracle.svm.test.javaagent; /** - * Assertions used inside agent when not using JUNIT + * Assertions used inside agent when not using JUNIT. */ public class AssertInAgent { public static void assertNotNull(Object o) { @@ -61,8 +61,6 @@ public static void assertEquals(Object expected, Object actual) { } } - - public static void assertTrue(boolean actual) { assertEquals(true, actual); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java index 378b0fa41f51..4ee62344a123 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/javaagent/agent1/TestJavaAgent1.java @@ -41,7 +41,7 @@ public class TestJavaAgent1 { public static void premain( - String agentArgs, Instrumentation inst) { + String agentArgs, Instrumentation inst) { AgentPremainHelper.parseOptions(agentArgs); System.setProperty("instrument.enable", "true"); AgentPremainHelper.load(TestJavaAgent1.class); @@ -144,18 +144,17 @@ static class DemoTransformer implements ClassFileTransformer { @Override public byte[] transform( - ClassLoader loader, - String className, - Class classBeingRedefined, - ProtectionDomain protectionDomain, - byte[] classfileBuffer) { + ClassLoader loader, + String className, + Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) { if (internalClassName.equals(className)) { ClassFile classFile = ClassFile.of(); ClassModel classModel = classFile.parse(classfileBuffer); return classFile.transformClass(classModel, (classbuilder, ce) -> { - if (ce instanceof MethodModel mm && mm.methodName().equalsString("getCounter") - && mm.methodType().equalsString("()I")) { + if (ce instanceof MethodModel mm && mm.methodName().equalsString("getCounter") && mm.methodType().equalsString("()I")) { classbuilder.transformMethod(mm, (mb, me) -> { mb.withCode(cb -> { cb.loadConstant(11);