javabytecodeinstrumentationjava-bytecode-asmjvm-bytecode

Why are my self-written classes / 3-party library classes invisible to JRE classes?


I'm writing a security-boosted Java program that uses ASM tool to add some hooks into JRE classes. The hooks will then call my method to make some rule checks. But the odd thing is, the hooks in JRE classes can never find my classes and always throw NoClassDefFoundError.

Usually, this error occurs with wrong java.class.path. I added bytecodes to JRE classes to print java.class.path and found nothing wrong. java.class.path doesn't change during method calls.

I try multiple times with different classes in JRE and 3-party library classes. NoClassDefFoundError always occurs when I add the hook into a JRE class that visits my class or a 3-party class. In other cases, like my class visiting a 3-party class or vice versa, the code works fine. So I'm sure the problem is not with my code.

I guess there's some hidden mechanism that makes my classes invisible to JRE classes, but I can't find clues from Java or ASM documents.

Here is a simple program that can reproduce my problem.

Main.java:

package mypackage;

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println(sc.nextLine());
        sc.close();
    }

    public static void insert() {
        System.out.println("Main.insert() called.");
    }
}

Agent.java

package mypackage;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class Agent {
    public static void premain(String ops, Instrumentation inst) {
        inst.addTransformer(new myTransformer());
    }
}

class myTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if ("java/util/Scanner".equals(className)) {
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            myClassVisitor cv = new myClassVisitor(Opcodes.ASM9, cw);
            cr.accept(cv, ClassReader.EXPAND_FRAMES);
            return cw.toByteArray();
        }
        return null;
    }
}

class myClassVisitor extends ClassVisitor implements Opcodes {
    protected myClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
            String[] exceptions) {
        MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("nextLine".equals(name)) {
            System.out.println("Modify Scanner.nextLine");
            return new MethodVisitor(api, methodVisitor) {
                @Override
                public void visitCode() {
                    methodVisitor.visitMethodInsn(INVOKESTATIC, "mypackage/Main", "insert", "()V", false);
                    super.visitCode();
                }
            };
        }
        return methodVisitor;
    }
}

This program modifies nextLine() in java.util.Scanner by adding a call to mypackage.Main.insert().

I use this Manifest to pack all classes into a jar file:

Main-Class: mypackage.Main
Premain-Class: mypackage.Agent
Agent-Class: mypackage.Agent
Class-Path: lib/asm-9.4.jar
Can-Set-Native-Method-Prefix: true
Can-Redefine-Classes: true
Can-Retransform-Classes: true

The ASM jar is located in lib/. Then run the following command: java -javaagent:mypackage.jar -jar mypackage.jar

Here's the output:

Modify Scanner.nextLine
Exception in thread "main" java.lang.NoClassDefFoundError: mypackage/Main
    at java.util.Scanner.nextLine(Scanner.java)
    at mypackage.Main.main(Main.java:8)

My Java version is OpenJDK-8. I can reproduce the problem on both Mac and Ubuntu.


Solution

  • You could adjust cmdline as below. The purpose is to add asm and mypackage.jar to bootstrap classloader.

    Please change "xxx" of the cmdline to your local full path.

    java -javaagent:mypackage.jar  -Xbootclasspath/a:xxx/lib/asm-9.4.jar:xxx/mypackage.jar -jar mypackage.jar