`
nanapoleon
  • 浏览: 46872 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

ClassLoader学习记录3

    博客分类:
  • java
阅读更多
果然啊,内容越往后面,就越来越往安全上转移了,转移了有木有,看来得开始制定新的学习计划,这再往后学没神马用啊,或者说短时间用不上。写完这期,开始找找新的学习路线。
这节介绍的是字节码的效验,严格来讲和“ClassLoader学习”这个题目有点不贴切,因为介绍的偏重于效验器(verifier)。不过还是能学到不少东西。
先回顾一下基本知识,在执行一个java文件的时候一般的步骤是:
javac Hello.java -> Hello.class
java Hello
(上面两行,忘记的或者记不清的,去面壁去!)

大家好,我刚面壁回来,给大家继续讲解:
如果执行命令 javac Hello.java -verbose,我们可以回顾下前面学习的知识,打印出的消息是:
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: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 -noverify Hello。
该命令特别之处就是在执行Hello的时候使用非正式的-noverify选项来钝化效验。
那么,我们就先来看看效验,都要效验什么东西
  • 变量要在使用之前进行初始化。
  • 方法调用与对象引用类型之间要匹配。
  • 访问私有数据和方法的规则没有被违反。
  • 对本地变量的访问都在运行时堆栈内。
  • 运行时堆栈没有溢出。

如果以上这些检查中任何一条没有通过,那么该类就被认为遭到了破坏,并且不予加载。

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

为了深入了解效验器的工作,也为了了解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
得到:
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进制的class文件。先列出代码对应的16进制数:
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进制数编辑器,将3C改为3B。这样执行VerifierTest程序,我们会得到java.lang.VerifyError的错误。
没错,虚拟机发现了我们的修改。
如果加入刚才的java -noverify VerifierTest参数,那么程序就可以产生输出了,为15102330,一个随机数,典型的输出错误。
所以,可以看出效验器果然给我们提供了很好的安全机制。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics