mac下编译openjdk1.9及集成clion动态调试

1字数 1726阅读 6768

晚上被小伙伴问道如何使用ide进行jvm源码的调试,刚好前段时间花了点时间折腾了一下,mac最新版本下jvm9顺利编译通过,并且可以完美集成clion进行调试(支持windows),下面记录一下全过程,如果想看效果的话,可以直接拉到集成到clion进行调试小节末尾

mac下openjdk源码编译过程

准备编译环境准备

我的mac的版本如下

image.png

由于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,需要替换成本机实际安装的版本

执行完之后,记下来会进行一系列的配置,这个过程时间要短很多,最后,如果出现如下的提示,那么恭喜你,接下来就可以执行编译了

image.png

接下来调用下面的脚本进行编译

export LANG=C
make all LOG=debug  2>&1 | tee make_mac_x64.log

编译完成之后,如果没有出现错误提示,那么再次恭喜你,你的第一个自行编译的openjdk版本已经顺利通过了,可以考虑打个赏来庆祝一下快乐的心情

最后,验证一下

image.png

编译过程中遇到的问题

我比较幸运,在编译过程中就只遇到过三个空指针转换的问题

vi src/share/vm/memory/virtualspace.cpp (char *)

image.png

vi src/share/vm/opto/lcm.cpp (unsigned char *)

image.png

vi src/share/vm/opto/loopPredicate.cpp (const TypeInt *)

image.png

然后,google了一把,根据这篇文章,找到对应的源码文件的位置,把0强制转换成同一种类型
比如下面这个

image.png

好在一共只有三个地方,我都给你列出来了

#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会做大量的索引,索引建立完成之后,项目导入到此结束,界面如下

image.png

很多小伙伴遇到clion导入源码之后遇到头文件找不到的问题,而实际上这些头文件在源码里面是存在的,只不过在某些源文件里面是以相对路径的方式来搜索,可以在CMakeLists.txt里面添加一些根路径,我添加了如下根路经

image.png
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的时候行数不会串掉,我在读源码的过程中添加了如下几个头文件

image.png

# 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,总觉得看起代码来不真实,于是,接下来我们继续构建自己的调试环境

image.png

右上角,我们点击Edit Configuration,进入到下面这个界面

image.png

我们创建一个调试环境


image.png

Executable选择编译好的二进制 java文件,然后在Before launch...选项中,干掉Build,就是说我们在debug的时候不需要再build,再说了,这里你也build不起来。
到了这里,一个可调试的jvm源码调试环境已经准备完毕了,下面我们来调试一把看看效果

我们来到 jni.cpp,在 JNI_CreateJavaVM_inner 这个方法上打个断点,点击右上角的debug,神奇的一幕出现了

image.png

方法调用栈,当前方法上下文环境,调试所需要的东西应有尽有,如果上面这个画面不够打动你的话,那么下面一张实际源码阅读过程中的动图呢?


3.gif

与java程序联合调试

我们想要修改jvm源码,修改完之后想要通过修改完的源码来运行我们的指定的java程序,由于clion默认的build工具无法build jvm,我们只能借助于make命令。
clion在debug的时候可以添加两个前置处理器,如下图

image.png

第一个前置处理器用于build jvm,第二个前置处理器用于compile java文件,这样,当你修改了点jvm源码,并且修改了java文件之后,在点击右上角的debug文件的时候,clion就会默认先执行jvm的编译,然后再进行java文件的编译,编译完之后就在 /Users/yuchao/IdeaProjects/jvm/src(我们在此目录下放置java源文件) 目录下生成 一个Main.class文件,然后就可以使用编译过后的jvm来运行,下面是构建jvm和编译java文件的两个external tool

image.png
image.png
image.png
image.png

最后,我们来看下,修改了jvm源码之后的效果


image.png

当然,你也可以在你的java ide中,把jdk选择自己编译的jdk,再执行,也是一样的效果


image.png

[参考资料]
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