使用java ASM代理,如何检测对象中的集合修改?

问题描述 投票:0回答:1

我正在透明肮脏检测代理(tdd-agent)中工作。它可以很好地重新定义目标类来实现 setDirty()/isDirty() 并在检测到放置字段时设置它,但我想扩展它以检测对集合中 add() 的调用,例如。 我怎样才能检测到该呼叫?

当使用复杂参数进行调用时,生成的 asm 将字段和对方法的最终调用分开,我不知道如何检测和处理它。

例如:

private List<Foo> listaFoo = new ArrayList<>();
public void testNativeCollections(){

        this.listaFoo.add(new Foo("text").setS("otro text"));
        
}

这个块生成这个asm:

 aload 0 // reference to self
 getfield test/OuterTarget.listaFoo:java.util.List
 new test/Foo
 dup
 ldc "text" (java.lang.String)
 invokespecial test/Foo.<init>(Ljava/lang/String;)V
 ldc "otro text" (java.lang.String)
 invokevirtual test/Foo.setS(Ljava/lang/String;)Ltest/Foo;
 invokeinterface java/util/List.add(Ljava/lang/Object;)Z
 pop

我需要将第一个 getfield 与最后一个调用接口配对,以检测此调用将修改对象,并在其之后插入对 setDirty() 的调用。主要问题是 getfield/invoke 之间的代码可能任意长且复杂。

java java-bytecode-asm
1个回答
0
投票

你必须构建java自己的内部(例如,它的私有API,但是,java是开源的,就是这样)静态stackwalk分析代码,这是极其复杂的。一个好处是字节码生成已更改为更适合它(例如,

RET
不再由
javac
生成,主要是因为这个原因)。

您需要将存在的每个操作码(而且有很多)映射到它在堆栈上的效果。例如,

ALOAD_0
(ASM 至少有助于扁平化;
ALOAD_0
ALOAD_1, 
ALOAD_2
, 
ALOAD_3
, and 
ALOAD 常量
are all flattened into just
aload` 有点帮助)具有弹出任何内容并推送 1 的效果对象入栈。

进行分析:

          // stack at 0
aload 0   // stack at 1
getfield  // pops 1, then pushes value. stack at 1, remember: stack[0] = listaFoo
new test/Foo // stack at 2
dup          // stack at 3
ldc "text" (java.lang.String) //stack at 4
invokespecial test/Foo.<init>(Ljava/lang/String;)V // stack at 2
ldc "otro text" (java.lang.String) // stack at 3
invokevirtual test/Foo.setS(Ljava/lang/String;)Ltest/Foo; // stack at 2
invokeinterface java/util/List.add(Ljava/lang/Object;)Z // stack at 1
pop

invoke
的所有风格都需要分析签名以了解它对堆栈有什么影响(它拥有的每个参数都有-1堆栈,除了
invokestatic
之外,所有其他版本都为隐式接收者参数额外提供-1堆栈 - 然后+1如果其返回类型不是
V
,则堆栈。

在这里,您的分析器应该能够注册最后一个

invokeinterface
是将您感兴趣的字段从堆栈中弹出作为
add
方法的接收者,从而将其限定为“脏”标志。

我认为不存在一个公开的库,并且以用于这样的分析的方式进行维护,但是,我还没有看那么多 - 也许现在你知道要寻找什么了。

JVM 在加载类时会自行进行此类分析。例如,如果您有以下字节码:

ALOAD_0
DUP
GETFIELD someIntField
IADD

验证器将用

VerifyError
中止,整个类甚至永远不会被加载:它分析了
IADD
指令,该指令从堆栈中弹出 2 个东西,如果它们都是整数并且压入,则 [A] 添加它们回到上面,或者 [B] 如果它们不是都是 int 值,那么一切都会崩溃 - 并且它分析这是 B 情况,因为堆栈上的是
this
和一个 int - 而不是 2 个 int。

它是如何做到的?应用相同的原则:分析每条指令并跟踪它对堆栈的作用。在这种情况下,它注册了堆栈上每个事物的类型(

ALOAD_0
- 好吧,
stack[0]
中有一个对象。
DUP
- 好吧,
stack[1]
中有一个对象。
GETFIELD someIntField
- 好吧,现在
stack[1]
中有一个 int。
IADD
- 好的,检查
stack[current]
stack[current-1]
的类型?呃,其中之一是
int
- 验证错误!

你会做同样的事情,除了不跟踪“int”,“object”等,你具体跟踪:“啊,字段这个或那个”,一旦你点击一个方法签名的

INVOKEVIRTUAL
你知道“弄脏”了它的接收器(例如
j/u/List.add(...)Z
),你可以使用堆栈分析来确切地知道它弄脏了什么。

© www.soinside.com 2019 - 2024. All rights reserved.