前言
上一篇文章介紹了幾種 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 服務。