ClassLoader學(xué)習(xí)記錄3
果然啊,內(nèi)容越往后面,就越來越往安全上轉(zhuǎn)移了,轉(zhuǎn)移了有木有,看來得開始制定新的學(xué)習(xí)計劃,這再往后學(xué)沒神馬用啊,或者說短時間用不上。寫完這期,開始找找新的學(xué)習(xí)路線。
這節(jié)介紹的是字節(jié)碼的效驗,嚴(yán)格來講和“ClassLoader學(xué)習(xí)”這個題目有點(diǎn)不貼切,因為介紹的偏重于效驗器(verifier)。不過還是能學(xué)到不少東西。
先回顧一下基本知識,在執(zhí)行一個java文件的時候一般的步驟是:
javac Hello.java -> Hello.class
java Hello
(上面兩行,忘記的或者記不清的,去面壁去!)
大家好,我剛面壁回來,給大家繼續(xù)講解:
如果執(zhí)行命令 javac Hello.java -verbose,我們可以回顧下前面學(xué)習(xí)的知識,打印出的消息是:
D:\code>javac Hello.java -verbose
[解析開始時間 Hello.java]
[解析已完成時間 30ms]
[源文件的搜索路徑: .,C:\Program Files\Java\jdk1.6.0_29\lib\dt.jar,C:\Program Fi
les\Java\jdk1.6.0_29\lib\tools.jar]
[類文件的搜索路徑: C:\Program Files\Java\jdk1.6.0_29\jre\lib\resources.jar,C:\P
rogram Files\Java\jdk1.6.0_29\jre\lib\rt.jar,C:\Program Files\Java\jdk1.6.0_29\j
re\lib\sunrsasign.jar,C:\Program Files\Java\jdk1.6.0_29\jre\lib\jsse.jar,C:\Prog
ram Files\Java\jdk1.6.0_29\jre\lib\jce.jar,C:\Program Files\Java\jdk1.6.0_29\jre
\lib\charsets.jar,C:\Program Files\Java\jdk1.6.0_29\jre\lib\modules\jdk.boot.jar
,C:\Program Files\Java\jdk1.6.0_29\jre\classes,C:\Program Files\Java\jdk1.6.0_29
\jre\lib\ext\dnsns.jar,C:\Program Files\Java\jdk1.6.0_29\jre\lib\ext\localedata.
jar,C:\Program Files\Java\jdk1.6.0_29\jre\lib\ext\sunjce_provider.jar,C:\Program
Files\Java\jdk1.6.0_29\jre\lib\ext\sunmscapi.jar,C:\Program Files\Java\jdk1.6.0
_29\jre\lib\ext\sunpkcs11.jar,.,C:\Program Files\Java\jdk1.6.0_29\lib\dt.jar,C:\
Program Files\Java\jdk1.6.0_29\lib\tools.jar]
[正在裝入 java\lang\Object.class(java\lang:Object.class)]
[正在裝入 java\lang\String.class(java\lang:String.class)]
[正在檢查 Hello]
[正在裝入 java\lang\System.class(java\lang:System.class)]
[正在裝入 java\io\PrintStream.class(java\io rintStream.class)]
[正在裝入 java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[正在裝入 java\io\OutputStream.class(java\io:OutputStream.class)]
[已寫入 Hello.class]
[總時間 234ms]
代碼就是一個Hello World。通過輸出,我們可以看見前面學(xué)過得加載類的過程。
然后使用java Hello就可以輸出Hello World了。
本章的開始部分引入這個命令: java -noverify Hello。
該命令特別之處就是在執(zhí)行Hello的時候使用非正式的-noverify選項來鈍化效驗。
那么,我們就先來看看效驗,都要效驗什么東西:
•變量要在使用之前進(jìn)行初始化。
•方法調(diào)用與對象引用類型之間要匹配。
•訪問私有數(shù)據(jù)和方法的規(guī)則沒有被違反。
•對本地變量的訪問都在運(yùn)行時堆棧內(nèi)。
•運(yùn)行時堆棧沒有溢出。
如果以上這些檢查中任何一條沒有通過,那么該類就被認(rèn)為遭到了破壞,并且不予加載。
肯定很多人有疑問了,在javac Hello.java的過程中,如果你違反了規(guī)則,就不會得到Hello.class,(這事編譯器都做了)那還要效驗器來干嘛?因為,一個具有匯編程序經(jīng)驗的人,可以很容易的制造出這樣的字節(jié)碼出來,然后送給虛擬機(jī)來執(zhí)行。所以效驗器是很有必要的,要記住,效驗器總是在防范被故意篡改的類文件,而不僅僅只是檢查編譯器產(chǎn)生的類文件。
為了深入了解效驗器的工作,也為了了解java字節(jié)碼的載入原理,我們繼續(xù)來看下面的代碼:- static int fun(){
- int m;
- int n;
- int m = 1;
- int n = 2;
- int r = m + n;
- return r;
- }
復(fù)制代碼 這個方法被編譯以后會生成16進(jìn)制的.class文件。
要了解編譯后的數(shù)據(jù),我們要借助javap工具,來查看助記(mnemonic)格式的字節(jié)碼。
javap -c VerifierTest
得到:
0 iconst_1
1 istore_0
2 iconst_2
3 istore_1
4 iload_0
5 iload_1
6 iadd
7 istore_2
8 iload_2
9 ireturn
那么如果我們把int n = 2 替換成 int m = 2,也就是istore_1改成istore_0; 使得n沒有被初始化。我們就要修改16進(jìn)制的class文件。先列出代碼對應(yīng)的16進(jìn)制數(shù):
0 iconst_1 04
1 istore_0 3B
2 iconst_2 05
3 istore_1 3C
4 iload_0 1A
5 iload_1 1B
6 iadd 60
7 istore_2 3D
8 iload_2 1C
9 ireturn AC
使用16進(jìn)制數(shù)編輯器,將3C改為3B。這樣執(zhí)行VerifierTest程序,我們會得到j(luò)ava.lang.VerifyError的錯誤。
沒錯,虛擬機(jī)發(fā)現(xiàn)了我們的修改。
如果加入剛才的java -noverify VerifierTest參數(shù),那么程序就可以產(chǎn)生輸出了,為15102330,一個隨機(jī)數(shù),典型的輸出錯誤。
所以,可以看出效驗器果然給我們提供了很好的安全機(jī)制。
|