使用Instrumentation记录未处理的exception

我试图使用instrumentation调试java应用程序。 当前系统的问题是

  • 几乎没有写任何日志声明
  • 差异处理不当

这使得很难追溯function损坏的根本原因。

为了处理这种情况,我开发了工具,java代理使用Instrumentation API,我能够注入日志语句,并解决了一半的问题。

但下一个问题是记录exception。 我想在应用程序执行期间抛出的每个exception都扩展我的工具记录。 我尝试使用javaassist API为方法注入’try-catch’块(使用addCatchinsertBeforeinsertAfter ),并且它在某种程度上是有效的。

 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直接操作字节码。 这是算法:

  1. 访问所有try / catch块(请参阅visitTryCatchBlock )并保存所有handler标签
  2. 访问说明,直到遇到其中一个handler标签。
  3. 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()); } }