晚上被小伙伴问道如何使用ide进行jvm源码的调试,刚好前段时间花了点时间折腾了一下,mac最新版本下jvm9顺利编译通过,并且可以完美集成clion进行调试(支持windows),下面记录一下全过程,如果想看效果的话,可以直接拉到集成到clion进行调试小节末尾
mac下openjdk源码编译过程
准备编译环境准备
我的mac的版本如下
由于openjdk1.9之前的版本对mac下编译支持得不是很流畅,所以这篇文章选择openjdk1.9
编译之前,首先你需要准备 homebrew,homwbrew是mac下的包管理器,如果你的mac上没有安装,可以按照下面的方式来安装
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
homwbrew下载完成之后,接下来准备编译环境
- 首先安装openjdk的版本管理工具mercurial
- 然后安装ccache和freetype,ccache用来加速编译,freetype在编译过程也会依赖到
上述准备编译环境的脚本为
brew install mercurial
brew install ccache
brew install freetype
请确保上述三个依赖安装成功再进行下面的步骤
源码获取
环境准备好之后,接下来获取源码,我这里工作目录是 ~/jvm
,建议你也建一个此目录
cd ~/jvm
hg clone http://hg.openjdk.java.net/jdk9/jdk9 jdk9
这条命令运行之后,openjdk的源码并没有下载下来,我们随后进入到~/jvm/jdk9
目录,会发现有一个 get_source.sh
的文件
调用这个脚本下载完整的源码之前,需要做一下简单的修改,不然可能你在下载源码的过程中会数次中断
get_source.sh
文件最后几行的内容如下
# Get clones of all absent nested repositories (harmless if already exist)
sh ./common/bin/hgforest.sh clone "$@" || exit $?
# Update all existing repositories to the latest sources
sh ./common/bin/hgforest.sh pull -u
我们把上面几行的脚本删掉,替换成下面的脚本
# Get clones of all absent nested repositories (harmless if already exist)
sh ./common/bin/hgforest.sh clone "$@"
while [ $? -ne 0 ]
do
sh ./common/bin/hgforest.sh clone "$@"
done
# Update all existing repositories to the latest sources
sh ./common/bin/hgforest.sh pull -u
while [ $? -ne 0 ]
do
sh ./common/bin/hgforest.sh pull -u
done
然后,愉快地调用
bash ./get_source.sh
友情提醒:这个过程可能要持续1~2小时,请提前点好外卖
开始编译
源码下载完之后,我们开始编译,我们先进行编译前的配置
./configure --with-target-bits=64 --with-freetype=/usr/local/Cellar/freetype/2.8.1 --enable-ccache --with-jvm-variants=server,client --with-boot-jdk-jvmargs="-Xlint:deprecation -Xlint:unchecked" --disable-zip-debug-info --disable-warnings-as-errors --with-debug-level=slowdebug 2>&1 | tee configure_mac_x64.log
注意,上面的freetype,需要替换成本机实际安装的版本
执行完之后,记下来会进行一系列的配置,这个过程时间要短很多,最后,如果出现如下的提示,那么恭喜你,接下来就可以执行编译了
接下来调用下面的脚本进行编译
export LANG=C
make all LOG=debug 2>&1 | tee make_mac_x64.log
编译完成之后,如果没有出现错误提示,那么再次恭喜你,你的第一个自行编译的openjdk版本已经顺利通过了,可以考虑打个赏来庆祝一下快乐的心情
最后,验证一下
编译过程中遇到的问题
我比较幸运,在编译过程中就只遇到过三个空指针转换的问题
vi src/share/vm/memory/virtualspace.cpp (char *)
vi src/share/vm/opto/lcm.cpp (unsigned char *)
vi src/share/vm/opto/loopPredicate.cpp (const TypeInt *)
然后,google了一把,根据这篇文章,找到对应的源码文件的位置,把0强制转换成同一种类型
比如下面这个
好在一共只有三个地方,我都给你列出来了
#1\. src/hotspot/share/memory/virtualspace.cpp # l585
if (base() != NULL) {
#2\. src/hotspot/share/opto/lcm.cpp # l42
if (Universe::narrow_oop_base() != NULL) { // Implies UseCompressedOops.
#3\. src/hotspot/share/opto/loopPredicate.cpp # l915
assert(rng->Opcode() == Op_LoadRange || iff->is_RangeCheck() || _igvn.type(rng)->is_int()->_lo >= 0, "must be");
集成到clion进行调试
千呼万唤试出来,终于来到本小节的内容,相信很多小伙伴更想知道jvm源码编译完之后,如何在本机进行流畅地调试阅读源码,在这之前,我发现网上这方面的资料少得可怜,所以自己慢慢摸索出来,分享给大家
使用clion载入源码
首先,我们打开clion,选择 File->ImportProject
,选择到 ~/jvm/jdk9/hotspot
作为jvm源码的根目录,这里导入的过程无脑点击next
即可
项目导入之后,clion会给你默认建立一个 CMakeLists.txt
文件,这里可以不用管他,让他建,完成之后,clion会做大量的索引,索引建立完成之后,项目导入到此结束,界面如下
很多小伙伴遇到clion导入源码之后遇到头文件找不到的问题,而实际上这些头文件在源码里面是存在的,只不过在某些源文件里面是以相对路径的方式来搜索,可以在CMakeLists.txt
里面添加一些根路径,我添加了如下根路经
include_directories(./src/share/vm)
include_directories(./src/cpu/x86/vm)
include_directories(./src/share/vm/precompiled)
include_directories(./src/share/vm/utilities)
另外,如果某些头文件依然找不到,可以手工导入,然后把导入的头文件加到
hotspot/src/share/vm/precompiled/precompiled.hpp
里,因为大多数源文件都会包含这个源文件,加到这个头文件,可以保证在能够引入缺失头文件的同时在debug的时候行数不会串掉,我在读源码的过程中添加了如下几个头文件
# include <cstdlib>
# include <cstdint>
# include "register_x86.hpp"
# include "assembler_x86.hpp"
# include "globalDefinitions.hpp"
# include "globalDefinitions_x86.hpp"
# include "assembler_x86.hpp"
#include <stubRoutines_x86.hpp>
构建调试环境
到了这里,源码的阅读环境才刚刚开始,没有debug,总觉得看起代码来不真实,于是,接下来我们继续构建自己的调试环境
右上角,我们点击Edit Configuration
,进入到下面这个界面
我们创建一个调试环境
Executable
选择编译好的二进制 java文件,然后在Before launch...
选项中,干掉Build
,就是说我们在debug的时候不需要再build,再说了,这里你也build不起来。
到了这里,一个可调试的jvm源码调试环境已经准备完毕了,下面我们来调试一把看看效果
我们来到 jni.cpp
,在 JNI_CreateJavaVM_inner
这个方法上打个断点,点击右上角的debug,神奇的一幕出现了
方法调用栈,当前方法上下文环境,调试所需要的东西应有尽有,如果上面这个画面不够打动你的话,那么下面一张实际源码阅读过程中的动图呢?
与java程序联合调试
我们想要修改jvm源码,修改完之后想要通过修改完的源码来运行我们的指定的java程序,由于clion默认的build工具无法build jvm,我们只能借助于make命令。
clion在debug的时候可以添加两个前置处理器,如下图
第一个前置处理器用于build jvm,第二个前置处理器用于compile java文件,这样,当你修改了点jvm源码,并且修改了java文件之后,在点击右上角的debug文件的时候,clion就会默认先执行jvm的编译,然后再进行java文件的编译,编译完之后就在
/Users/yuchao/IdeaProjects/jvm/src(我们在此目录下放置java源文件)
目录下生成 一个Main.class
文件,然后就可以使用编译过后的jvm来运行,下面是构建jvm和编译java文件的两个external tool
最后,我们来看下,修改了jvm源码之后的效果
当然,你也可以在你的java ide中,把jdk选择自己编译的jdk,再执行,也是一样的效果
[参考资料]
https://segmentfault.com/a/1190000008346240
https://liuzhengyang.github.io/2017/04/28/buildopenjdk/
https://www.jianshu.com/p/746963f28245
https://bugs.openjdk.java.net/browse/JDK-8187787