Java SE-8 injectable class with JNI DefineClass resulting in NoClassDefFound

I am developing an application where I directly inject some classes into the JVM like this in a custom launcher:

Step 1: Load jvm dll using LoadLibrary (C ++ code)

Step 2: actually injects the class: (note that this uses the load classloader)

jclass ljcl_LoadedClass = env->DefineClass(className, NULL, classBytes, classSize);

      

Step 3: Run the application jniEnvironment_->CallStaticVoidMethod

to actually call the main method and start the application.

Further on in Java code I try to use the injected class

mySingleton = MySingletonClass.getInstance();

      

This line throws a NoClassDefFound exception, which is hard for me to solve. Note that the same code works with Java SE-7, so this is likely due to some changes in how both classes are garbage collected or due to changes in visibility for the classloader. I'm just obsessed with how to debug this and find the reason.

Another interesting thing: inside the C ++ code (before running the application, but with the JVM dll loaded, the following code actually finds the class and I can even call the getInstance method:

jclass olDecryptorClass = jniEnvironment_->FindClass("com/foo/bar/MySingletonClass");

      

+3


source to share


1 answer


I know Andrea and discovered the cause and solution, but I don't know why this change happened in Java 8.

In the original question, the key piece of information was not that the Java agent is being used, but the calling class is NoClassDefFoundError

being used by the agent.

I created a very simple C ++ console application to launch the JVM, a simple agent that prints to the console "Agent starts" when the method is called premain()

, and then "Load class: className" by our override ClassFileTransformer.transform()

.

When using Java 7, we get the following when debugging:

C ++ step

options[1].optionString = "-javaagent:simple-agent.jar";
errorCode = createJavaVM(&javaVM, (void**)&jniEnv, &initArgs);

      

Console

Agent Started

      

C ++ step

jclass ljcl_LoadedClass = jniEnv->DefineClass(CLASS_NAME, NULL, ljby_ClassBytes, lji_Size);

      

Console

Loading class: com/foo/bar/JVMDefinedClass

      

C ++ step

jniEnv->CallStaticVoidMethod(helloWorldClass, mainMethod, mainArgs);

      

Console

Loading class: com/for/bar/HelloWorld
Hello World!

      

In Java 8 this changes to:



C ++ step

options[1].optionString = "-javaagent:simple-agent.jar";
errorCode = createJavaVM(&javaVM, (void**)&jniEnv, &initArgs);

      

Console

Agent Started
Loading class: java/lang/invoke/MethodHandleImpl
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 4 more
Caused by: java.lang.ClassNotFoundException:
... 8 more
Loading class: java/lang/invoke/MethodHandleImpl$1
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleImpl$2
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/util/function/Function
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleImpl$3
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleImpl$4
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/ClassValue
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/ClassValue$Entry
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 6 more
Loading class: java/lang/ClassValue$Identity
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 7 more
Loading class: java/lang/ClassValue$Version
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 7 more
Loading class: java/lang/invoke/MemberName$Factory
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleStatics
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 4 more
Loading class: java/lang/invoke/MethodHandleStatics$1
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: sun/misc/PostVMInitHook
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 3 more

      

C ++ step

jclass ljcl_LoadedClass = jniEnv->DefineClass(CLASS_NAME, NULL, ljby_ClassBytes, lji_Size);

      

Console

Loading class: com/foo/bar/JVMDefinedClass

      

C ++ step

jniEnv->CallStaticVoidMethod(helloWorldClass, mainMethod, mainArgs);

      

Console

Loading class: com/objectiflune/simple/TestClass
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
at com.foo.bar.ClassTransformer.transform(ClassTransformer.java:41)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
... 13 more
Hello World!

      

It's easy to see why the original 14 java/lang

classes can't find our JVMDefinedClass, it's not yet defined. But even after we've defined it, it seems like there is some knocking effect that causes the rest of the application to not find the class.

Hence, the solution is to make sure our method ClassFileTransformer.transform()

ignores these (and any) Java classes in the system:

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

    if (className.equals("com/foo/bar/JVMDefinedClass")
     || className.startsWith("com/sun")
     || className.startsWith("java/")
     || className.startsWith("javax/")
     || className.startsWith("org/ietf")
     || className.startsWith("org/jcp")
     || className.startsWith("org/omg")
     || className.startsWith("org/w3c")
     || className.startsWith("org/xml")
     || className.startsWith("sun/")
     || className.startsWith("com/oracle/")
     || className.startsWith("jdk/")
     || className.startsWith("oracle/")
     || className.startsWith("javafx/")
    ) return null;
//... rest of method ...//

      

Now that we do not process these 14 classes, it is not generated NoClassDefFoundError

and the application works as expected.

The only thing I don't understand is why the JVM in Java 8 is now sending these 14 classes through the agent when it used to be, and whether it should be.

+1


source







All Articles