我正在做Java字节码分析。我想不断跟踪局部变量的每个变化状态。这个想法很像调试器。例如,我有一个Java源代码,如
public class Foo {
public void main() {
printOne();
}
public void printOne() {
int i=11110;
String hello="hello";
}
通过使用ASM5.0,我可以访问行号、局部变量名、mehtod名称、var指令等。访问者如下所示
@Override
public void visitLineNumber(int line, Label start) {
System.out.println("line: "+line+" ");
mv.visitLineNumber(line, start);
}
@Override
public void visitLocalVariable(String name, String desc,String signature, Label start, Label end, int index) {
System.out.println("local variable:"+name);
mv.visitLocalVariable(name, desc, signature, start, end, index);
}
@Override
public void visitVarInsn(int opcode, int var) {
System.out.println("var instruction"+Integer.toHexString(opcode));
mv.visitVarInsn(opcode, var);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
System.out.println("method");
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
问题是在哪里注入跟踪器(只是一个静态方法调用),并在堆栈上查找变量值并作为参数传递给跟踪器?
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "path/to/Tracer", "trace", "(Ljava/lang/String;)V",false);
你不能对访问者做你想做的事情。访问者只访问局部变量的声明,这些声明存储在类文件Code
属性的可选LocalVariableTable
属性中(请参阅此处的规范)。这为您提供了局部变量的签名,但没有告诉您它们的值在运行时会发生什么。为此,您需要分析字节码中的load
和store
指令(等等),正如您所说,这将有效地编写调试器。
原则上,您可以访问代码,查找存储到您感兴趣的局部变量索引中的所有操作码,并插入插桩代码以将新值传递给您自己的某个报告方法,但要重新实现调试器已经做的事情,这将是大量工作。
重申:LocalVariableTable
不包含值。它是描述局部变量的元数据——它们的名称、类型等——存储在类文件中,以便于调试器使用。它是一个可选属性,只有在编译时包含调试信息时才会创建。在运行时,正在执行的代码不使用LocalVariableTable
。局部变量值本身存在于堆栈中,在运行时分配。我认为你需要退后一步,想想你正在努力实现什么。你想知道局部变量在运行时采用了什么值。直到运行时才能知道。
您不能将调试器指向类文件并询问“局部变量x的值是多少?”。该信息在类文件中不存在。这甚至不是一个有意义的问题。您必须运行程序才能在其上使用调试器。调试器暂停执行并查看特定时间点执行堆栈上的值。您的asm访问者类正在查看类文件,而不是正在运行的代码或执行堆栈。
您可以知道的是运行时局部变量值在堆栈上的位置的索引。这是传递给局部变量访问者的索引参数。您需要获取该索引参数并分析方法的字节码,以找到在该索引处将值存储到变量中的所有操作码。这些表示方法中局部变量的值可能发生变化的点。然后,您可以插入额外的字节码来调用您自己的报告方法,在指定的索引处传递局部变量的值。然后您必须运行已检测的代码。不用说,这些都不适合胆小的人!