使用 ASM 选择和修改 `if` 语句
Selecting and modifying `if` statement with ASM
我想在不更改整个方法的情况下更新特定行上已经存在的 class 中的 if
语句。
这是目标代码(classes、方法和一些代码的名称已更改,因为它们不相关):
public class Target extends Something {
public Target(){
super();
//some code...
}
public Result targetMethod(Data firstPar, Data secondPar){
if(someStatement()) {
return Result.FAIL;
} else {
if(firstPar.check()){ //here is the line I want to change
firstPar.doSomething()
}
secondPar.doSomething();
return Result.SUCCESS;
}
}
}
在此代码中,我想将 if(firstPar.check())
更改为如下内容:
if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar))
这是我尝试解决此问题的方法:
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(bytes); //bytes - original target class
reader.accept(node, 0);
ClassWriter cw = new ClassWriter(0);
node.accept(cw);
MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, "targetMethod", "<it's signature>", null, null);
visitor.visitCode();
//No idea what to do next
不明白的地方:
如何在 MethodVisitor 中正确选择一行?
如何只更改 if
语句的一半?
如何获取新 class 的字节码(将生成
在我们改变目标后 class)?
如果您能提供某种示例代码,那就太好了。
谢谢!
问题 1 可能是最难的。您需要通过识别某种模式来找出插入说明的位置。如果你假设 firstPar.check()
只被调用一次,那么你可以为 if(firstPar.check())
寻找以下字节码指令:
ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
其中L3
是跳转标签if check
return false
.
对于问题 2,请注意 if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar))
的字节码指令是:
ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
ALOAD 1
ALOAD 2
INVOKESTATIC Utilities.someOtherCheck (LData;LData;)Z
IFNE L3
因此,您需要在 IFEQ L3
之后插入 4 条新指令。
您可以使用 Tree API 来完成此操作,您可以在其中通过子类化 ClassVisitor
和 MethodNode
:
为 targetMethod
创建一个适配器
private static class ClassAdapter extends ClassVisitor {
public ClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("targetMethod"))
return new MethodAdapter(access, name, desc, signature, exceptions, mv);
else
return mv;
}
}
private static class MethodAdapter extends MethodNode {
public MethodAdapter(int access, String name, String desc,
String signature, String[] exceptions, MethodVisitor mv) {
super(Opcodes.ASM5, access, name, desc, signature, exceptions);
this.mv = mv;
}
// More to come ...
}
在MethodAdapter
里面,你可以重写visitEnd
来遍历方法里面的所有instructions
,并尝试检测上面的3条指令,并在它们之后插入4条新指令:
@Override
public void visitEnd() {
// Iterates all instructions in the method
ListIterator<AbstractInsnNode> itr = instructions.iterator();
while (itr.hasNext()) {
// Checks whether the instruction is ALOAD 1
AbstractInsnNode node = itr.next();
if (node.getOpcode() != Opcodes.ALOAD
|| ((VarInsnNode) node).var != 1)
continue;
// Checks whether the next instruction is INVOKEVIRTUAL
if (node.getNext() == null
|| node.getNext().getOpcode() != Opcodes.INVOKEVIRTUAL)
continue;
// Checks the invoked method name and signature
MethodInsnNode next = (MethodInsnNode) node.getNext();
if (!next.owner.equals("Data")
|| !next.name.equals("check")
|| !next.desc.equals("()Z"))
continue;
// Checks whether the next of the next instruction is IFEQ
AbstractInsnNode next2 = next.getNext();
if (next2 == null
|| next2.getOpcode() != Opcodes.IFEQ)
continue;
// Creates a list instructions to be inserted
InsnList list = new InsnList();
list.add(new VarInsnNode(Opcodes.ALOAD, 1));
list.add(new VarInsnNode(Opcodes.ALOAD, 2));
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"Utilities", "someOtherCheck",
"(LData;LData;)Z", false));
list.add(new JumpInsnNode(Opcodes.IFNE, ((JumpInsnNode) next2).label));
// Inserts the list, updates maxStack to at least 2, and we are done
instructions.insert(next2, list);
maxStack = Math.max(2, maxStack);
break;
}
accept(mv);
}
要使用适配器,您可以将它与 ClassReader
和 ClassWriter
链接起来。下面,我也链一个TraceClassVisitor
打印出tmp目录下的日志文件:
ClassReader reader = new ClassReader("Target");
ClassWriter writer = new ClassWriter(reader, 0);
TraceClassVisitor printer = new TraceClassVisitor(writer,
new PrintWriter(System.getProperty("java.io.tmpdir") + File.separator + "Target.log"));
ClassAdapter adapter = new ClassAdapter(printer);
reader.accept(adapter, 0);
byte[] b = writer.toByteArray(); // The modified bytecode
我想在不更改整个方法的情况下更新特定行上已经存在的 class 中的 if
语句。
这是目标代码(classes、方法和一些代码的名称已更改,因为它们不相关):
public class Target extends Something {
public Target(){
super();
//some code...
}
public Result targetMethod(Data firstPar, Data secondPar){
if(someStatement()) {
return Result.FAIL;
} else {
if(firstPar.check()){ //here is the line I want to change
firstPar.doSomething()
}
secondPar.doSomething();
return Result.SUCCESS;
}
}
}
在此代码中,我想将 if(firstPar.check())
更改为如下内容:
if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar))
这是我尝试解决此问题的方法:
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(bytes); //bytes - original target class
reader.accept(node, 0);
ClassWriter cw = new ClassWriter(0);
node.accept(cw);
MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, "targetMethod", "<it's signature>", null, null);
visitor.visitCode();
//No idea what to do next
不明白的地方:
如何在 MethodVisitor 中正确选择一行?
如何只更改
if
语句的一半?如何获取新 class 的字节码(将生成 在我们改变目标后 class)?
如果您能提供某种示例代码,那就太好了。
谢谢!
问题 1 可能是最难的。您需要通过识别某种模式来找出插入说明的位置。如果你假设 firstPar.check()
只被调用一次,那么你可以为 if(firstPar.check())
寻找以下字节码指令:
ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
其中L3
是跳转标签if check
return false
.
对于问题 2,请注意 if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar))
的字节码指令是:
ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
ALOAD 1
ALOAD 2
INVOKESTATIC Utilities.someOtherCheck (LData;LData;)Z
IFNE L3
因此,您需要在 IFEQ L3
之后插入 4 条新指令。
您可以使用 Tree API 来完成此操作,您可以在其中通过子类化 ClassVisitor
和 MethodNode
:
targetMethod
创建一个适配器
private static class ClassAdapter extends ClassVisitor {
public ClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("targetMethod"))
return new MethodAdapter(access, name, desc, signature, exceptions, mv);
else
return mv;
}
}
private static class MethodAdapter extends MethodNode {
public MethodAdapter(int access, String name, String desc,
String signature, String[] exceptions, MethodVisitor mv) {
super(Opcodes.ASM5, access, name, desc, signature, exceptions);
this.mv = mv;
}
// More to come ...
}
在MethodAdapter
里面,你可以重写visitEnd
来遍历方法里面的所有instructions
,并尝试检测上面的3条指令,并在它们之后插入4条新指令:
@Override
public void visitEnd() {
// Iterates all instructions in the method
ListIterator<AbstractInsnNode> itr = instructions.iterator();
while (itr.hasNext()) {
// Checks whether the instruction is ALOAD 1
AbstractInsnNode node = itr.next();
if (node.getOpcode() != Opcodes.ALOAD
|| ((VarInsnNode) node).var != 1)
continue;
// Checks whether the next instruction is INVOKEVIRTUAL
if (node.getNext() == null
|| node.getNext().getOpcode() != Opcodes.INVOKEVIRTUAL)
continue;
// Checks the invoked method name and signature
MethodInsnNode next = (MethodInsnNode) node.getNext();
if (!next.owner.equals("Data")
|| !next.name.equals("check")
|| !next.desc.equals("()Z"))
continue;
// Checks whether the next of the next instruction is IFEQ
AbstractInsnNode next2 = next.getNext();
if (next2 == null
|| next2.getOpcode() != Opcodes.IFEQ)
continue;
// Creates a list instructions to be inserted
InsnList list = new InsnList();
list.add(new VarInsnNode(Opcodes.ALOAD, 1));
list.add(new VarInsnNode(Opcodes.ALOAD, 2));
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"Utilities", "someOtherCheck",
"(LData;LData;)Z", false));
list.add(new JumpInsnNode(Opcodes.IFNE, ((JumpInsnNode) next2).label));
// Inserts the list, updates maxStack to at least 2, and we are done
instructions.insert(next2, list);
maxStack = Math.max(2, maxStack);
break;
}
accept(mv);
}
要使用适配器,您可以将它与 ClassReader
和 ClassWriter
链接起来。下面,我也链一个TraceClassVisitor
打印出tmp目录下的日志文件:
ClassReader reader = new ClassReader("Target");
ClassWriter writer = new ClassWriter(reader, 0);
TraceClassVisitor printer = new TraceClassVisitor(writer,
new PrintWriter(System.getProperty("java.io.tmpdir") + File.separator + "Target.log"));
ClassAdapter adapter = new ClassAdapter(printer);
reader.accept(adapter, 0);
byte[] b = writer.toByteArray(); // The modified bytecode