程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 一個通不過Java字節碼校驗的例子

一個通不過Java字節碼校驗的例子

編輯:關於JAVA

一般我們寫Java源碼,用Java編譯器編譯出.class文件,是不會碰到校驗失敗的狀況的,因為正常的 Java編譯器都會小心對待生成的代碼。所以,想要看到校驗失敗的狀況,很容易的一個辦法就是自己生成 不合法的字節碼。

這裡我用了ObjectWeb的ASM來生成字節碼。可以從官網下載asm-3.1.jar,並保證其在編譯和運行下面 這個程序時在classpath上。

(本來是很想順便試試Charles O. Nutter寫的bitescript庫,不過惰性上來了,懶得去下載……下次 吧,下次)

Java代碼

import java.io.FileOutputStream;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class TestASM implements Opcodes {
    public static void main(String[] args) throws Exception {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(
            V1_5,               // class format version
            ACC_PUBLIC,         // class modifiers
            "TestVerification", // class name fully qualified name
            null,               // generic signature
            "java/lang/Object", // super class fully qualified name
            new String[] { }    // implemented interfaces
        );
        
        MethodVisitor mv = cw.visitMethod(
            ACC_PUBLIC, // access modifiers
            "foo",      // method name
             "()V",     // method description
             null,      // generic signature
             null       // exceptions
        );
        mv.visitCode();
        mv.visitInsn(FCONST_0);
        mv.visitVarInsn(FSTORE, 1);
        mv.visitVarInsn(ILOAD, 1);
        mv.visitVarInsn(ISTORE, 1);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 2);
        mv.visitEnd(); // end method
        
        cw.visitEnd(); // end class
        
        byte[] clz = cw.toByteArray();
        FileOutputStream out = new FileOutputStream("TestVerification.class");
        out.write(clz);
        out.close();
    }
}

運行該程序後,得到的是TestVerification.class文件,其內容是:

Java bytecode代碼

public class TestVerification extends java.lang.Object
  minor version: 0
  major version: 49
  Constant pool:
const #1 = Asciz        TestVerification;
const #2 = class        #1;     //  TestVerification
const #3 = Asciz        java/lang/Object;
const #4 = class        #3;     //  java/lang/Object
const #5 = Asciz        foo;
const #6 = Asciz        ()V;
const #7 = Asciz        Code;

{
public void foo();
  Code:
   Stack=1, Locals=2, Args_size=1
   0:   fconst_0
   1:   fstore_0
   2:   iload_0
   3:   istore_0
   4:   return

}

可以看到,foo()裡代碼先把float類型的常量0.0壓到求值棧上(fconst_0),然後將它彈出並保存到 局部存儲區的第一格(fstore_0)。接下來從局部存儲區的第一格取出一個int類型的值壓到求值棧上 (iload_0),再將其彈出並再次保存到局部存儲區的第一格(istore_0)。

要是硬要用Java來表示這個.class文件裡的邏輯,大概類似這樣:

Java代碼

public class TestVerification {
    public void foo() {
        float a = 0;
        ((int) a) = a;
    }
}

很明顯這沒辦法用Java表達出來……

上面代碼中,局部存儲區的第一格就在同一個方法裡前後用於保存了float和int類型的值,破壞了類 型安全。JVM為了保證類型安全,要求局部存儲區裡每格只能保存固定類型的值,load/store與對應的格 的類型必須匹配。

運行這個程序會導致校驗錯誤:

Command prompt代碼

D:\temp_code>java TestVerification
Exception in thread "main" java.lang.VerifyError: (class: TestVerification, method: foo 

signature: ()V) Register 1 contains wrong type
Could not find the main class: TestVerification.  Program will exit.

於是JVM的類加載器先抱怨校驗失敗,然後拒絕加載這個main()方法,後面就連鎖抱怨找不到main()方 法。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved