Java ASM GeneratorAdapter变量命名
Java ASM GeneratorAdapter variable naming
我正在生成一个简单的 class,但无法注入正确的变量名。
ASM 版本为 5.2
.
代码如下:
package com.test;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null);
Method ctor = Method.getMethod("void <init>()");
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, null, null, cw);
mg.visitCode();
mg.loadThis();
mg.invokeConstructor(Type.getType(Object.class), ctor);
int var = mg.newLocal(Type.INT_TYPE);
mg.push(42.42);
mg.storeLocal(var);
Label varLabel = mg.mark();
mg.returnValue();
Label endLabel = mg.mark();
mg.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
mg.endMethod();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);
}
}
我正在使用 GeneratorAdapter
来简化代码生成。由于 GeneratorAdapter
是从 LocalVariablesSorter
继承的,我假设允许使用它的 newLocal(Type)
方法。
除了变量名外,发出的字节码没有任何问题。当调用 visitLocalVariable()
方法时,它不会为变量分配名称,而是在字节码中创建一个新名称。
发出的字节码:
// class version 52.0 (52)
// access flags 0x1
public class com/test/Sub {
// access flags 0x1
public <init>()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
LDC 42.42
DSTORE 1
L0
RETURN
L1
LOCALVARIABLE x D L0 L1 3
MAXSTACK = 2
MAXLOCALS = 5
}
我正在使用 newLocal()
在 visitLocalVariable()
中调用提供的相同变量索引。但是在字节码映射索引中是 3
而不是 1
。如果变量具有 "shorter" 类型,例如 int
,那么索引将是 2
,但仍然不是它应该的 1
。
根据我的观察,发生这种情况的原因如下。 LocalVariablesSorter
维护从旧变量索引到新变量索引的映射。它还会覆盖方法 visitLocalVariable
,并且在将调用委托给访问者链之前,它会根据映射计算 newIndex
。 newIndex
是通过另一个私有方法 remap()
计算得出的。此方法检查给定变量的映射是否已存在,如果不存在,则创建一个新映射。我看到的问题是 newLocal()
方法没有向映射添加任何内容。
我还可以从 ASM 源中看到 GeneratorAdapter
中的 storeInsn()
委托 visitVarInsn()
调用链而不是调用 LocalVariablesSorter
的实现。因为它在 LocalVariablesSorter
实现中,所以为变量索引调用 remap()
方法并更新映射。
因此我的问题是如何使用 GeneratorAdapter
以便在发出的字节码中正确命名变量,或者如何在链中将 GeneratorAdapter
与 LocalVariablesSorter
组合以便它们一起正常工作?
由于 GeneratorAdapter
extends LocalVariablesSorter
,其目的是适应所有访问者调用,所有属于访问者 API 的方法都得到适应,这与引入的专用方法不同GeneratorAdapter
。这种设计允许将新代码插入现有方法,其中旧代码通过访问者报告 API.
因此方法 visitLocalVariable
是访问者 API 的一部分,必须在目标 MethodVisitor
上调用,绕过 LocalVariablesSorter
:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
name.replace('.', '/'), null, "java/lang/Object", null);
Method ctor = Method.getMethod("void <init>()");
MethodVisitor direct = cw.visitMethod(
Opcodes.ACC_PUBLIC, ctor.getName(), ctor.getDescriptor(), null, null);
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, direct);
mg.visitCode();
mg.loadThis();
mg.invokeConstructor(Type.getType(Object.class), ctor);
int var = mg.newLocal(Type.DOUBLE_TYPE);
mg.push(42.42);
mg.storeLocal(var);
Label varLabel = mg.mark();
mg.returnValue();
Label endLabel = mg.mark();
direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
mg.endMethod();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);
因为这可能会造成混淆,这里的替代方案直接在目标 MethodVisitor
上工作,完全没有像 GeneratorAdapter
这样的任何便利包装。它并不复杂,尽管它需要更多的知识,但是,开发人员在处理 Java 字节码和 class 文件时无论如何都应该掌握这些知识……
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
String superClName = "java/lang/Object", ctorName = "<init>", ctorDesc = "()V";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.','/'), null, superClName, null);
MethodVisitor direct = cw.visitMethod(Opcodes.ACC_PUBLIC, ctorName, ctorDesc, null, null);
direct.visitCode();
// "this" is alway 0 (zero) and for parameterless methods the next var location is 1 (one)
int thisVar = 0, var = 1;
direct.visitVarInsn(Opcodes.ALOAD, thisVar);
direct.visitMethodInsn(Opcodes.INVOKESPECIAL, superClName, ctorName, ctorDesc, false);
direct.visitLdcInsn(42.42);
Label varLabel = new Label(), endLabel = new Label();
direct.visitVarInsn(Opcodes.DSTORE, var);
direct.visitLabel(varLabel);
direct.visitInsn(Opcodes.RETURN);
direct.visitLabel(endLabel);
direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
direct.visitMaxs(-1, -1);// no actual values, using COMPUTE_FRAMES
direct.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);
如果您不习惯直接将 ()V
用于无参数 void
方法,您仍然可以像以前一样使用 Method
对象或 Type.getMethodDescriptor(Type.VOID_TYPE)
我正在生成一个简单的 class,但无法注入正确的变量名。
ASM 版本为 5.2
.
代码如下:
package com.test;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null);
Method ctor = Method.getMethod("void <init>()");
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, null, null, cw);
mg.visitCode();
mg.loadThis();
mg.invokeConstructor(Type.getType(Object.class), ctor);
int var = mg.newLocal(Type.INT_TYPE);
mg.push(42.42);
mg.storeLocal(var);
Label varLabel = mg.mark();
mg.returnValue();
Label endLabel = mg.mark();
mg.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
mg.endMethod();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);
}
}
我正在使用 GeneratorAdapter
来简化代码生成。由于 GeneratorAdapter
是从 LocalVariablesSorter
继承的,我假设允许使用它的 newLocal(Type)
方法。
除了变量名外,发出的字节码没有任何问题。当调用 visitLocalVariable()
方法时,它不会为变量分配名称,而是在字节码中创建一个新名称。
发出的字节码:
// class version 52.0 (52)
// access flags 0x1
public class com/test/Sub {
// access flags 0x1
public <init>()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
LDC 42.42
DSTORE 1
L0
RETURN
L1
LOCALVARIABLE x D L0 L1 3
MAXSTACK = 2
MAXLOCALS = 5
}
我正在使用 newLocal()
在 visitLocalVariable()
中调用提供的相同变量索引。但是在字节码映射索引中是 3
而不是 1
。如果变量具有 "shorter" 类型,例如 int
,那么索引将是 2
,但仍然不是它应该的 1
。
根据我的观察,发生这种情况的原因如下。 LocalVariablesSorter
维护从旧变量索引到新变量索引的映射。它还会覆盖方法 visitLocalVariable
,并且在将调用委托给访问者链之前,它会根据映射计算 newIndex
。 newIndex
是通过另一个私有方法 remap()
计算得出的。此方法检查给定变量的映射是否已存在,如果不存在,则创建一个新映射。我看到的问题是 newLocal()
方法没有向映射添加任何内容。
我还可以从 ASM 源中看到 GeneratorAdapter
中的 storeInsn()
委托 visitVarInsn()
调用链而不是调用 LocalVariablesSorter
的实现。因为它在 LocalVariablesSorter
实现中,所以为变量索引调用 remap()
方法并更新映射。
因此我的问题是如何使用 GeneratorAdapter
以便在发出的字节码中正确命名变量,或者如何在链中将 GeneratorAdapter
与 LocalVariablesSorter
组合以便它们一起正常工作?
由于 GeneratorAdapter
extends LocalVariablesSorter
,其目的是适应所有访问者调用,所有属于访问者 API 的方法都得到适应,这与引入的专用方法不同GeneratorAdapter
。这种设计允许将新代码插入现有方法,其中旧代码通过访问者报告 API.
因此方法 visitLocalVariable
是访问者 API 的一部分,必须在目标 MethodVisitor
上调用,绕过 LocalVariablesSorter
:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
name.replace('.', '/'), null, "java/lang/Object", null);
Method ctor = Method.getMethod("void <init>()");
MethodVisitor direct = cw.visitMethod(
Opcodes.ACC_PUBLIC, ctor.getName(), ctor.getDescriptor(), null, null);
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, direct);
mg.visitCode();
mg.loadThis();
mg.invokeConstructor(Type.getType(Object.class), ctor);
int var = mg.newLocal(Type.DOUBLE_TYPE);
mg.push(42.42);
mg.storeLocal(var);
Label varLabel = mg.mark();
mg.returnValue();
Label endLabel = mg.mark();
direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
mg.endMethod();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);
因为这可能会造成混淆,这里的替代方案直接在目标 MethodVisitor
上工作,完全没有像 GeneratorAdapter
这样的任何便利包装。它并不复杂,尽管它需要更多的知识,但是,开发人员在处理 Java 字节码和 class 文件时无论如何都应该掌握这些知识……
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
String superClName = "java/lang/Object", ctorName = "<init>", ctorDesc = "()V";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.','/'), null, superClName, null);
MethodVisitor direct = cw.visitMethod(Opcodes.ACC_PUBLIC, ctorName, ctorDesc, null, null);
direct.visitCode();
// "this" is alway 0 (zero) and for parameterless methods the next var location is 1 (one)
int thisVar = 0, var = 1;
direct.visitVarInsn(Opcodes.ALOAD, thisVar);
direct.visitMethodInsn(Opcodes.INVOKESPECIAL, superClName, ctorName, ctorDesc, false);
direct.visitLdcInsn(42.42);
Label varLabel = new Label(), endLabel = new Label();
direct.visitVarInsn(Opcodes.DSTORE, var);
direct.visitLabel(varLabel);
direct.visitInsn(Opcodes.RETURN);
direct.visitLabel(endLabel);
direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
direct.visitMaxs(-1, -1);// no actual values, using COMPUTE_FRAMES
direct.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);
如果您不习惯直接将 ()V
用于无参数 void
方法,您仍然可以像以前一样使用 Method
对象或 Type.getMethodDescriptor(Type.VOID_TYPE)