6

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

  • 几乎没有写任何日志语句
  • 异常处理不佳

这使得很难追踪功能损坏的根本原因。

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

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

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当值为null 时,此代码会吃掉search,我从不知道此异常并且应用程序因其他原因而失败。

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

  • 异常类型
  • 异常堆栈跟踪
  • 方法和类名

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

[更新1]

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

如何检测 java 系统类?

任何指导都会非常有帮助。

4

3 回答 3

4

我认为您应该使用 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);
 }

您在特定包中转换类。这些类包含的代码尤其会引发异常。

和这里

 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 实现这一目标。

于 2013-11-29T11:05:59.803 回答
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类的构造函数中。

这是我的变压器课,

公共类 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;
}
于 2013-11-29T10:39:12.217 回答
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());
    }
}
于 2018-06-05T09:31:07.803 回答