简体   繁体   English

如何使用 java 代理和 ASM 监控 object 创建?

[英]How to monitor object creation using java agent and ASM?

What I want to do is to monitor the object creation and record a unique ID for that object.我想要做的是监视 object 的创建并记录该 object 的唯一 ID。

Firstly I tried to monitor the NEW instruction but it can not work and throw VerifyError: (...) Expecting to find object/array on stack .首先,我尝试监视NEW指令,但它无法工作并抛出VerifyError: (...) Expecting to find object/array on stack I heard that the object after NEW is uninitialized so it can not be passed to other methods.听说NEW后的object没有初始化所以不能传给其他方法。 So I abandoned this approach.所以我放弃了这种方法。

Secondly, I tried to monitor the invocation of <init> , this method initializes the uninitialized object.其次,我试图监控<init>的调用,这个方法初始化了未初始化的 object。 But I am not sure that after the initialization, if the initialized object will be pushed to the stack?但是我不确定初始化之后,如果初始化的object会被压栈?

In my method visitor adapter:在我的方法访问者适配器中:

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);
    }
}

In MyRecorder.java :MyRecorder.java

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

I tried them in a demo, it throws VerifyError :我在演示中尝试过它们,它抛出了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)

It seems not working as well.它似乎也不起作用。 Are there any alternatives to monitor the object creation?是否有任何替代方法可以监视 object 的创建?

The part of the message消息的一部分

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

hints at it: you are instrumenting a constructor.提示:您正在检测构造函数。 Within a constructor, invokespecial <init> is also used to delegate to another constructor, either in the same class or in the superclass.在构造函数中, invokespecial <init>也用于委托给另一个构造函数,无论是在同一个 class 中还是在超类中。

The typical sequence for calling another constructor is aload_0 (this), push arguments, invokespecial <init> , so there is no reference to the object on the stack after the invocation.调用另一个构造函数的典型顺序是aload_0 (this),push arguments, invokespecial <init> ,所以调用后栈上没有对object的引用。

This is how the decoded bytecode of the VerifyError looks like:这是VerifyError的解码字节码的样子:

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

Normally, you don't want to report these delegating constructor calls, as they would cause reporting the same object multiple times.通常,您不想报告这些委托构造函数调用,因为它们会导致多次报告相同的 object。 But identifying them can be tricky, as the receiver class is not a reliable criteria.但识别它们可能很棘手,因为接收器 class 不是可靠的标准。 Eg, the following is valid Java code:例如,以下是有效的 Java 代码:

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

Here, we have a constructor containing three invokespecial instruction having the same target class and the delegating constructor call is neither the first nor the last one, so there is no simple-to-check property of the instruction itself telling you this.在这里,我们有一个包含三个具有相同目标 class 的invokespecial指令的构造函数,并且委托构造函数调用既不是第一个也不是最后一个,因此指令本身没有简单检查的属性告诉您这一点。 You have to identify the target providing instruction as aload of index zero, ie this , to understand whether an instruction is initializing the current instance, which is nontrivial when there are argument providing instructions in-between.您必须将提供指令的目标标识为索引为零的aload ,即this ,以了解指令是否正在初始化当前实例,当中间有参数提供指令时,这是不平凡的。

That said, even outside the constructor there is no guaranty that the newly instantiated object is on the stack.也就是说,即使在构造函数之外,也不能保证新实例化的 object 在堆栈上。 It is usually the case when the instantiation is used in an expression context where the result is subsequently stored or used, but not in a statement context.当实例化在随后存储或使用结果的表达式上下文中使用时,通常是这种情况,而不是在语句上下文中。 In other words for a method like换句话说,对于像这样的方法

void test() {
    new Example();
}

naive compiler implementations (like javac ) may generate the equivalent to the expression code, followed by a pop instruction, but other implementations (like ecj ) could elide the preceding dup in this case, eliminating the need for the subsequent pop , as no reference will be on the stack after the invokespecial <init> instruction.幼稚的编译器实现(如javac )可能会生成表达式代码的等价物,后跟pop指令,但在这种情况下,其他实现(如ecj )可以省略前面的dup ,从而消除对后续pop的需要,因为没有参考将在invokespecial <init>指令之后在堆栈上。

A safer approach is to search for instruction sequences starting with new and leading to an invokespecial <init> (allowing nested occurrences).一种更安全的方法是搜索以new开头并导致invokespecial <init>的指令序列(允许嵌套出现)。 Then, inject a dup right after the new instruction and the invokestatic after the invokespecial .然后,在new指令之后注入一个dup ,在invokestatic之后注入一个invokespecial

Instrumenting new objects at instantiation site can be quite tricky for the reasons described in Holger's answer.由于 Holger 的回答中描述的原因,在实例化站点检测新对象可能非常棘手。 To instrument object allocations, agents usually go another way - they modify Object() constructor, since all normal constructors end up in calling Object() through a chain of super() constructors.为了检测 object 分配,代理通常 go 以另一种方式 - 他们修改Object()构造函数,因为所有普通构造函数最终都会通过super()构造函数链调用Object()

However, this will not catch all object allocations.但是,这不会捕获所有 object 分配。 If you care about arrays, you'll also need to instrument newarray , anewarray , multianewarray bytecodes.如果您关心 arrays,您还需要检测newarrayanewarraymultianewarray字节码。

Also, the native code and the JVM itself may create or clone objects without calling constructors.此外,本机代码和 JVM 本身可以在不调用构造函数的情况下创建或克隆对象。 This needs to be handled separately with JVM TI.这需要使用 JVM TI 单独处理。

For more information, take a look at this question »有关更多信息,请查看此问题 »

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM