JVM 源碼分析(二):搭建 JDK 8 源碼調試環境(Windows 上使用 CLion)

前言

上一篇文章介紹了幾種 JVM,接下來,我將以 OpenJDK 8 中的 HotSpot VM 為例,通過分析其源碼,探索 JVM 的實現。本篇主要記錄調試環境的搭建過程。

由於在 Windows 下編譯 JVM 必須使用 Visual Studio,然而本人用慣了 JetBrains 家的 CLion,不想更換 IDE,所以選擇在 Linux (CentOS7) 上編譯,在 Windows 上使用 CLion 遠程調試。

這裡需要注意,由於整個操作過程需要安裝很多工具,並且編譯時還將產生大量的臨時文件,因此,在開始編譯前必須確保有足夠的磁碟空間(最好大於20G)。

一、準備源碼

CentOS 中執行如下命令:

# 下載源碼包
wget //download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-src-b04-14_jan_2020.zip

#
 如果沒有安裝 unzip,先安裝
yum install -y unzip

#
 解壓
unzip openjdk-8u41-src-b04-14_jan_2020.zip

二、安裝 "Bootstrap JDK"

OpenJDK 的編譯除了依賴 C/C++ 編譯器之外,還依賴一個 Java 編譯器。這是因為 OpenJDK 的很多模組都是用 Java 寫的,編譯這部分程式碼就需要用到另一個 JDK。官方稱這個 JDK 為 「Bootstrap JDK」, 它的版本應當低於需要編譯的目標 JDK 的版本。

編譯 OpenJDK 8 需要使用 Update 7 或更高版本的 JDK 7 版本。參考源碼根目錄下的 「README-builds.html」 文件。

CentOS 中執行如下命令:

# 卸載已安裝的JDK
yum list installed | grep jdk
rpm -qa | grep jdk
yum remove -y xxxx

#
 確保卸載成功
java -version

#
 下載jdk-7u80,這裡選擇從華為鏡像站下載
wget //repo.huaweicloud.com/java/jdk/7u80-b15/jdk-7u80-linux-x64.tar.gz

#
 解壓到指定目錄
tar -zxf jdk-7u80-linux-x64.tar.gz -C /usr/local/java/

#
 配置環境變數
vim /etc/profile
 # 追加如下內容
    export JAVA_HOME=/usr/local/java/jdk1.7.0_80
    export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib/
    export PATH=$PATH:$JAVA_HOME/bin
source /etc/profile

#
 確保安裝成功
java -version

完成後如圖所示:

三、配置編譯環境

CentOS 中執行如下命令:

# 安裝編譯所需工具
yum install -y gcc gcc-c++ make libXtst-devel libXt-devel libXrender-devel cups-devel freetype-devel alsa-lib-devel fontconfig-devel

#
 進入源碼目錄
cd openjdk/

#
 確保configure腳本擁有可執行許可權
chmod +x configure

#
 執行configure腳本,看看缺少什麼依賴項,根據錯誤提示安裝即可,然後重複執行直到提示成功
./configure --with-debug-level=slowdebug --enable-debug-symbols --disable-zip-debug-info

#
 參數說明
# --with-debug-level=slowdebug 設置編譯級別為slowdebug,將會輸出較多的調試資訊
# --enable-debug-symbols 啟用調試符號,將會生成調試資訊文件
# --disable-zip-debug-info 禁用調試資訊壓縮,否則,調試資訊默認會被壓縮成"libjvm.diz"文件,調試時只能看到彙編程式碼,不能跟進源碼

完成後如圖所示:

四、編譯與測試

CentOS 中執行如下命令:

# 編譯(這裡啟動6條編譯執行緒以加快編譯速度)
make JOBS=6

#
 測試
./build/linux-x86_64-normal-server-release/jdk/bin/java -version

#
 確保"libjvm.debuginfo"文件存在,否則調試時將不能跟進源碼
ls ./build/linux-x86_64-normal-server-release/jdk/lib/amd64/server/

如果 Linux 內核版本為 4+,編譯時將出現 「This OS is not supported」 的報錯。解決辦法是修改源碼目錄下的 「./hotspot/make/linux/MakeFile」 文件,找到 SUPPORTED_OS_VERSION 變數定義的地方,在後面追加 「4%」,如下圖所示。

如果一切順利,將會看到如圖所示資訊:

至此,我們已經完成了 JDK 的編譯。

五、安裝 CMake 和 GDB

為了在本地使用 CLion 進行遠程調試,需要在服務端安裝與本地版本相兼容的 CMake 和 GDB。

由於從 yum 源安裝的版本較低,因此這裡選擇編譯安裝。

CentOS 中執行如下命令:

# 卸載已有的cmake和gdb
yum remove -y cmake gdb

#
 下載cmake3.14.5(使用華為鏡像站)
wget //mirrors4.tuna.tsinghua.edu.cn/pkgsrc/distfiles/cmake-3.14.5.tar.gz

#
 解壓
tar -zxf cmake-3.14.5.tar.gz

#
 進入cmake目錄,執行編譯安裝
cd cmake-3.14.5
./bootstrap && make && make install

#
 為cmake命令創建軟鏈接
ln -s /usr/local/bin/cmake /usr/bin/cmake

#
 驗證是否安裝成功
cmake -version

#
 回到上一級目錄,準備安裝gdb
cd ..

#
 下載gdb8.1(使用華為鏡像站)
wget //mirrors4.tuna.tsinghua.edu.cn/pkgsrc/distfiles/gdb-8.1.tar.gz

#
 解壓
tar -zxf gdb-8.1.tar.gz

#
 安裝編譯所需工具
yum install -y texinfo

#
 進入gdb目錄,執行編譯安裝
cd gdb-8.1
./configure && make && make install

#
 為gdb命令創建軟鏈接
ln -s /usr/local/bin/gdb /usr/bin/gdb

#
 驗證是否安裝成功
gdb -ver

五、準備遠程調試

在 CLion 中創建一個空項目,推薦 Language standard 選擇 C++11。

配置工具鏈,如圖所示。

配置 SFTP 連接,用於連接到遠程主機。

配置路徑映射,用於同步兩端的程式碼。

配置排除路徑,排除本地的 cmake 輸出路徑。

將遠程主機的程式碼同步到本地。在 Project 面板中點右鍵,在彈出的菜單中選擇 Deployment -> Download from,然後點擊目標 Server,等待下載。如圖所示。

同步的過程比較耗時,主要是因為 CLion 需要給每一個文件建立映射關係。同步完成後的效果如下。

接下來需要在 CMakeLists.txt 文件中完成項目模型的配置。

CMakeLists.txt 中的一些基本配置可以通過 「New CMake Project from Sources」 的方式讓 CLion 自動完成。不過 CLion 僅僅是把項目文件註冊進來,並沒有正確配置依賴關係和宏定義,當我們打開程式碼時會發現處處爆紅。

為了解決這些爆紅,我花了兩個晚上,經反覆嘗試,終於得到了一個看似使程式碼不再爆紅的 CMakeLists.txt 文件。

附上 CMakeLists.txt 文件的鏈接://github.com/zhangyongheng/jdkbuild/blob/master/openjdk8/CMakeLists.txt

項目模型配置好了之後,接下來就可以開始遠程調試了。

六、開始遠程調試

首先在遠程主機上開啟 GDB 服務,命令如下:

# 開啟GDB服務,這裡指定埠號為8899,後面跟上要調試的java命令
gdbserver :8899 /root/openjdk/build/linux-x86_64-normal-server-release/jdk/bin/java -version

#
 如果沒有安裝gdbserver,先安裝
yum install -y gdb-gdbserver

打開 CLion 的 "Run/Debug Configuration" 對話框,添加一個 "GDB Remote Debug",按照如圖所示進行配置。其中,"target remote args" 是遠程 GDB 服務的 IP 地址和埠號,"Path mappings" 是遠程項目根路徑與本地項目根路徑間的映射關係。

打開 "jdk/src/share/bin/java.c" 文件,找到 JavaMain() 函數,它是 Hotspot 虛擬機的執行入口,我們可以在這個方法內打上斷點。

點擊 "Debug" 開始調試,在 」GDB「 標籤頁中可以查看到 GDB 日誌,我們還可以在這裡通過輸入 GDB 命令,以命令行的方式執行調試。

從 GDB 日誌中可以看到, "libjvm.debuginfo" 文件的載入通常比較耗時,我這裡大約需要等待 3 分鐘,然後程式啟動後會停在斷點的位置。

在跟蹤調試之前,還需要設置 GDB 對 SIGSEGV 訊號的處理方式,忽略調試時的 SIGSEGV 訊號。

在 」GDB「 標籤頁下的 "(gdb)" 命令行中輸入 "handle SIGSEGV nostop noprint pass",如圖所示。

接下來就可以繼續跟蹤調試了。

調試完成後,遠程 GDB 服務將會退出,再次調試需要重新開啟 GDB 服務。