Using Instrumentation to record unhandled exception

I think you should use ASM to manipulate bytecode directly. Here is algoritms:

  1. Visit all try/catch blocks (see visitTryCatchBlock) and save all handler labels
  2. Visit instructions until one of the handler labels met.
  3. After handler label insert logging code

    GETSTATIC java/lang/System out
    LDC "exception X occured"
    INVOKEVIRTUAL java/io/PrintStream println (java/lang/String)V
    

And ensure that your javaagent works fine. Checkt that your MANIFEST.MF file contains proper premain declaration and enables class transformation.


About your current code. Here

 if (className.startsWith("com/alu/")) {
      return insertLog(className, classBeingRedefined, classfileBuffer);
 }

you transforming classes inside particular package. That classes contain code that, in particular, throw exceptions.

And here

 if(className.endsWith("Exception")){
     System.out.println("============= exception occured "+className);
 }

you log of class being retransfomed when it is first loaded by JVM, when its name ends with "Exception". Not when exception occured. But transforming exception is useless itself. So I guess you should proceed like this:

if (className.startsWith("com/alu/")) {
    System.out.println("============= class transformed "+ className);
    return insertLog(className, classBeingRedefined, classfileBuffer);
} 

So you could know that your agent at least works.


You have to deal with code like this

    try {
        if(search.equals("Category")){
            //do operation
        }
    } catch (Exception e) {
    }

where exceptions are swallowed. You transform methods that they will be like this:

try {
    try {
        if(search.equals("Category")){
            //do operation
        }
    } catch (Exception e) {
    }
} catch (Exception e) {
    e.printStackTrace();
}

Of course, when exception was swallowed by the first catch, the second one never cathes it. Instead, you should transform existing catch blocks themself, to get the following code:

try {
    if(search.equals("Category")){
        //do operation
    }
} catch (Exception e) {
    e.printStackTrace();
}

Above I shown you how to achieve this with ASM.


After more research, I found a starting point(thanks to @jarnbjo), which I believe take me to complete solution

There is an option to retransform System classes,

SimpleClassTransformer transformer = new SimpleClassTransformer();
    instrumentation.addTransformer(transformer,true);
    try {
        instrumentation.retransformClasses(java.lang.NullPointerException.class);
    } catch (UnmodifiableClassException e) {
        e.printStackTrace();
    }

I applied this code in my premain class and able to reload the NullPointerException class. I still would need to work on Transformer implementation to enable instant recording of exception.

[update 2] Finally I got a break through!!

After re transforming the required Exception class, I injected my code right inside the constructor of the Exception class.

Here is my transformer class,

public class SimpleClassTransformer implements ClassFileTransformer{

public byte[] transform(ClassLoader    loader,
        String              className,
        Class<?>            classBeingRedefined,
        ProtectionDomain    protectionDomain,
        byte[]              classfileBuffer)
                throws IllegalClassFormatException {
    System.out.println("----------------- "+className);
    if (className.startsWith("com/alu/")) {
        return insertLog(className, classBeingRedefined, classfileBuffer);
    }

    if(className.endsWith("Exception")){
        System.out.println("============= exception occured");
        return recordError(className, classBeingRedefined, classfileBuffer);
    }

    return classfileBuffer;
}

private byte[] recordError(String name, Class clazz, byte[] b){
    ClassPool pool = ClassPool.getDefault();
    CtClass cl = null;
    try {
        cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
        if (cl.isInterface() == false) {
            CtConstructor[] constr=cl.getDeclaredConstructors();
            for(CtConstructor con:constr){
                    con.insertAfter("System.out.println(\"Hey hurrray I got you mannnnnnn  -------\");  ");
            }
            b = cl.toBytecode();
        }
    } catch (Exception e) {
        System.out.println(e);
    } finally {
        if (cl != null) {
            cl.detach();
        }
    }
    return b;
}

I solved this challenge this way. I used javaassist method addCatch.

Short demo

 CtClass exceptionCtClass = ClassPool.getDefault().makeClass("java.lang.Exception");
 method.addCatch("{ System.out.println(\"Caught exception\");\n$_e.printStackTrace();\nthrow $_e; }", exceptionCtClass, "$_e");

Full example

public class ExceptionReporterAgent {

    public static void premain(final String agentArgs, final Instrumentation inst) {
        inst.addTransformer(new ExceptionReporterTransformer());
    }
}

public class ExceptionReporterTransformer implements ClassFileTransformer {

    private CtClass exceptionCtClass;

    public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain, byte[] bytes) {
        return transformClass(redefiningClass, bytes);
    }

    private byte[] transformClass(Class classToTransform, byte[] b) {
        ClassPool pool = ClassPool.getDefault();
        CtClass cl = null;
        try {
            cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
            if (cl.getName().endsWith("ClassCException")) {
                exceptionCtClass = cl; //Or any exception, you can move this logic into constructor.
            } else {
                modifyClass(cl);
            }

            b = cl.toBytecode();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cl != null) {
                cl.detach();
            }
        }

        return b;
    }

    private byte[] modifyClass(CtClass cl) throws Exception {
        CtBehavior[] methods = cl.getDeclaredBehaviors();
        for (CtBehavior method : methods) {
            changeMethod(method);
        }
        return cl.toBytecode();
    }

    private void changeMethod(CtBehavior method) throws CannotCompileException {
        method.addLocalVariable("$_start", CtClass.longType);
        method.addLocalVariable("$_end", CtClass.longType);
        method.addLocalVariable("$_total", CtClass.longType);
        method.insertBefore("{ $_start = System.currentTimeMillis(); }");

        //For methods returning String
        // method.insertAfter("{ $_end = System.currentTimeMillis();\n$_total = $_end - $_start;\nSystem.out.println(\"Total: \" + $_total);return \"AAA\"; }");


        method.addCatch("{ System.out.println(\"Caught exception\");\n$_e.printStackTrace();\nthrow $_e; }", exceptionCtClass, "$_e");

        //For methods returning String
        // method.insertAfter("{ System.out.println(\"Finally\");\nreturn \"ABC\"; }", true);

        method.insertBefore("{ System.out.println(\"Before\"); }");

        System.out.println("Modifying method: " + method.getName());
    }
}