如何用BCEL与bcel-util生成通过字节码验证的Java类?
解决BCEL生成含switch语句类的栈映射帧验证错误
问题背景
你尝试用BCEL生成一个带switch语句的HelloWorld类,目标代码如下:
public class HelloWorld { public static void main(String[] args) { switch (args[0]) { case "Hello": System.out.println("Hello World"); break; default: break; } } }
使用的Build配置:
plugins { id 'java' } repositories { mavenCentral() } dependencies { implementation 'org.apache.bcel:bcel:6.6.1' implementation 'org.plumelib:bcel-util:1.1.16' }
用BCELifier生成HelloWorldCreator类后,运行生成的HelloWorld.class时出现以下验证错误:
Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 51 Exception Details: Location: HelloWorld.main([Ljava/lang/String;)V @8: lookupswitch Reason: Expected stackmap frame at this location. Bytecode: 0x0000000: 2a03 3259 4cb6 0014 ab00 0000 0000 002b 0x0000010: 0000 0001 0426 28b2 0000 0014 2b12 16b6 0x0000020: 001a 9a00 06a7 000e b200 2012 22b6 0028 0x0000030: a700 03b1 at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.privateGetMethodRecursive(Class.java:3048) at java.lang.Class.getMethod0(Class.java:3018) at java.lang.Class.getMethod(Class.java:1784) at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:650) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:632)
即便引入bcel-util并添加栈映射处理逻辑,问题依然存在。
原因分析
问题出在两个核心点:
- 生成方法时调用了
il.dispose();,释放了指令列表资源,导致后续修改类时无法获取有效指令列表,无法生成正确的栈映射帧。 - 栈映射帧处理时机错误,先将方法添加到ClassGen再事后修改,此时指令资源已被释放,处理逻辑无法生效。
修正后的代码
以下是调整后的HelloWorldCreator代码,修复了上述问题:
import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import org.apache.bcel.Const; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.BranchInstruction; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.InstructionConst; import org.apache.bcel.generic.InstructionFactory; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.LOOKUPSWITCH; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.PUSH; import org.apache.bcel.generic.Select; import org.apache.bcel.generic.Type; import org.plumelib.bcelutil.InstructionListUtils; public class HelloWorldCreator extends InstructionListUtils { private InstructionFactory _factory; private ConstantPoolGen _cp; private ClassGen _cg; public HelloWorldCreator() { _cg = new ClassGen("HelloWorld", "java.lang.Object", "HelloWorld.java", Const.ACC_PUBLIC | Const.ACC_SUPER, new String[] {}); _cg.setMajor(52); _cg.setMinor(0); _cp = _cg.getConstantPool(); _factory = new InstructionFactory(_cg, _cp); } private void processMethod(MethodGen mg) { InstructionList il = mg.getInstructionList(); if (il == null) { return; } // 初始化栈映射相关资源 pool = _cp; set_current_stack_map_table(mg, _cg.getMajor()); fix_local_variable_table(mg); build_unitialized_NEW_map(il); update_uninitialized_NEW_offsets(il); create_new_stack_map_attribute(mg); // 更新方法的最大栈和本地变量数 mg.setMaxStack(); mg.setMaxLocals(); mg.update(); remove_local_variable_type_table(mg); } public void create(OutputStream out) throws IOException { createConstructor(); createMainMethod(); JavaClass jc = _cg.getJavaClass(); jc.dump(out); } private void createConstructor() { InstructionList il = new InstructionList(); MethodGen method = new MethodGen(Const.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[] {}, "<init>", "HelloWorld", il, _cp); il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); il.append(_factory.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Const.INVOKESPECIAL)); il.append(InstructionFactory.createReturn(Type.VOID)); // 处理栈映射帧 processMethod(method); _cg.addMethod(method.getMethod()); il.dispose(); } private void createMainMethod() { InstructionList il = new InstructionList(); MethodGen method = new MethodGen(Const.ACC_PUBLIC | Const.ACC_STATIC, Type.VOID, new Type[] { new ArrayType(Type.STRING, 1) }, new String[] { "arg0" }, "main", "HelloWorld", il, _cp); il.append(InstructionFactory.createLoad(Type.OBJECT, 0)); il.append(new PUSH(_cp, 0)); il.append(InstructionConst.AALOAD); il.append(InstructionConst.DUP); il.append(InstructionFactory.createStore(Type.OBJECT, 1)); il.append(_factory.createInvoke("java.lang.String", "hashCode", Type.INT, Type.NO_ARGS, Const.INVOKEVIRTUAL)); Select lookupswitch_8 = new LOOKUPSWITCH(new int[] { 69609650 }, new InstructionHandle[] { null }, null); il.append(lookupswitch_8); InstructionHandle ih_28 = il.append(InstructionFactory.createLoad(Type.OBJECT, 1)); il.append(new PUSH(_cp, "Hello")); il.append(_factory.createInvoke("java.lang.String", "equals", Type.BOOLEAN, new Type[] { Type.OBJECT }, Const.INVOKEVIRTUAL)); BranchInstruction ifne_34 = InstructionFactory.createBranchInstruction(Const.IFNE, null); il.append(ifne_34); BranchInstruction goto_37 = InstructionFactory.createBranchInstruction(Const.GOTO, null); il.append(goto_37); InstructionHandle ih_40 = il.append(_factory.createFieldAccess("java.lang.System", "out", new ObjectType("java.io.PrintStream"), Const.GETSTATIC)); il.append(new PUSH(_cp, "Hello World")); il.append(_factory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING }, Const.INVOKEVIRTUAL)); BranchInstruction goto_48 = InstructionFactory.createBranchInstruction(Const.GOTO, null); il.append(goto_48); InstructionHandle ih_51 = il.append(InstructionFactory.createReturn(Type.VOID)); lookupswitch_8.setTarget(ih_51); lookupswitch_8.setTarget(0, ih_28); ifne_34.setTarget(ih_40); goto_37.setTarget(ih_51); goto_48.setTarget(ih_51); // 处理栈映射帧 processMethod(method); _cg.addMethod(method.getMethod()); il.dispose(); } public static void main(String[] args) throws Exception { HelloWorldCreator creator = new HelloWorldCreator(); creator.create(new FileOutputStream("HelloWorld.class")); } }
关键修改点
- 调整处理时机:在每个方法的指令构建完成后、添加到ClassGen前,立即处理栈映射帧,确保指令列表处于有效状态。
- 移除事后修改类的逻辑:不再先创建所有方法再批量修改,改为逐个方法生成后即时处理。
- 复用常量池:直接使用类的常量池,避免资源不一致问题。
验证
运行修改后的HelloWorldCreator生成class文件,执行以下命令:
java HelloWorld Hello
会正常输出Hello World,且无验证错误。
内容的提问来源于stack exchange,提问作者Nicolas Baumann
相关产品推荐
相关产品推荐

