Java ASM在字节码方法内联期间重新映射变量
我正在使用ASM进行在线字节码方法内联优化。我的更改基于示例Java ASM在字节码方法内联期间重新映射变量,java,inline,bytecode,java-bytecode-asm,bytecode-manipulation,Java,Inline,Bytecode,Java Bytecode Asm,Bytecode Manipulation,我正在使用ASM进行在线字节码方法内联优化。我的更改基于示例3.2.6内联方法()。测试示例(调用方::test处内联被调用方的计算(int,int)是: 基于ASM 5.0版本,我的代码是: //MainInliner.java public class MainInliner extends ClassLoader{ public byte[] generator(String caller, String callee) throws ClassNotFoundException
3.2.6内联方法()。测试示例(调用方::test处内联被调用方的计算(int,int)是:
基于ASM 5.0版本,我的代码是:
//MainInliner.java
public class MainInliner extends ClassLoader{
public byte[] generator(String caller, String callee) throws ClassNotFoundException{
String resource = callee.replace('.', '/') + ".class";
InputStream is = getResourceAsStream(resource);
byte[] buffer;
// adapts the class on the fly
try {
resource = caller.replace('.', '/')+".class";
is = getResourceAsStream(resource);
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(0);
ClassVisitor visitor = new BCMerge(Opcodes.ASM5, cw, callee);
cr.accept(visitor, 0);
buffer= cw.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException(caller, e);
}
// optional: stores the adapted class on disk
try {
FileOutputStream fos = new FileOutputStream("/tmp/data.class");
fos.write(buffer);
fos.close();
} catch (IOException e) {}
return buffer;
}
@Override
protected synchronized Class<?> loadClass(final String name,
final boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) {
System.err.println("Adapt: loading class '" + name
+ "' without on the fly adaptation");
return super.loadClass(name, resolve);
} else {
System.err.println("Adapt: loading class '" + name
+ "' with on the fly adaptation");
}
String caller = "code.sxu.asm.example.Caller";
String callee = "code.sxu.asm.example.Callee";
byte[] b = generator(caller, callee);
// returns the adapted class
return defineClass(caller, b, 0, b.length);
}
public static void main(final String args[]) throws Exception {
// loads the application class (in args[0]) with an Adapt class loader
ClassLoader loader = new MainInliner();
Class<?> c = loader.loadClass(args[0]);
Method m = c.getMethod("main", new Class<?>[] { String[].class });
}
}
class BCMerge extends ClassVisitor{
String _callee;
String _caller;
public BCMerge(int api, ClassVisitor cv, String callee) {
super(api, cv);
// TODO Auto-generated constructor stub
_callee = callee.replace('.', '/');
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this._caller = name;
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if(!name.equals("test")){
return super.visitMethod(access, name, desc, signature, exceptions);
}
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
ClassReader cr = null;
try {
cr = new ClassReader(this.getClass().getClassLoader().getResourceAsStream(_callee.replace('.', '/')+".class"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
MethodNode inlinedMethod = null;
for(MethodNode node: classNode.methods){
if(node.name.equals("calculate")){
inlinedMethod = node;
break;
}
}
return new MethodCallInliner(access, desc, mv, inlinedMethod, _callee, _caller );
}
}
//MethodCallInliner.java
public class MethodCallInliner extends LocalVariablesSorter {
private final String oldClass;
private final String newClass;
private final MethodNode mn; //Method Visitor wrappers the mv.
private List blocks = new ArrayList();
private boolean inlining;
public MethodCallInliner(int access, String desc, MethodVisitor mv, MethodNode mn,
String oldClass, String newClass){
super(Opcodes.ASM5, access, desc, mv);
this.oldClass = oldClass;
this.newClass = newClass;
this.mn = mn;
inlining = false;
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
System.out.println("opcode:" + opcode + " owner:" + owner + " name:"
+ name + " desc:" + desc);
if (!canBeInlined(owner, name, desc)) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
//if it is INVOKEVIRTUAL ../Callee::calculate(II), then..
Remapper remapper = new SimpleRemapper(oldClass, newClass);
Label end = new Label();
inlining = true;
mn.instructions.resetLabels();
mn.accept(new InliningAdapter(this,opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,remapper, end));
inlining = false;
super.visitLabel(end);
}
private boolean canBeInlined(String owner, String name, String decs){
if(name.equals("calculate") && owner.equals("code/sxu/asm/example/Callee")){
return true;
}
return false;
}
}
//InliningAdapter.java
public class InliningAdapter extends RemappingMethodAdapter {
private final LocalVariablesSorter lvs;
private final Label end;
public InliningAdapter(LocalVariablesSorter mv,
int acc, String desc,Remapper remapper, Label end) {
super(acc, desc, mv, remapper);
this.lvs = mv;
this.end = end;
// int offset = (acc & Opcodes.ACC_STATIC)!=0 ?0 : 1;
// Type[] args = Type.getArgumentTypes(desc);
// for (int i = args.length-1; i >= 0; i--) {
// super.visitVarInsn(args[i].getOpcode(
// Opcodes.ISTORE), i + offset);
// }
// if(offset>0) {
// super.visitVarInsn(Opcodes.ASTORE, 0);
// }
}
public void visitInsn(int opcode) {
if(opcode==Opcodes.RETURN || opcode == Opcodes.IRETURN) {
super.visitJumpInsn(Opcodes.GOTO, end);
} else {
super.visitInsn(opcode);
}
}
public void visitMaxs(int stack, int locals) {
System.out.println("visit maxs: "+stack+" "+locals);
}
protected int newLocalMapping(Type type) {
return lvs.newLocal(type);
}
}
data.class上的javap结果显示Callee::calculate的主体已插入到正确的位置(Caller::test line::15)。然而,有两个主要问题:
- invokevirtual之前的前三个堆栈对象
被调用方::计算(第15行)
9:aload_0
10:getfield#14//Field(被调用方):Lcode/sxu/asm/example/callee;
13:iload_1
14:iload_2
内联后不应在堆栈上
- 复制的正文(Callee::calculate())中的变量编号0应映射到正确的编号
- 变量编号不正确。首先,data.class(从第15行到第42行)中复制的Callee::calculate主体的变量号应该从5开始(而不是0)。其次,Callee::calculate()之后的变量号应按照以下规则重新编号:a)如果介于(0,4)之间,则不更改;b)如果与Callee::calculate()复制的正文中的数字冲突,则重新编号
我检查了基类LocalVariablesSorter
的实现。问题似乎在于其结构:
protected LocalVariablesSorter(final int api, final int access,
final String desc, final MethodVisitor mv) {
super(api, mv);
Type[] args = Type.getArgumentTypes(desc);
nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (int i = 0; i < args.length; i++) {
nextLocal += args[i].getSize();
}
firstLocal = nextLocal;
}
private int[] mapping = new int[40];
Update1:取消InliningAdapter的构造函数注释后的data.class:
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #14 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: istore_2 //should be istore 5
16: istore_1 //should be istore 6
17: astore_0 //should be astore 7
18: aload_0
19: getfield #40 // Field _a:Ljava/lang/String;
22: invokevirtual #46 // Method java/lang/String.length:()I
25: aload_0
26: getfield #49 // Field _b:Ljava/lang/String;
29: invokevirtual #46 // Method java/lang/String.length:()I
32: iadd
33: istore 6
35: iload 6
37: iload_1
38: iload_2
39: iadd
40: iadd
41: istore 6
43: iload 6
45: goto 48
48: isub
49: istore 6
51: iload_3
52: iload 4
54: isub
55: istore 7
57: getstatic #59
新存储的三个变量(15,16,17)应编号为5,6,7,而不是2,1,0,内联代码中*store/*load
中的映射应如下
0 => 7
1 => 6
2 => 5
3 => 8
4 => 9 ...
这些映射应该位于数组中:LocalVariablesSorter::mapping
,该数组由LocalVariablesSorter::remap()方法更新。但是,我似乎不可能将它们插入到mapping
数组中
应进行两种类型的重新映射:
- 重新映射内联代码(从第18行到第45行)和变量的内部
索引从5开始。最大索引是k
- 内联代码后重新映射(从第46行到末尾),如果原始索引大于5,则应重新映射任何变量索引(新索引从k+1开始)
正如@Holger已经建议的那样,首先取消对IningAdapter
中行的注释
为了解决您列出的主要问题:LocalVariablesSorter
(由InliningAdapter
扩展)认为参数已经存储在固定位置的局部变量中-这确实是输入方法时的正常情况。所以它根本不映射这些(请参见LocalVariablesSorter.remap()中的第一行-firstLocal
在构造函数中计算)。然而,在这种情况下,我们得到的却是堆栈上的参数,需要手动分配局部变量。解决方案是告诉LocalVariablesSorter
本地变量中没有存储参数(使firstLocal=0
)。然后,它会将对它们的任何引用视为新变量,并为它们分配新的局部变量。这可以通过愚弄LocalVariablesSorter
来实现,认为没有参数,方法是静态的(即使它实际上不是)。因此,我们将InliningAdapter中的第一行从
super(acc, desc, mv, remapper);
到
现在变量0,1,2,。。。重新映射到5,6,7,。。。或者类似的(不管它们是什么,调用方的LocalVariablesSorter
(即MethodCallInliner
实例)负责分配它们)
还有一个问题是,通过使用InlineAdapter
扩展RemappingMethodAdaptor
,将被调用方
类映射到调用方
——但是我想您希望将a
和\u b
变量存储在被调用方
实例中
- 如果我的猜测是正确的,那么您实际上不应该将引用从
被调用方
重新映射到调用方
。您只需将InliningAdapter
extendlocalvariablessorter
改为并去掉重新映射即可
- 如果我的猜测不正确,那么我猜您可能需要将
Callee
的变量也嵌入Caller
中,在这种情况下,您应该保留现有的RemappingMethodAdaptor
调试内联代码时,被调用方
的行号没有意义,因为代码内联到调用方
类中。因此,Caller
中的所有行号可能都应该替换为发生内联调用的Caller
中的行号。不幸的是,在Java中,您不能逐行指定不同的源代码文件(例如,您可以在C中这样做)。因此,您可以使用类似这样的方法覆盖InliningAdapter
中的visitLineNumber()
(InliningAdapter
将被传递给InliningAdapter
的构造函数):
。。或者干脆跳过超级电话,我不是100%确定
您的代码缺少您注释掉的部分。这是从堆栈中弹出参数并将它们放入与参数和内联代码的接收者匹配的局部变量的部分。如果不这样做,堆栈上将出现悬空值,并且this
的局部变量与内联方法的参数不匹配,正如您所描述的。感谢@Holger,您是正确的,这些代码在开始时由于变量索引错误而被注释掉。有关问题解释,请参阅本文末尾的update1:无法在LocalVariablesSorter::rmapping
arrayAct上保留映射
0: iload_1
1: istore_3
2: iload_2
3: istore 4
5: iload_3
6: iload 4
8: iadd
9: aload_0
10: getfield #14 // Field _callee:Lcode/sxu/asm/example/Callee;
13: iload_1
14: iload_2
15: istore_2 //should be istore 5
16: istore_1 //should be istore 6
17: astore_0 //should be astore 7
18: aload_0
19: getfield #40 // Field _a:Ljava/lang/String;
22: invokevirtual #46 // Method java/lang/String.length:()I
25: aload_0
26: getfield #49 // Field _b:Ljava/lang/String;
29: invokevirtual #46 // Method java/lang/String.length:()I
32: iadd
33: istore 6
35: iload 6
37: iload_1
38: iload_2
39: iadd
40: iadd
41: istore 6
43: iload 6
45: goto 48
48: isub
49: istore 6
51: iload_3
52: iload 4
54: isub
55: istore 7
57: getstatic #59
0 => 7
1 => 6
2 => 5
3 => 8
4 => 9 ...
super(acc, desc, mv, remapper);
super(acc | Opcodes.ACC_STATIC, "()V", mv, remapper);
@Override
public void visitLineNumber(int line, Label start) {
super.visitLineNumber(inlinedLine, start);
}