I was trying to debug java application using instrumentation. The problem with current system are
This made very difficult to trace root cause of broken functionality.
To handle the situation I have developed tool,java agent using Instrumentation
API , and I was able to inject log statements and half of the problem solved.
But the next problem is to recording the Exception. I want to extend my tool record every exception thrown during the execution of the application. I tried injecting 'try-catch' block using javaassist
API for methods (using addCatch
, insertBefore
and insertAfter
), and it is effective certain extent.
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {
if (className.startsWith("com/alu/")) {
return insertLog(className, classBeingRedefined, classfileBuffer);
}
if(className.endsWith("Exception")){
System.out.println("============= exception occured "+className);
}
Here inserLog(..)
method will inject necessary log statement and works fine,but when there is any Exception it doesn't come to transformer.
But the problem is some of the method handles exception inside ( even with out log/sysout).
eg:
try {
if(search.equals("Category")){
//do operation
}
} catch (Exception e) {
}
This code eats NullPointerException
when value of search
is null, I never know this exception and application fail for some thing else.
Ultimately what I want is a mechanism to record any exception thrown by application. following details are to be captured
I know there is API Thread.setDefaultUncaughtExceptionHandler
, but not sure how it use with java instrumentation. I don't have any source access the application.
[update 1]
I found below link tells to use retransformation
, I will give a try and update
How to instrument java system classes?
Any guidance would be greatly helpful.
I think you should use ASM to manipulate bytecode directly. Here is algoritms:
handler
labelshandler
labels met.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());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With