Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setting the classpath for a newly injected class

Context: A new class say Bar, is injected into the JVM at run-time. This class belongs to a package say com.foo. A reference to this class is injected into another class that belongs to the same package. The new class may have a different name each time it is loaded - so this cannot be specified as part of any config file - e.g. cannot be specified in build.xml to be included as part of a jar file.

Issue: At class load time, jvm throws an error - java Result 1. Although I cannot conclusively determine the root cause, it looks like the newly injected class is not being found by class loader. The server was run in verbose mode which shows the list of classes loaded by the JVM and this newly injected class is seen loaded.

Question: Is the newly injected class already in the classpath? If not how to set it?

[Edit] - adding some code to the question.

Code segment - 1: This code segment below is called from the PreMain method - Premain method will be called by JVM agent and will inject the instrumentation reference at run time. Premain method injects 1 new class - Bar - and 1 reference to this new class from a method - returnsABool() - in an existing class - ExistingClass.

public static void premain(String agentArgs, Instrumentation inst) {

        // 1. Create and load the new class - Bar
        String className = "Bar";
        byte [] b = getBytesForNewClass();
        //override classDefine (as it is protected) and define the class.
        Class clazz = null;
        try {
          ClassLoader loader = ClassLoader.getSystemClassLoader();
          Class cls = Class.forName("java.lang.ClassLoader");
          java.lang.reflect.Method method =
            cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
          // protected method invocation
          method.setAccessible(true);
          try {
            Object[] args = new Object[] { className, b, new Integer(0), new Integer(b.length)};
            clazz = (Class) method.invoke(loader, args);
          } finally {
            method.setAccessible(false);
          }
        } catch (Exception e) {
         System.err.println(
            "AllocationInstrumenter was unable to create new class" + e.getMessage());
         e.printStackTrace();
        }

        // 2. Inject some lines of code into the returnsABool method in ExistingClass class that references Bar
        inst.addTransformer(new CustomInstrumenter(), true);

        // end of premain method
}

Code sement 2: The method returnsABool() needs to be byte-injected with the commented lines shown below. The code to byte inject this is also called from the PreMain method.

public class ExistingClass{

    public static boolean returnsABool() {
     // Code within comments is byte-injected, again as part of the pre-main method

     /*
     String str = Bar.get();
     if (str != "someValue") {
      return true;
     }
     */

        return false;
    }
}

Byte code injection for ExistingClass - done using asm library

{  
    MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);  
    mv.visitCode();  
    Label l0 = new Label();  
    mv.visitLabel(l0);   
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/Bar", "get", "()Ljava/lang/String;");        
    mv.visitLdcInsn("some constant here");   
    Label l1 = new Label();   
    mv.visitJumpInsn(Opcodes.IF_ACMPNE, l1);   
    mv.visitInsn(Opcodes.ICONST_0); Label l2 = new Label();   
    mv.visitJumpInsn(Opcodes.GOTO, l2);   
    mv.visitLabel(l1);   
    mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);   
    mv.visitInsn(Opcodes.ICONST_1); 
    mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});  
    mv.visitInsn(Opcodes.IRETURN);   
    mv.visitMaxs(2, 0);   
    mv.visitEnd();   
}
like image 906
crazy horse Avatar asked Nov 21 '10 17:11

crazy horse


1 Answers

I would suspect you have something wrong with your bytecode generation, the following ASM code works for me:

        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/Bar", "get", "()Ljava/lang/String;");
        Label l1 = new Label();
        mv.visitLdcInsn("some constant here");
        mv.visitJumpInsn(Opcodes.IF_ACMPEQ, l1);
        mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
        mv.visitInsn(Opcodes.ICONST_1);
        mv.visitFrame(Opcodes.F_SAME, 0, null, 1, new Object[] {Opcodes.INTEGER});
        mv.visitInsn(Opcodes.IRETURN);
        mv.visitLabel(l1);
        mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitFrame(Opcodes.F_SAME, 0, null, 1, new Object[] {Opcodes.INTEGER});
        mv.visitInsn(Opcodes.IRETURN);
        mv.visitMaxs(2, 1);
        mv.visitEnd();

Also note that:

  • the way you are comparing Strings will most likely lead to issues, you should use str.equals(str2)
  • you are replacing the entire method, instead of injecting your custom code in the beginning (your comments seem to indicate that you want to inject, instead of replace)
like image 162
Neeme Praks Avatar answered Sep 26 '22 15:09

Neeme Praks