使用Instrumentation记录未处理的exception
我试图使用instrumentation调试java应用程序。 当前系统的问题是
- 几乎没有写任何日志声明
- 差异处理不当
这使得很难追溯function损坏的根本原因。
为了处理这种情况,我开发了工具,java代理使用Instrumentation
API,我能够注入日志语句,并解决了一半的问题。
但下一个问题是记录exception。 我想在应用程序执行期间抛出的每个exception都扩展我的工具记录。 我尝试使用javaassist
API为方法注入’try-catch’块(使用addCatch
, insertBefore
和insertAfter
),并且它在某种程度上是有效的。
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); }
这里inserLog(..)
方法将注入必要的日志语句并且工作正常,但是当有任何exception时它不会来到变换器。
但问题是一些方法处理exception内部(即使没有log / sysout)。
例如:
try { if(search.equals("Category")){ //do operation } } catch (Exception e) { }
当search
值为null时,此代码会使用NullPointerException
,我从不知道此exception,并且应用程序因某些其他问题而失败。
最终我想要的是一种记录应用程序抛出的任何exception的机制。 以下详细信息将被捕获
- 例外类型
- Excpetion Stacktrace
- 方法和类名
我知道有API Thread.setDefaultUncaughtExceptionHandler
,但不确定它如何与java检测一起使用。 我没有任何源访问该应用程序。
[更新1]
我发现下面链接告诉使用重新retransformation
,我会试一试并更新
如何检测java系统类?
任何指导都会非常有帮助。
我认为你应该使用ASM直接操作字节码。 这是算法:
- 访问所有try / catch块(请参阅visitTryCatchBlock )并保存所有
handler
标签 - 访问说明,直到遇到其中一个
handler
标签。 -
handler
标签后插入日志代码GETSTATIC java/lang/System out LDC "exception X occured" INVOKEVIRTUAL java/io/PrintStream println (java/lang/String)V
并确保您的javaagent正常工作。 检查您的MANIFEST.MF文件是否包含正确的premain
声明并启用类转换。
关于您当前的代码。 这里
if (className.startsWith("com/alu/")) { return insertLog(className, classBeingRedefined, classfileBuffer); }
你转换特定包内的类。 这些类包含特别是抛出exception的代码。
和这里
if(className.endsWith("Exception")){ System.out.println("============= exception occured "+className); }
您在JVM首次加载时重新定义的类的日志,当它的名称以"Exception"
结尾时。 不是在发生exception时。 但转换exception本身是没用的。 所以我猜你应该这样做:
if (className.startsWith("com/alu/")) { System.out.println("============= class transformed "+ className); return insertLog(className, classBeingRedefined, classfileBuffer); }
所以你可以知道你的代理人至少有效。
你必须处理这样的代码
try { if(search.equals("Category")){ //do operation } } catch (Exception e) { }
吞噬exception的地方。 你转换它们将是这样的方法:
try { try { if(search.equals("Category")){ //do operation } } catch (Exception e) { } } catch (Exception e) { e.printStackTrace(); }
当然,当第一个catch
物吞没exception时,第二个catch
不会吞噬它。 相反,您应该自己转换现有的catch
块,以获取以下代码:
try { if(search.equals("Category")){ //do operation } } catch (Exception e) { e.printStackTrace(); }
上面我向您展示了如何使用ASM实现这一目标。
经过更多的研究,我找到了一个起点(感谢@jarnbjo),我相信我会完成解决方案
可以选择重新转换System
类,
SimpleClassTransformer transformer = new SimpleClassTransformer(); instrumentation.addTransformer(transformer,true); try { instrumentation.retransformClasses(java.lang.NullPointerException.class); } catch (UnmodifiableClassException e) { e.printStackTrace(); }
我在我的premain
类中应用了这段代码,并能够重新加载NullPointerException类。 我仍然需要使用Transformer
实现来启用exception的即时记录。
[更新2] 最后我突破了!!
在重新转换所需的Exception类之后,我将代码注入到Exception
类的构造函数中。
这是我的变压器类,
公共类SimpleClassTransformer实现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; }
我用这种方式解决了这个挑战。 我用了javaassist方法addCatch
。
简短的演示
CtClass exceptionCtClass = ClassPool.getDefault().makeClass("java.lang.Exception"); method.addCatch("{ System.out.println(\"Caught exception\");\n$_e.printStackTrace();\nthrow $_e; }", exceptionCtClass, "$_e");
完整的例子
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()); } }