Yet another offline Java bytecode verifier
When directly manipulating or generating Java bytecode, it's not too hard to end up with bytecode that will not verify. The JVM will catch this code at runtime. However, the verifier in a JVM exists primarily to ensure secure execution of Java code, not to assist with bytecode-level programming. As a result, the verification error messages printed by a JVM are typically not so useful when trying to figure out why exactly your manipulated/generated bytecode does not verify. This is a well known problem and there already exists a number of standalone verifiers that provide very informative error messages. The [URL="http://asm.ow2.org/index.html"]ASM[/URL] Java bytecode manipulation and analysis framework includes a [URL="http://asm.ow2.org/doc/faq.html#Q4"]builtin[/URL] class verifier as does the [URL="http://jakarta.apache.org/bcel/"]BCEL[/URL] library.
In the [URL="http://wikis.sun.com/display/MaxineVM/Home"]Maxine VM[/URL] project, we use bytecode generation/manipulation as an alternative to writing assembler code. For example, we implement JNI stubs via generated bytecode (using a couple of bytecode extensions specific to Maxine). As such, we run into the usual issues of ensuring that the bytecode we generated was verifiable. Initially, we considered using one of the aforementioned libraries to address this. However, given that our goal is to develop a specification compliant JVM implementation we had to implement a verifier anyway. So, with the help of a sharp intern (thanks [URL="http://abl.med.utah.edu/~dave/"]Dave[/URL]!) we wrote one. Actually, we wrote two - one that performs type inferencing for class files with a version number less than 50 and one that does type checking for more recent class files (those compliant with the class file changes specified in [URL="http://jcp.org/en/jsr/detail?id=202"]JSR202[/URL]). Now we have a verifier that not only passes the relevant JCK tests but is also a development aid whenever we do bytecode-level programming in the VM.
Useful functionality in the Maxine code base that can be made to work standalone is exposed by a the [URL="http://wikis.sun.com/display/MaxineVM/Building+and+Running"]max[/URL] script. So, for anyone wanting yet another offline verifier, this script includes a verify sub-command. Here's how to get the usage message for this command:
~/maxine$ bin/max help verifymax verify [options] patterns...verifies a set methods using the Maxine bytecode verifier Run the Maxine verifier over a set of specified methods available on the class path. To extend the class path, use one of the global "-cp/p:" or "-cp/a:" options. See Patterns below for a description of the format expected for "patterns..." Use "max verify -help" to see what other options this command accepts. --- Patterns --- A pattern is a class name pattern followed by an optional method name...
Here's the output of using it to verify a negative-test (i.e. expected to cause a failure) from the JCK:
~/maxine$ bin/max -cp/a:/Volumes/JCK-runtime-6/classes verify javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn:mInitializing verifier system...Initialized verifier systemFinding specified methods...Found 1 methodsException in thread "main" VerifyError: Missing stackmap frame for bytecode position 26 (branch target) while verifying javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn.m() at bytecode position 1 at com.sun.max.vm.classfile.ErrorContext.verifyError(ErrorContext.java:179) at com.sun.max.vm.verifier.MethodVerifier.verifyError(MethodVerifier.java:124) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.frameAt(TypeCheckingMethodVerifier.java:209) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.performBranch(TypeCheckingMethodVerifier.java:317) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier$Interpreter.lookupswitch(TypeCheckingMethodVerifier.java:1579) at com.sun.max.vm.bytecode.BytecodeScanner.scanInstruction(BytecodeScanner.java:991) at com.sun.max.vm.bytecode.BytecodeScanner.scan(BytecodeScanner.java:1197) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.verifyBytecodes(TypeCheckingMethodVerifier.java:145) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.verify(TypeCheckingMethodVerifier.java:112) at com.sun.max.vm.verifier.TypeCheckingVerifier.verify(TypeCheckingVerifier.java:71) at test.com.sun.max.vm.verifier.CommandLineVerifier.main(CommandLineVerifier.java:106)
To see the abstract interpreter in action, use the -verbose option:
~/maxine$ bin/max -cp/a:/Volumes/JCK-runtime-6/classes verify -verbose=3 javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn:mInitializing verifier system... Initialized verifier systemFinding specified methods...Found 2 methodsVerifying javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn.m()Input bytecode:Stack=1, Locals=10: iconst_1 | 41: lookupswitch default:20 1:26 | 171 0 0 0 0 0 19 0 0 0 1 0 0 0 1 0 0 0 2520: return | 177StackMapTable: number of entries = 1 20: frame_type = 255 /* full_frame */ offset_delta = 20 number_of_locals = 1 locals = [ javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn ] number_of_stack_items = 0 stack = [ ]StackMapTable frames:0: local[0] = javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn20: local[0] = javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tnInterpreting bytecode: local[0] = javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn0: iconst_1 stack[0] = int local[0] = javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn1: lookupswitchException in thread "main" VerifyError: Missing stackmap frame for bytecode position 26 (branch target) while verifying javasoft.sqe.tests.vm.classfmt.ins.instr_006.instr_00601m1t.instr_00601m1tn.m() at bytecode position 1 at com.sun.max.vm.classfile.ErrorContext.verifyError(ErrorContext.java:179) at com.sun.max.vm.verifier.MethodVerifier.verifyError(MethodVerifier.java:122) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.frameAt(TypeCheckingMethodVerifier.java:209) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.performBranch(TypeCheckingMethodVerifier.java:317) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier$Interpreter.lookupswitch(TypeCheckingMethodVerifier.java:1579) at com.sun.max.vm.bytecode.BytecodeScanner.scanInstruction(BytecodeScanner.java:991) at com.sun.max.vm.bytecode.BytecodeScanner.scan(BytecodeScanner.java:1197) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.verifyBytecodes(TypeCheckingMethodVerifier.java:145) at com.sun.max.vm.verifier.TypeCheckingMethodVerifier.verify(TypeCheckingMethodVerifier.java:112) at com.sun.max.vm.verifier.TypeCheckingVerifier.verify(TypeCheckingVerifier.java:71) at test.com.sun.max.vm.verifier.CommandLineVerifier.main(CommandLineVerifier.java:106)
The verify sub-command is available as of version [URL="http://kenai.com/projects/maxine/sources/maxine/revision/4278"]4278[/URL] in the main Maxine repository.
[url=http://blogs.sun.com/dns/entry/yet_another_offline_java_bytecode]Read More about [Yet another offline Java bytecode verifier...[/url]