JVM 原始碼分析(二):搭建 JDK 8 原始碼除錯環境(Windows 上使用 CLion)

張永恆發表於2021-01-12

前言

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

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

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

一、準備原始碼

CentOS 中執行如下命令:

# 下載原始碼包
wget https://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 https://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 https://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 https://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 檔案的連結:https://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 服務。

相關文章