在上一章《在docker上编译openjdk8》里,我们在docker容器内成功编译了openjdk8的源码,有没有读者朋友产生过这个念头:“能不能修改openjdk源码,构建一个与众不同的jdk“,今天我们就来阅读一些openjdk的源码,再尝试做些小改动并验证。

我们先编译openjdk:
首先通过命令git clone git@github.com:zq2599/centos7_build_openjdk8.git下载构建镜像所需的文件,下载后打开控制台进入centos7_build_openjdk8目录,执行

docker build -t bolingcavalryopenjdk:0.0.1 .

这样就构建好了镜像文件,再执行启动docker容器的命令(命令中的参数“–security-opt seccomp=unconfined”有特殊用处,稍后会讲到):

docker run --name=jdk001 --security-opt seccomp=unconfined -idt bolingcavalryopenjdk:0.0.1

然后执行以下命令进入容器的控制台:

docker exec -it jdk001 /bin/bash

进入容器的控制台后执行以下两个命令开始编译:

./configure --with-debug-level=slowdebug make all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK CONF=linux-x86_64-normal-server-slowdebug

以上就是编译openjdk的步骤了,请大家开始编译吧,因为等会儿会用到,我们要用编译好的jdk做调试。

现在开始看源码吧,本次分析的目标是针对我们熟悉的java -version命令,当我们在终端敲下这个命令的时候,jvm到底做了些什么呢?

整个分析验证的流程是这样的:

这里写图片描述

准备工作:
在容器内通过vim看源码是很不方便的,所以我这里是在电脑上复制了一份openjdk的源码(下载地址:http://www.java.net/download/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip
),用sublime text3打开openjdk源码,真正到了要修改的时候再去docker容器里通过vi修改。

寻找程序入口

第一步就是把程序的入口和源码对应起来,先要找到入口main函数,步骤如下:

  1. 在docker容器内的/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/bin目录下,执行命令以下命令可以进入GDB的命令行模式:
gdb --args ./java -version

效果如下图,可以看到已进入GDB命令行模式,可以继续输入GDB命令了:

这里写图片描述

输入b main命令,在main函数打断点,此时GDB会返回断点位置的信息,如下图,main函数的位置在/usr/local/openjdk/jdk/src/share/bin/main.c, line 97:

这里写图片描述

再输入l命令可以打印源码,如下图:

这里写图片描述

在容器外的电脑上,通过sublime text3或者其他ide打开main.c,如下图,开始读代码吧:

这里写图片描述

顺序阅读代码

main函数中的代码并不多,但有几个宏定义会扰乱我们思路,从字面上看#ifdef _WIN32这样的宏应该是windows平台下才会生效的,但总不能每次都靠字面推断,此时打断点单步执行是最直接的方法,但是在打断点之前,我们先解决前面遗留的一个问题吧,此问题挺重要的

还记得我们启动docker容器的命令么:

docker run --name=jdk001 --security-opt seccomp=unconfined -idt bolingcavalryopenjdk:0.0.1

命令中的–security-opt seccomp=unconfined参数有什么用?为何要留在打断点之前再次提到这个参数?

这个参数和Docker的安全机制有关,具体的文档链接在这里,请读者们自行参悟,本人的英文太差就不献丑了,简单的说就是Docker有个Seccomp filtering功能,以伯克莱封包过滤器(Berkeley Packet Filter,缩写BPF)的方式允许用户对容器内的系统调用(syscall)做自定义的“allow”, “deny”, “trap”, “kill”, or “trace”操作,由于Seccomp filtering的限制,在默认的配置下,会导致我们在用GDB的时候run失败,所以在执行docker run的时候加入–security-opt seccomp=unconfined这个参数,可以关闭seccomp profile的功能;

我之前不知道seccomp profile的限制,用命令docker run –name=jdk001 -idt bolingcavalryopenjdk:0.0.1启动了容器,编译可以成功,但是在用GDB调试的时候出了问题,如下图:

这里写图片描述

上图中,黄框中的“进入GDB”和“b main”(添加断点)两个命令都能正常执行,但是红框中的”r”(运行程序)命令在执行的时候提示错误“Error disabling address space randomization: Operation not permitted”,在执行”n”(单步执行)命令的时候提示程序不在运行中。

遗留问题已经澄清,可以继续跟踪代码了,之前我们已经在GDB输入了”b mian”,给main函数打了断点,现在输入”r”开始执行,然后就会看到main函数的断点已经生效,输入”n”可以跟踪代码执行到了哪一行,如下图:

这里写图片描述

原来代码执行的位置分别是97,122,123,125这四行,和下图的