ClassLoader学习记录3

本文深入探讨了Java字节码的效验过程,包括效验的内容、目的及如何利用javap工具查看编译后的字节码。通过实例演示了如何修改类文件并触发验证错误,从而展示了验证器在确保类文件安全性和正确性方面的重要作用。
果然啊,内容越往后面,就越来越往安全上转移了,转移了有木有,看来得开始制定新的学习计划,这再往后学没神马用啊,或者说短时间用不上。写完这期,开始找找新的学习路线。
这节介绍的是字节码的效验,严格来讲和“ClassLoader学习”这个题目有点不贴切,因为介绍的偏重于[color=red]效验器(verifier)[/color]。不过还是能学到不少东西。
先回顾一下[color=red]基本知识[/color],在执行一个java文件的时候一般的步骤是:
javac Hello.java -> Hello.class
java Hello
(上面两行,忘记的或者记不清的,去面壁去!)

大家好,我刚面壁回来,给大家继续讲解:
如果执行命令 javac Hello.java -verbose,我们可以回顾下前面学习的知识,[color=red]打印出的消息[/color]是:
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)]
[[color=red]正在检查[/color] Hello]
[正在装入 java\lang\System.class(java\lang:System.class)]
[正在装入 java\io\PrintStream.class(java\io:PrintStream.class)]
[正在装入 java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[正在装入 java\io\OutputStream.class(java\io:OutputStream.class)]
[已写入 Hello.class]
[总时间 234ms]

代码就是一个Hello World。通过输出,我们可以看见前面学过得加载类的过程。
然后使用java Hello就可以输出Hello World了。

本章的开始部分引入这个命令: java [color=red]-noverify[/color] Hello。
该命令特别之处就是在执行Hello的时候使用非正式的-noverify选项来钝化效验。
那么,我们就先来看看效验,都要[color=red]效验什么东西[/color]:
[list]
[*]变量要在使用之前进行初始化。
[*]方法调用与对象引用类型之间要匹配。
[*]访问私有数据和方法的规则没有被违反。
[*]对本地变量的访问都在运行时堆栈内。
[*]运行时堆栈没有溢出。
[/list]
如果以上这些检查中任何一条没有通过,那么该类就被认为遭到了破坏,并且不予加载。

肯定很多人有疑问了,在javac Hello.java的过程中,如果你违反了规则,就不会得到Hello.class,(这事[color=red]编译器[/color]都做了)那还要效验器来干嘛?因为,一个具有汇编程序经验的人,可以很容易的制造出这样的字节码出来,然后送给虚拟机来执行。所以效验器是很有必要的,要记住,效验器总是在防范被故意篡改的类文件,而不仅仅只是检查编译器产生的类文件。

为了深入了解效验器的工作,也为了了解java字节码的载入原理,我们继续来看下面的代码:
static int fun(){
int m;
int n;
int m = 1;
int n = 2;
int r = m + n;
return r;
}
这个方法被编译以后会生成16进制的.class文件。
要了解编译后的数据,我们要借助javap工具,来查看助记(mnemonic)格式的字节码。
javap -c VerifierTest
得到:
[table]
|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
[/table]
那么如果我们把[color=red]int n = 2 替换成 int m = 2[/color],也就是[color=red]istore_1改成istore_0[/color]; 使得n没有被初始化。我们就要修改16进制的class文件。先列出代码对应的16进制数:
[table]
|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
[/table]
使用16进制数编辑器,将[color=red]3C改为3B[/color]。这样执行VerifierTest程序,我们会得到java.lang.VerifyError的错误。
没错,虚拟机发现了我们的修改。
如果加入刚才的java -noverify VerifierTest参数,那么程序就可以产生输出了,为15102330,一个随机数,典型的输出错误。
所以,可以看出效验器果然给我们提供了很好的安全机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值