上周五偶然翻到本讲JVM原理的书,看着觉得很过瘾,居然因此错过了晚饭,很久没这么酣畅淋漓过了。然而过瘾之余,看不到JVM源码、也无法调试,总觉得未能尽兴。Linux的老爸Linus大牛曾说“Talk is cheap, show me the code”,光看书,总觉得像在吃别人已经吃剩下的饭,远不如自己动手看看源码、调试跟一跟来得实在。遂决定在Windows平台上自己动手编译一个Java虚拟机,并且要求能调试。
本文详细记录了Windows平台下使用VS2010编译x86版本的Java虚拟机客户端的全过程。包括编译前的准备工作、配置与编译过程与错误排除、将Hotspot VM源码导入VS2010 IDE实现图形界面管理,并在其中下断点调试的整个过程。
OpenJDK官方使用VS2010构建Windows平台下的JVM和JDK,并且在其配置脚本中也有一些针对VS2010“硬编码”的内容,如捕捉其编译器的版本信息。为了保证兼容性和编译的方便性,我们也入乡随俗,使用VS2010。
值得注意的是,为了顺利编译VS2010,需要为VS2010安装SP1补丁。之后最好再通过Windows的更新系统将所有VS2010相关的补丁打齐,然后开始编译。我安装的是VS2010中文版,在之后的编译过程中要改一些配置(OpenJDK的配置脚本对中文支持不好),因此如果直接安装英文版的VS2010,编译过程会更顺利些。
图中正在给VS2010打补丁。从安装VS2010再到安装VS2010 SP1补丁包,然后在线更新补丁,耗时会比较长,这段时间可以并行下面的操作。
由于OpenJDK在配置阶段使用了bash脚本和一些gnu工具集,要想在Windows系统中完成这些步骤,就需要Cygwin的帮助了。访问https://www.cygwin.com/网站,下载对应的Cygwin版本。由于我的开发环境是Win2k8 R2,于是下载了x64版本的。
Cygwin的安装比较傻瓜化,其中有一步是选择镜像,我选择的是kernel.org的镜像,速度还行。接下来是定制需要安装的工具,在默认的基础上,我们加装如下工具:
Binary Name Category Package Description
================================================================
ar.exe Devel binutils The GNU assembler, linker and binary utilities
make.exe Devel make The GNU version of the 'make' utility built for CYGWIN
m4.exe Interpreters m4 GNU implementation of the traditional Unix macro processor
cpio.exe Utils cpio A program to manage archives of files
zip.exe Archive zip Package and compress (archive) files
unzip.exe Archive unzip Extract compressed files in a ZIP archive
free.exe System procps Display amount of free and used memory in the system
================================================================
选定工具后就是漫长的等待下载过程了。
OpenJDK的源码采用Mercurial进行版本管理 ,为了获取最新版本的源码,我们需要下载TortoiseHg。由于开发机网络不太稳定,这一步我选择在VPS上完成,并将下载好的源码传回本地。
用于下载源码的VPS使用的是CentOS系统,需要先安装TortoiseHg:
yum install mercurial
如果是在Windows环境下,则可以到bitbucket官方进行下载和安装:https://tortoisehg.bitbucket.io/download/index.html
安装好后,将源码下载到本地,并下载好附加的源码:
hg clone http://hg.openjdk.java.net/jdk8u/jdk8u-dev
cd jdk8u-dev
sh get_source.sh
由于OpenJDK中的swing与JConsole需要使用freetype的字体渲染功能,因此需要首先对其进行编译。首先到https://www.freetype.org/download.html#stable-releases下载freetype的源码,最终转到https://download.savannah.gnu.org/releases/freetype/处。目前(17.12.17)最新的版本是freetype-2.8.1.tar.bz2,下载后解压到本地。
使用VS2010载入其中的\builds\windows\vc2010目录下的sln文件,打开该工程。然后在工程名上右键,进行属性设置。这里有几点要进行修改:
为了能在生成Dll的同时构建好lib文件,需要对freetype的源码进行一点点修改。找到其头文件中的ftconfig.h文件,定位到FT_EXPORT的条件宏部分,将其用如下代码替换:
#ifdef DLL_EXPORT
#undef DLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif /* !DLL_EXPORT */
#ifndef FT_EXPORT
#ifdef __cplusplus
#define FT_EXPORT( x ) extern "C" DLL_EXPORT x
#else
#define FT_EXPORT( x ) extern DLL_EXPORT x
#endif
如果有文件提示需要转换编码或者转换代码页,按其提示操作即可。(我这里偷了个懒,没管,编译时有警告,不过没影响到最终的生成)
在freetype的根目录下,创建一个名为lib的文件夹,将编译后得到的freetype.dll和freetype.lib拷贝到其中即可。如下所示:
到目前为止,我们已经完成了VS2010 with SP1的安装、Cygwin及需要用到的工具的安装、freetype的编译生成、OpenJDK8的源码获取。在本机,上述各组件的为止如下:
首先进行配置。开启一个Cygwin的bash窗口,进入OpenJDK源码路径(注意,Cygwin中硬盘路径为/cygdrive/盘符/路径),按我的配置,则为:
cd /cygdrive/c/jvm/jdk8u-dev
然后进行配置:
bash ./configure --with-freetype=/cygdrive/c/JVM/freetype-2.8.1 -with-target-bits=32 --with-debug-level=slowdebug --with-jvm-variants=client with_toolsdir="/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 10.0/VC/bin"
很快,我们就收到了报错提示:
打开jdk8u-dev\common\autoconf下的generated-configure.sh文件,搜索“Target CPU mismatch”关键字,共两处命中。分析其上下文可知,其是通过微软编译器cl.exe的输出获得目标平台和版本号的。由于我们使用的是中文的VS2010,因此和其预设的英文不同,故而无法匹配,进而报错。解决方案很简单,直接将这段判断逻辑干掉:
elif test "x$OPENJDK_TARGET_OS" = xwindows; then
# First line typically looks something like:
# Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
COMPILER_VERSION_TEST="Microsoft C/C++ Compiler version 16.00.40219.01 x80x86"
COMPILER_VERSION="16.00.40219.01"
COMPILER_VENDOR="Microsoft CL.EXE"
COMPILER_CPU_TEST="x80x86"
而几个变量也被我直接赋值了。此时再重新执行配置命令,很快就完成了。
在完成配置后,就可以进行构建了。由于我们只需要Win32版本的JVM客户端,因此继续在完成配置的bash窗口中执行生成命令:
make images
执行后,很快又发生了一个错误:
首先按照提示路径,定位并打开get_msc_ver.sh文件第61行:
MSC_VER_RAW="16.00.40219.01"
MSC_VER_MAJOR="16"
MSC_VER_MINOR="0"
MSC_VER_MICRO="40219"
if [ "${MSC_VER_MAJOR}" -eq 14 -a "${MSC_VER_MINOR}" -eq 0 -a "${MSC_VER_MICRO}" -eq 30701 ] ; then
MSC_VER=1399
else
MSC_VER="1600"
关键就是那个MSC_VER=”1600”,这便是VS2010中编译器cl的版本号。
执行完make clean后,重新执行make images,过了一会儿又报错了:
这个错误比较坑,在Google上搜索得到的结果又是换行符不同造成的血案。为此,需要修改两处class文件的换行标识。
用vi打开jdk\make目录下的CreateJars.gmk,定位到268行,相距不远处有两个$$换行符,将其转换为Windows下的换行符。方法是将光标定位到两个$$之前,按i切换到insert模式后,按Ctrl+C和Ctrl+M即可(感谢fh759208669兄指出,此处命令应该为Ctrl+V 与 Ctrl+M,并且经过他的实验,是ctrl+v 然后不用松开ctrl按m)。完成后按esc退出编辑模式,然后按:进入命令模式,输入wq保存并退出。
改完后,重新生成。稍作等待(去食堂吃了个饭回来)即完成了编译。
此时,进入jdk8u-dev\build\windows-x86-normal-client-slowdebug\images\j2sdk-image目录下即可看到生成的JVM文件。
在其bin下执行java -version结果如下:
将Cygwin的bin路径加入环境变量Path中,在我这里即C:\Cygwin\bin。
在dk8u-dev\hotspot\make\windows目录下开启一个新的命令提示符窗口,将vcvars32.bat拖到该窗口中,然后按回车键完成环境变量的设置。
然后执行set HOTSPOTMKSHOME=C:\cygwin64\bin 设置好HOTSPOTMKSHOME环境变量。
在该命令提示符窗口输入grep进行测试,如果该命令被执行了则可进行下面的操作了。】
上述过程如图所示:
接着执行:
create.bat C:\jvm\jdk8u-dev\build\windows-x86-normal-client-slowdebug\images\j2sdk-image
其中后面的路径即是我们之前测试jvm时访问的路径。执行完毕后即会在jdk8u-dev\hotspot\build\vs-i486中生成jvm.vcxproj工程文件。用VS2010打开该工程文件即可。使用F7快捷键即可重新生成jvm.dll。
修改工程属性中的启动参数,传入要加载的类名,如下图传入的即为C盘下,名为CHello的类。
然后到jdk8u-dev\build\windows-x86-normal-client-slowdebug\images\j2sdk-image\jre\bin\client下,将java.diz解压到当前目录,其中包含了java.exe对应的符号表和映射文件,这样才能进行源码级调试。
20171221注:在VS的调试命令参数中,加入-XXaltjvm=$(TargetDir)参数,则默认使用VS2010编译得到的jvm.dll,由于jvm.pdb也在该目录下,所以可以直接调试,无需再进行任何解压操作了。另外加入的类路径最好放在最后。
在share\vm\runtime\stubRoutines.hpp中的call_stub函数处下断:
static CallStub call_stub()
{
return CAST_TO_FN_PTR(CallStub, _call_stub_entry);
}
F5运行后,程序被断下:
在Visual Studio强大的源码浏览与动态调试功能的帮助下,我们终于可以亲自一窥JVM的全貌了。