JVM-在MacOS系统上使用CLion编译并调试OpenJDK12

写在前面

深入理解Java虚拟机:JVM高级特性与最佳实践》又出了第三版,之前第二版介绍编译OpenJDK7,当时(没记错的话,三年前)记得也记录了一篇在Deepin操作系统下编译OpenJDK8的笔记,不过那个时候编译好也就编译好了,没了下文。最近读第三版,看到第三版中介绍的是如何编译OpenJDK12,就又动起了编译OpenJDK12的心思,在经过一系列折腾后,终于折腾好了,记录一下折腾过程~

其实编译OpenJDK并不难,按照源码包里的文档,安装相应的依赖,基本上就能成功。一般导致编译不成功的原因,基本上都是要编译的OpenJDK版本所需的依赖,在当前系统上不兼容,比如你现在在MacOS编译OpenJDK8,它所要求的XCode版本是4,而现在XCode版本都出到11了吧,或许你可以通过改编译文件的方式,跳过这个校验,但你没办法保证在后面的编译过程中不出现一些莫名其妙的问题。所以,我觉得既然是自己编译着玩或者用来学习,其实完全没有必要浪费时间在这些没有意义的事情上面,要编译就编译最近的版本。

在编译OpenJDK12的过程中,其实很顺畅,基本没遇到什么问题,主要是在配置CLion时,总是不满意。因为,如果你直接按照文档里面的流程编译完之后,也许./java -version可以看到你编译的JDK版本输出中包含着你的名字,你很高兴,然后你继续兴奋的按照周大大的指引将源码导入CLion,打个断点,执行,看到断点生效了,继续兴奋,接着,你就发现不对了,为什么头文件都是红色的,提示无法找到?如果我想直接在CLion里改代码,就可以对JDK重新编译,怎么做?当然,周大大也提到了,是因为CLion生成的CMakeLists.txt有问题,如果想实现上面的需求,你需要修好这个文件。

我就是按照周大大的指引,开始找资料,怎么修好JDkCMakeLists.txt,找来找去,没有一个有用的,后来,开始留意到JetBrainsCLion team2020年发布的一篇文章Develop OpenJDK in CLion with Pleasure,说的是利用compilation databaseCLion构建OpenJDK。那么什么是compilation database,可以去官网瞅瞅,我是没看的很明白,大概理解是给一份源代码,生成不同编译器下编译每个源代码文件的参数,都存储在一个叫compile_commands.json的文件中,CLion会解析这个文件,并提取CLion所需要的编译器参数,这样就可以使用CLion的全部功能,即使你的Project不是CMake项目。

好了,废话不多说,开始编译吧!


基础环境

  • 操作系统:macOS Catalina 10.15.5

编译

在获取源码后,在${source_root}/doc目录下,有相关的编译文档,建议阅读。

获取源码

OpenJDK源码使用Mercurial管理,如果通过版本库下载,则需要安装Mercurial。安装完成后,执行以下命令clone源代码:

hg clone https://hg.openjdk.java.net/jdk/jdk12

使用该方式下载,最好全局科学上网,不然会很慢,而且如果中途网络异常,就得重新下载。

也可以通过页面直接下载压缩包,浏览器打开OpenJDK12,左侧就可以下载。

OpenJDK12


Bootstrap JDK

OpenJDK源代码中,有部分代码使用Java语言实现,所以需要准备一个JDK,官方叫做Bootstrap JDK。一般来说要比编译的JDK低一个版本,这里采用OpenJDK11。可以通过这里下载,一定要下载适合Mac平台的OpenJDK11


安装依赖

MacOS下建议使用Homebrew管理依赖。

# 用于生成shell脚本的工具,可以使软件包在不同的系统下都可以编译
brew install autoconf
# 字体引擎
brew install freetype

XCode

安装XCode,直接最新版就可以,我目前使用的版本是11.4.1

使用以下命令安装Command Line Tools for XCode

xcode-select --install

如果编译过程中有关于XCode相关的错误,都会在编译过程中有很明显的提示,都可以通过搜索引擎得到解决方案。


生成编译参数

执行configure命令,可以生成编译参数,详细参数可以通过执行bash configure --help获取帮助。本次编译使用以下参数进行编译:

bash configure  --with-boot-jdk='~/Work/JDK/jdk-11/Contents/Home' --with-debug-level=slowdebug --with-target-bits=64 --disable-warnings-as-errors --enable-dtrace --with-jvm-variants=server
  • --with-boot-jdk:指定Bootstrap JDK路径,如果在终端直接执行java -version所输出的版本是11的话,那么这个参数可省略;
  • --with-debug-level:编译级别,可选值为releasefastdebugslowdebugoptimized,默认值为release,如果我们要调试的话,需要设定为fastdebug或者slowdebug。建议设置为slowdebug
  • --with-target-bits:指定编译32位还是64位的虚拟机;
  • --disable-warnings-as-errors:避免因为警告而导致编译过程中断;
  • --enable-dtrace:开启一个性能工具,暂时还不知道怎么使用;
  • --with-jvm-variants:编译特定模式下的虚拟机,一般这里编译server模式;
  • --with-conf-name:指定编译配置的名称,如果没有指定,则会生成默认的配置名称,比如macosx-x86_64-server-slowdebug,我这里没有使用这个参数,采用默认生成配置。

另外,在很多场景下编译OpenJDK都会使用--enable-ccache参数,来通过ccache加快编译速度,但我没有采用,因为目前编译速度其实不慢,再有就是如果增加了这个参数,后续导入CLion的时候,会出现很多红字提示,看着好像不影响使用,但总归看着不太舒服。


生成Compilation Database

在官网的一个文档中,说明了怎么生成Compilation Database,具体可见:IDE support in the JDK

我们现在就在${source_root}下执行如下命令生成Compilation Database

make CONF=macosx-x86_64-server-slowdebug compile-commands

这里我使用了CONF参数,指定了要编译某个具体的配置,如果只有一个配置,可以省略,但我是编译了fastdebugslowdebug两个级别的JDK,所以使用了这个参数,建议使用CONF参数,如果你不知道你的生成的配置名称,打开${source_root}/build看下目录名就知道啦。

执行完该命令,就会在${source_root}/build/macosx-x86_64-server-slowdebug下生成compile_commands.json文件。


编译

在导入CLion之前,要编译一下,因为某些模块使用了预编译头,如果不编译,CLion会在索引过程中提示找不到各种各样的文件。执行以下命令进行编译:

make CONF=macosx-x86_64-server-slowdebug

调试

编译的步骤全部完成后,就可以开始在CLion中进行配置调试的相关操作了。

导入CLion

CLion中,首先确保配置好了Toolchains,可通过如下界面进行配置:

配置工具链

配置好Toolchains后,通过File -> Open...功能,选中${source_root}/build/macosx-x86_64-server-slowdebug/compile_commands.jsonAs a project打开,这样就导入了Compilation Database文件,接下来CLion开始进行索引。

这时候,你会发现你是看不到源码的,所以下面需要修改项目的根目录,通过Tools -> Compilation Database -> Change Project Root功能,选中你的源码目录,也就是${source_root},这样设置就可以在CLion中看到源代码啦。如果不出意外,这个时候,CLion又开始进行索引,为了避免不必要的麻烦,后续的操作,建议等待索引完成后进行。


配置

Custom Build Targets

配置构建目标

点击箭头所指的按钮,去分别配置BuildClean所执行的动作:

build

clean

Run/Debug configurations

Run/Debug configurations

  • Target:选择上一步配置的Build Target
  • Executable:选择${source_root}/build/macosx-x86_64-server-slowdebug/jdk/bin/java,或者其它你想调试的文件,比如javac
  • Before luanch:这个下面的Build可去可不去,去掉就不会每次执行都去Build,节省时间,但其实OpenJDK增量编译的方式,每次Build都很快,所以就看个人选择了。

Debug

${source_root}/src/java.base/share/native/libjli/java.c401行打断点,点击Debug,效果如下:

Debug

Tips

其实到这里,如果继续Debug,就会出现下面的问题:

调试问题

查了下,几乎所有说调试OpenJDK的文章都有涉及到这个问题,SIGSEGV代表指针所对应的地址是无效地址,没有物理内存对应该地址。其实还有一个,是SIGBUS,代表指针所对应的地址是有效地址,但总线不能正常使用该指针,通常是未对齐的数据访问所致。

MacOSCLion默认使用LLDB进行Debug,所以要避免这种情况,可以通过在进入第一个断点时,执行以下命令避免后面出现此类问题:

# LLDB使用如下命令,GDB暂不讨论,原理基本一致,可以自行搜索
pro hand -p true -s false SIGSEGV SIGBUS

Debug

这样虽然可以解决问题,但如果每次Debug都手动修改,会很繁琐。在JetBrains的文章Develop OpenJDK in CLion with Pleasure中,文末也提到了解决这种问题,但我试了一下,却总是不生效,也没找到是什么原因。

不过最后在这个博客中找到了一个解决方案,博主提到,LLDB只支持Session级别的忽略设置(GDB貌似支持全局,感兴趣的同学可以尝试),就是需要先启动Debug,打断点,然后执行忽略命令,才可以生效。然后博主提出了一种解决方案,在~/.lldbinit文件中,使用如下命令,实现每次Debug时自动打个断点,然后输入pro hand -p true -s false SIGSEGV SIGBUS,最后继续执行后续流程,文件内容如下(其中main.c文件的路径自行替换):

breakpoint set --file /Users/xxxx/Work/JDK/openjdk12/src/java.base/share/native/launcher/main.c --line 98 -C "pro hand -p true -s false SIGSEGV SIGBUS" --auto-continue true

这样即可解决Debug时的小问题,但依然觉得JetBrains说的那种方案是比较优雅的,后面再研究下~


写在最后

enjoy!!!

逃去吃把鸡,再啃C/C++~


参考资料