使用仪器来记录未处理的异常使用仪器来记录未处理的异常(Using Instrumentation t

2019-05-12 02:58发布

使用的仪器,我试图调试Java应用程序。 与目前的系统是问题

  • 几乎没有任何书面陈述日志
  • 可怜的异常处理

这使得很难追查的破碎功能根本原因。

要使用处理我已经开发工具的情况下,Java代理Instrumentation API,和我能够注入的日志报表和解决问题的一半。

但接下来的问题是记录异常。 我想延长我的工具,记录每个异常应用程序的执行过程中抛出。 我试图注入用“的try-catch”块javaassist对方法(使用API 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(..)方法将注入必要的日志语句和工作正常,但是当有任何异常它不来的变压器。

但问题是一些方法(甚至出日志/系统输出)内部处理的例外。

例如:

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

此代码吃NullPointerException时的数值search是空的,我从来不知道这个异常和应用失败一些别的东西。

最后我要的是一种机制,以记录由应用程序抛出的任何异常。 以下详细信息被捕获

  • 异常类型
  • 错误时抛出堆栈跟踪
  • 方法和类的名称

我知道有API Thread.setDefaultUncaughtExceptionHandler ,但不知道如何用java仪器使用。 我没有任何源访问的应用程序。

[更新1]

我发现下面的链接告诉使用retransformation ,我会给一个尝试,更新

如何检测Java系统类?

任何指导,将是极大的帮助。

Answer 1:

我认为你应该使用ASM直接操作字节码。 这里是algoritms:

  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工作正常。 Checkt您的MANIFEST.MF文件中包含适当的premain声明,使类转换。


关于当前的代码。 这里

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

你转化特定包内的类。 那类包含的代码,特别是,抛出异常。

和这里

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

您登录类被retransfomed当它第一次通过JVM,满载时,其名称结尾"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) {
    }

其中例外吞噬。 你变换方法,他们会是这样的:

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

当然,当异常是由第一吞噬catch ,第二个从未cathes它。 相反,你应该改变现有的catch块自理,得到下面的代码:

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

以上我展示了如何使用ASM实现这一目标。



Answer 2:

进一步的研究之后,我发现了一个起点(感谢@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实施,使异常的即时录制。

[更新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;
}


Answer 3:

我这种方式解决了这个难题。 我用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());
    }
}


文章来源: Using Instrumentation to record unhandled exception