【问题标题】:How to monitor object creation using java agent and ASM?如何使用 java 代理和 ASM 监控对象创建?
【发布时间】:2020-06-22 02:56:56
【问题描述】:

我想做的是监控对象的创建并为该对象记录一个唯一的 ID。

首先我尝试监视NEW 指令,但它无法工作并抛出VerifyError: (...) Expecting to find object/array on stack。听说NEW之后的对象是未初始化的,所以不能传给其他方法。所以我放弃了这种方法。

其次,我尝试监听<init>的调用,这个方法初始化了未初始化的对象。但是我不确定初始化之后,初始化的对象是否会被压栈?

在我的方法访问者适配器中:

public void visitMethodInsn(int opc, String owner, String name, String desc, boolean isInterface) {
    ...
    mv.visitMethodInsn(opc, owner, name, desc, isInterface);
    if (opc == INVOKESPECIAL && name.equals("<init>")) {
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESTATIC, "org/myekstazi/agent/PurityRecorder", "object_new",
                "(Ljava/lang/Object;)V", false);
    }
}

MyRecorder.java:

public static void object_new(Object ref){
    log("object_new !");
    log("MyRecorder: " + ref);
    log("ref.getClass().getName(): " + ref.getClass().getName());
}

我在演示中尝试过它们,它抛出VerifyError

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Operand stack underflow
Exception Details:
  Location:
    AbstractDemo.<init>()V @4: dup
  Reason:
    Attempt to pop empty stack.
  Current Frame:
    bci: @4
    flags: { }
    locals: { 'AbstractDemo' }
    stack: { }
  Bytecode:
    0x0000000: 2ab7 0001 59b8 003b b1

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.privateGetMethodRecursive(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

它似乎也无法正常工作。是否有其他方法可以监控对象创建?

【问题讨论】:

标签: java jvm instrumentation java-bytecode-asm javaagents


【解决方案1】:

消息部分

Location:
  AbstractDemo.<init>()V @4: dup

提示:您正在检测构造函数。在构造函数中,invokespecial &lt;init&gt; 也用于委托给同一个类或超类中的另一个构造函数。

调用另一个构造函数的典型顺序是aload_0(this),push arguments,invokespecial &lt;init&gt;,因此调用后堆栈上没有对象的引用。

VerifyError 的解码字节码如下所示:

  0 aload_0
  1 invokespecial   [1]
  4 dup
  5 invokestatic    [59]
  8 return

通常,您不想报告这些委托构造函数调用,因为它们会导致多次报告同一个对象。但是识别它们可能很棘手,因为接收器类不是可靠的标准。例如,以下是有效的 Java 代码:

public class Example {
    Example reference;
    Example(Example anotherObject) {
        reference = anotherObject;
    }
    Example() {
        this(new Example(null));
        reference.reference = new Example(this);
    }
}

在这里,我们有一个构造函数,其中包含三个具有相同目标类的invokespecial 指令,并且委托构造函数调用既不是第一个也不是最后一个,因此指令本身没有易于检查的属性告诉您这。您必须将提供指令的目标标识为索引为零的aload,即this,以了解指令是否正在初始化当前实例,这在中间有提供指令的参数时是不平凡的。

也就是说,即使在构造函数之外,也不能保证新实例化的对象在堆栈上。当实例化在随后存储或使用结果的表达式上下文中使用时,通常是这种情况,但不在语句上下文中。换句话说,对于像

这样的方法
void test() {
    new Example();
}

简单的编译器实现(如javac)可能会生成表达式代码的等价物,然后是pop指令,但其他实现(如ecj)在这种情况下可以省略前面的dup,从而消除需要后续的pop,因为在invokespecial &lt;init&gt; 指令之后,堆栈上不会有任何引用。

更安全的方法是搜索以new 开头并通向invokespecial &lt;init&gt; 的指令序列(允许嵌套出现)。然后,在new 指令之后注入dup,在invokespecial 之后注入invokestatic

【讨论】:

    【解决方案2】:

    由于 Holger 的回答中描述的原因,在实例化站点插入新对象可能非常棘手。为了检测对象分配,代理通常采用另一种方式 - 它们修改 Object() 构造函数,因为所有普通构造函数最终都会通过 super() 构造函数链调用 Object()

    但是,这不会捕获所有对象分配。如果您关心数组,您还需要检测 newarrayanewarraymultianewarray 字节码。

    此外,本机代码和 JVM 本身可以在不调用构造函数的情况下创建或克隆对象。这需要与 JVM TI 单独处理。

    欲了解更多信息,请查看this question »

    【讨论】:

    • 重要提示:在检测Object() 时,您需要过滤器,因为检测很可能会导致对象创建本身。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-05
    • 1970-01-01
    相关资源
    最近更新 更多