使用Instrumentation记录未处理的异常

时间:2013-11-29 09:42:48

标签: java aspectj instrumentation javaagents

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

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

这很难追查功能损坏的根本原因。

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

但下一个问题是记录异常。我想在应用程序执行期间抛出的每个异常都扩展我的工具记录。我尝试使用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(..)方法将注入必要的日志语句并且工作正常,但是当有任何异常时它不会变为变换器。

但问题是一些方法处理异常内部(即使没有log / sysout)。

例如:

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

NullPointerException的值为null时,此代码会占用search,我从来不知道此异常,并且应用程序因其他原因而失败。

最终我想要的是记录应用程序抛出的任何异常的机制。以下详细信息将被捕获

  • 例外类型
  • Excpetion Stacktrace
  • 方法和班级名称

我知道有API Thread.setDefaultUncaughtExceptionHandler,但不确定它如何与java检测一起使用。我没有任何来源访问该应用程序。

[更新1]

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

How to instrument java system classes?

任何指导都会有很大帮助。

3 个答案:

答案 0 :(得分:4)

我认为您应该使用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
    
  4. 确保您的javaagent正常工作。检查您的MANIFEST.MF文件是否包含正确的premain声明并启用类转换。


    关于您当前的代码。这里

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

    您在特定包中转换类。这些类包含特别是抛出异常的代码。

    在这里

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

    当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吞下异常时,第二个永远不会碰到它。相反,您应该自己转换现有的catch块,以获取以下代码:

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

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

答案 1 :(得分:3)

经过更多的研究,我找到了一个起点(感谢@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类的构造函数中。

这是我的变压器类,

public class 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;
}

答案 2 :(得分:1)

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