0

We have a javaagent written in Java 8 that instruments application code using javassist. There is one simple ClassFileTransformer that instruments java.lang.Error class constructor to record an instantiated error. I am retransforming Error class because it is loaded much before my agent is loaded. The instrumetation code used links java.lang.Error class to classes in agent jar. As java.lang.Error is loaded by bootstrap class loader and that Error is linked to classes in agent jar, I am using -Xbootclasspath/a option to load agent classes as well using bootstrap class loader. This is working fine in Java 8.

Now to Java 9+: Now I am trying to run the same Java 8 agent in Java 9 JVM and running into modularity issues. I am still using -Xbootclasspath/a option because I still have to load the agent classes using bootstrap class loader. As with Java 9 modularity, my agent classes are loaded into unnamed module of bootstrap class loader. Though java.lang.Error class is also loaded by bootstrap class loader but it is part of java.base named module in Java 9 and I am running into below error because unnamed module is not accessible to named java.base module.

Java command line options used are -Xbootclasspath/a:"path-to-agent.jar" and -javaagent:path-to-agent.jar

javassist.CannotCompileException: [source error] no such class: com.sg.agent.ErrorRecorder
        at javassist.CtBehavior.insertAfter(CtBehavior.java:877)
        at javassist.CtBehavior.insertAfter(CtBehavior.java:792)
        at com.sg.agent.ErrorClassTransformer.transform(ErrorClassTransformer.java:60)
        at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
        at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:550)
        at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
        at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:157)
        at com.sg.agent.SGAgent.retransformError(SGAgent.java:362)
        at com.sg.agent.SGAgent.premain(SGAgent.java:346)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:500)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:512)
Caused by: compile error: no such class: com.sg.agent.ErrorRecorder
        at javassist.compiler.MemberResolver.searchImports(MemberResolver.java:470)
        at javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:414)
        at javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:321)
        at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:683)
        at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:157)
        at javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
        at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:242)
        at javassist.compiler.CodeGen.atStmnt(CodeGen.java:330)
        at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
        at javassist.compiler.Javac.compileStmnt(Javac.java:567)
        at javassist.CtBehavior.insertAfterAdvice(CtBehavior.java:892)
        at javassist.CtBehavior.insertAfter(CtBehavior.java:851)
        ... 15 more

So after some googling I found there are java command line options to overcome these issues. Accordingly, now I am using these below command line options

1) -Xbootclasspath/a:"path-to-agent.jar"
2) -javaagent:path-to-agent.jar
3) --module-path path-to-agent-jar-folder
4) --add-modules sg.agent
5) --add-modules java.base
6) --add-reads java.base=sg.agent

and agent jar file has below contents in it's manifest file.

Manifest-Version: 1.0
Premain-Class: com.sg.agent.SGAgent
Can-Retransform-Classes: true
Created-By: Srinivas
Automatic-Module-Name: sg.agent
Add-Exports: module/package
Add-Opens: module/package
Boot-Class-Path: C:/Test/SGAgent.jar

and now JVM is throwing below error. From the below stacktrace it can be seen that JVM has read sg.agent as a named module but still complaining no such class: com.sg.agent.ErrorRecorder though --add-reads option gives java.base access to sg.agent. And additionally, my jetty server is not starting now.

javassist.CannotCompileException: [source error] no such class: com.sg.agent.ErrorRecorder
        at sg.agent/javassist.CtBehavior.insertAfter(CtBehavior.java:877)
        at sg.agent/javassist.CtBehavior.insertAfter(CtBehavior.java:792)
        at sg.agent/com.sg.agent.ErrorClassTransformer.transform(ErrorClassTransformer.java:60)
        at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
        at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:550)
        at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
        at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:157)
        at sg.agent/com.sg.agent.SGAgent.retransformError(SGAgent.java:362)
        at sg.agent/com.sg.agent.SGAgent.premain(SGAgent.java:346)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:500)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:512)
Caused by: compile error: no such class: com.sg.agent.ErrorRecorder
        at sg.agent/javassist.compiler.MemberResolver.searchImports(MemberResolver.java:470)
        at sg.agent/javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:414)
        at sg.agent/javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:321)
        at sg.agent/javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:683)
        at sg.agent/javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:157)
        at sg.agent/javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
        at sg.agent/javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:242)
        at sg.agent/javassist.compiler.CodeGen.atStmnt(CodeGen.java:330)
        at sg.agent/javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
        at sg.agent/javassist.compiler.Javac.compileStmnt(Javac.java:567)
        at sg.agent/javassist.CtBehavior.insertAfterAdvice(CtBehavior.java:892)
        at sg.agent/javassist.CtBehavior.insertAfter(CtBehavior.java:851)
        ... 15 more
Error: LinkageError occurred while loading main class org.eclipse.jetty.start.Main
        java.lang.LinkageError: loader (instance of  jdk/internal/loader/ClassLoaders$AppClassLoader): attempted  duplicate class definition for name: "org/eclipse/jetty/start/Main"
Srinivas
  • 31
  • 4
  • 1
    The initial issue seems to be that *Javassist* can’t find the class definition. I doubt that this has anything to do with the module system. This seems to be connected to the way, it tries to find the class path/bootstrap path. It’s better, to specify the jar file explicitly. – Holger Sep 01 '21 at 17:53
  • I specified jar file explicitly to -Xbootclasspath/a. In the initial issue, yes, javassist could not find the class definition for com.sg.agent.ErrorRecorder. The reason is, this class is in unnamed module while java.lang.Error is in java.base module, which cannot access unnamed module. This is the same reason in later issue also but it is surprising that java.base module is still not finding sg.agent module with all the given options. – Srinivas Sep 01 '21 at 18:18
  • 1
    The exception is created at `javassist.compiler.MemberResolver.searchImports`, so it’s Javassist which tries to resolve the type on its own, and, as said, I doubt that Javassist cares about module access rules at all. With “specify the jar file explicitly”, I mean, specify it explicitly to Javassist, i.e. the [`ClassPool`](http://www.javassist.org/html/javassist/ClassPool.html) used here. – Holger Sep 02 '21 at 06:24
  • 1
    By the way, I wouldn’t rely on the presence of command line options like `-Xbootclasspath/a` here. You’re inside a Java Agent, so you have access to the `Instrumentation` API and can call [`Instrumentation.appendToBootstrapClassLoaderSearch(…)`](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#appendToBootstrapClassLoaderSearch-java.util.jar.JarFile-) directly. – Holger Sep 02 '21 at 06:27
  • @Holger Excellento! You are absolutely correct. classPool.appendClassPath() fixed the problem without needing any modularity options. If you can post this as Answer, I will accept it. One follow-up question, in a customer installation, I need the location of agent jar to set it on classPool. What is the best practice to find that location? 1) Accept it as an argument to java agent or 2) Finding with a call to Agent.class.getProtectionDomain().getCodeSource().getLocation().toURI()? – – Srinivas Sep 02 '21 at 09:19

0 Answers0