この記事は NTTコムウェア Advent Calendar 2024 22日目の記事です。
はじめに
NTTコムウェアの稲垣です。
業務ではモバイルアプリのバックエンド開発に従事しています。開発には主に Java を使用しており、使い始めてかれこれ2年ほど経ちます。普段は Java を使う側なのですが、長く触れていると Java 自身がどのように実装/実行されているのかについても気になってくるものでして、今回は手始めに OpenJDK をビルドしてみることにしました。ついでに GDB、Visual Studio Code (以下、VS Code) を使って java コマンドのデバッグ実行ができるところまで試してみたので、本記事ではその内容を紹介していきます。
環境
基本的にコマンドは WSL 2 上で実行しています。ディストリビューションは Ubuntu です。また、今回は環境を汚さずにビルドしてみたかったので Docker を使用しました。なお、私は Docker に慣れ親しんでいるわけではないのでそのあたりは温かい目で見守っていただけると幸いです。
- WSL: 2.3.26.0
- Ubuntu: 24.04.1
- Docker: 27.3.1
OpenJDK のビルド
それではビルドをしていきます。OpenJDK のビルドについてはドキュメントが用意されているので、基本的にこれを見ておけば事足ります。
ちなみにリポジトリ内にも同様のドキュメントがファイルとしてコミットされています。個人的にはこちらのほうが見やすかったのでこちらを見ていました。
まずはビルドする OpenJDK のバージョンを決めます。執筆時点ではリリース済みの最新バージョンが JDK 23、開発中のバージョンが JDK 24 でした。どうせなら最先端のものをビルドしてみよう、ということで今回は JDK 24 をビルドすることにします。
また、OpenJDK をビルドするには Boot JDK と呼ばれる別の JDK を用意する必要があります。OpenJDK には Java のソースコードも含まれており、それらをビルドするために必要となるのでしょう。ドキュメントの Boot JDK の項目 を読むと、ビルド対象のバージョンを N としたとき Boot JDK のバージョンは N-1 にすべし、と記載があります。そのため、今回は Boot JDK として JDK 23 を使用することになります。
バージョンも決まったので、実際にビルドをしていきます。まずは OpenJDK のリポジトリ openjdk/jdk からソースコードを取得します。タグは執筆時点で最新の jdk-24+26 を指定しています。リポジトリが大きそうだったので --depth 1
を指定していますが、これは指定しなくて問題ないです。
$ git clone https://github.com/openjdk/jdk -b jdk-24+26 --depth 1
次に、Dockerfile を用意します。今回はソースコードはローカル (WSL) に置きつつ、各種コマンドは Docker コンテナ上で実行していく構成としています。そのため、ビルドに必要となる各種ツール類 (Boot JDK 含む) はコンテナ側にインストールする必要があります。紆余曲折ありできあがった Dockerfile はこちら。
FROM ubuntu:24.04
# ローカル側とコンテナ側の UID, GID を合わせる
ARG USER=user
ARG GROUP=user
ARG UID=1002
ARG GID=1002
# パッケージインストール
RUN apt update
RUN DEBIAN_FRONTEND=noninteractive \
apt install -y \
sudo wget pandoc git gdb \
build-essential autoconf file zip libasound2-dev libcups2-dev libfontconfig1-dev \
libx11-dev libxext-dev libxrender-dev libxrandr-dev libxtst-dev libxt-dev
# ユーザー・グループ追加、sudo 権限付与
RUN groupadd -g $GID $GROUP
RUN adduser --disabled-password --gecos '' --uid $UID --gid $GID $USER
RUN echo "$USER ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/$USER
# 作成したユーザーに切り替え、ディレクトリも切り替え
USER $USER
WORKDIR /home/$USER
# Boot JDK ダウンロード
RUN wget https://download.java.net/java/GA/jdk23.0.1/c28985cbf10d4e648e4004050f8781aa/11/GPL/openjdk-23.0.1_linux-x64_bin.tar.gz && tar zxf openjdk-23.0.1_linux-x64_bin.tar.gz
ENV JAVA_HOME=/home/$USER/jdk-23.0.1
ENV PATH=$PATH:/home/$USER/jdk-23.0.1/bin
# JTReg ダウンロード
RUN wget https://ci.adoptium.net/view/Dependencies/job/dependency_pipeline/lastSuccessfulBuild/artifact/jtreg/jtreg-7.4+1.tar.gz && tar zxf jtreg-7.4+1.tar.gz
ENV JT_HOME=/home/$USER/jtreg
ポイントを以下に記載します。
- ローカル (WSL) 側とコンテナ側で UID, GID が異なっている場合、権限の問題でローカル側で先ほどクローンした jdk ディレクトリの中にコンテナ側からディレクトリを作れなくてビルドに失敗するので、ユーザー、グループを合わせています。
user
,1002
は私の WSL 環境に合わせて書いているので、もし試される場合は適宜変更してください。Docker を使用しない場合はもちろん気にする必要のないポイントとなります。 - インストールしているパッケージはドキュメントに記載されていたものに加え、後述する
bash configure
コマンド実行時にこれもインストールしてねと指示されたものをどんどん追加していきました。また、sudo や wget などの便利ツールもインストールしています。 - Boot JDK (JDK 23) は以下のページに置いてあるビルド済みの OpenJDK をダウンロードして使用しています。
- JTReg はテストを実行する際に必要となるテストフレームワークです。ビルドするだけなら必要ないですが、今回はお試しでテストも回してみたかったのでダウンロードしています。
こちらの Dockerfile を使ってイメージをビルドします。Dockerfile が置いてあるディレクトリで以下のコマンドを実行します。イメージ名は jdkbuild としました。
$ docker build -t jdkbuild .
成功したら、先ほどクローンした jdk ディレクトリに移動します。
$ cd /path/to/jdk
コンテナ上で configure を実行します。指定しているオプションは以下の通りです。Docker を使用しない場合は bash configure
だけで問題ないです。
-
--rm
: コマンドの実行が終わったらコンテナを削除する -
-v $(pwd):$(pwd)
: ローカル側のカレントディレクトリをコンテナ側の同じパスにマウントする -
-w $(pwd)
: コンテナ側のワーキングディレクトリを指定する
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild bash configure
configure: Configuration created at Sat Dec 14 04:34:25 UTC 2024.
...
checking for cygpath... [not found]
checking for wslpath... [not found]
checking for cmd.exe... [not found]
checking build system type... x86_64-pc-wsl
checking host system type... x86_64-pc-wsl
checking target system type... x86_64-pc-wsl
checking openjdk-build os-cpu... windows-x86_64
checking openjdk-target os-cpu... windows-x86_64
checking compilation type... native
checking Windows environment type... wsl1
configure: error: Incorrect wsl1 installation. Neither cygpath nor wslpath was found
configure exiting with result code 1
はい、無事エラーになりました。ドキュメントの WSL の項目 によると、WSL では Windows 用と Linux 用の両方のバイナリをビルドすることができて、デフォルトでは Windows 用のバイナリをビルドしようとするようです。今回は Boot JDK も Linux 用のものを用意しており、Linux 向けにビルドしたいのでオプションを追加して再実行してみます。なお、Windows 用のバイナリを WSL で本当にビルドできるかは未確認です。
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild \
bash configure \
--build=x86_64-unknown-linux-gnu \
--openjdk-target=x86_64-unknown-linux-gnu
configure: Configuration created at Sat Dec 14 04:47:55 UTC 2024.
...
====================================================
A new configuration has been successfully created in
/home/user/jdk/build/linux-x86_64-server-release
using configure arguments '--build=x86_64-unknown-linux-gnu --openjdk-target=x86_64-unknown-linux-gnu'.
Configuration summary:
* Name: linux-x86_64-server-release
* Debug level: release
* HS debug level: product
* JVM variants: server
* JVM features: server: 'cds compiler1 compiler2 epsilongc g1gc jfr jni-check jvmci jvmti management parallelgc serialgc services shenandoahgc vm-structs zgc'
* OpenJDK target: OS: linux, CPU architecture: x86, address length: 64
* Version string: 24-internal-adhoc.user.jdk (24-internal)
* Source date: 1734152719 (2024-12-14T05:05:19Z)
Tools summary:
* Boot JDK: openjdk version "23.0.1" 2024-10-15 OpenJDK Runtime Environment (build 23.0.1+11-39) OpenJDK 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing) (at /home/user/jdk-23.0.1)
* Toolchain: gcc (GNU Compiler Collection)
* C Compiler: Version 13.2.0 (at /usr/bin/gcc)
* C++ Compiler: Version 13.2.0 (at /usr/bin/g++)
Build performance summary:
* Build jobs: 24
* Memory limit: 31649 MB
The following warnings were produced. Repeated here for convenience:
WARNING: pandoc is version 3.1.3, not the recommended version 2.19.2
今度は成功しました。pandoc のバージョンが新しすぎて警告が出ていますが、気にしないでおきます。
ビルドを実行します。けっこう時間がかかるので気長に待ちましょう。
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild make images
Building target 'images' in configuration 'linux-x86_64-server-release'
Compiling up to 1 files for BUILD_TOOLS_HOTSPOT
...
Creating CDS-NOCOOPS-COH archive for jdk image for server
Stopping javac server
Finished building target 'images' in configuration 'linux-x86_64-server-release'
こちらも成功しました。生成された java コマンドを実行してみます。java コマンドを実行するだけであればコンテナを使う必要はないので、ローカルから実行しています。
$ ./build/linux-x86_64-server-release/images/jdk/bin/java -version
openjdk version "24-internal" 2025-03-18
OpenJDK Runtime Environment (build 24-internal-adhoc.user.jdk)
OpenJDK 64-Bit Server VM (build 24-internal-adhoc.user.jdk, mixed mode, sharing)
ちゃんとバージョンが表示されました。うれしいですね。最後にテストを回してみます。
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild make test-tier1
Building target 'test-tier1' in configuration 'linux-x86_64-server-release'
...
==============================
Test summary
==============================
TEST TOTAL PASS FAIL ERROR
>> jtreg:test/hotspot/jtreg:tier1 2619 2611 8 0 <<
jtreg:test/jdk:tier1 2439 2439 0 0
jtreg:test/langtools:tier1 4592 4592 0 0
jtreg:test/jaxp:tier1 0 0 0 0
jtreg:test/lib-test:tier1 35 35 0 0
==============================
TEST FAILURE
make[1]: *** [/home/user/jdk/make/Init.gmk:327: main] Error 1
make: *** [/home/user/jdk/make/Init.gmk:189: test-tier1] Error 2
すごく時間がかかりますが、無事終了しました。一部失敗していますが、これは HotSpot のテストに Google Test を必要とするものが含まれているためです。今回 Google Test はインストールしていないため想定内となります。
GDB でデバッグ実行
ビルドができたので、今度は java コマンドのデバッグ実行にチャレンジしていきます。OpenJDK には C/C++ コードと Java コードが含まれていますが、今回デバッグ対象とするのは C/C++ コードの方です。まずは GDB を用いてデバッグ実行できるか試してみます。
デバッグしやすいように、configure 実行時に追加で --with-debug-level=slowdebug
, --with-native-debug-symbols=internal
オプションを指定します。それぞれのオプションについてはドキュメントに記載がありますので適宜ご参照ください。
それでは configure を実行します。
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild \
bash configure \
--build=x86_64-unknown-linux-gnu \
--openjdk-target=x86_64-unknown-linux-gnu \
--with-debug-level=slowdebug \
--with-native-debug-symbols=internal
ビルドします。configuration が複数ある場合は明示的に指定が必要となります。今回は CONF=linux-x86_64-server-slowdebug
を指定します。
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild make images CONF=linux-x86_64-server-slowdebug
ビルドが成功したら、GDB を使ってデバッグ実行してみます。コンテナから実行してもよいですが、GDB を動かせる環境さえ整っていればローカルからでも実行可能です。先ほどと異なり build/linux-x86_64-server-slowdebug ディレクトリを使っていることに注意しましょう。
$ gdb -q --args ./build/linux-x86_64-server-slowdebug/images/jdk/bin/java -version
Reading symbols from ./build/linux-x86_64-server-slowdebug/images/jdk/bin/java...
(gdb)
ちゃんと読み込めているようです。試しに main
関数にブレークポイントを設定してみます。
(gdb) info functions main
All functions matching regular expression "main":
File /home/user/jdk/src/java.base/share/native/launcher/main.c:
57: int main(int, char **);
(gdb) break main
Breakpoint 1 at 0x12dc: file /home/user/jdk/src/java.base/share/native/launcher/main.c, line 58.
実行すると、main
関数で停止しました。
(gdb) run
Starting program: /home/user/jdk/build/linux-x86_64-server-slowdebug/images/jdk/bin/java -version
Downloading separate debug info for system-supplied DSO at 0x7ffff7fc3000
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main (argc=2, argv=0x7fffffffdfa8) at /home/user/jdk/src/java.base/share/native/launcher/main.c:58
58 {
(gdb) list .
53 __initenv = _environ;
54
55 #else /* JAVAW */
56 JNIEXPORT int
57 main(int argc, char **argv)
58 {
59 int margc;
60 char** margv;
61 int jargc;
62 char** jargv;
変数の中身を見ることもできます。
(gdb) print argc
$1 = 2
(gdb) print argv[0]
$2 = 0x7fffffffe25e "/home/user/jdk/build/linux-x86_64-server-slowdebug/images/jdk/bin/java"
(gdb) print argv[1]
$3 = 0x7fffffffe2af "-version"
最後まで走らせます。途中 Segmentation fault が発生していますが、HotSpot は内部で SIGSEGV を使用しているためエラーではないそうです。
(gdb) continue
Continuing.
[New Thread 0x7ffff50b96c0 (LWP 4240)]
Thread 2 "java" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff50b96c0 (LWP 4240)]
0x00007fffdf85466c in ?? ()
(gdb) continue
Continuing.
[New Thread 0x7fffce3196c0 (LWP 4241)]
[New Thread 0x7fffce2186c0 (LWP 4242)]
...
[New process 4236]
[Inferior 1 (process 4236) exited normally]
VS Code でデバッグ実行
GDB でデバッグ実行できることは確認できたので、次は OpenJDK のソースコードを VS Code で開いてデバッグ実行していきます。ドキュメントはこちらです。
記載の通り、まずは VS Code のワークスペースを作成します。
$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) jdkbuild make vscode-project CONF=linux-x86_64-server-slowdebug
Using exact match for CONF=linux-x86_64-server-slowdebug (other matches are possible)
Building target 'vscode-project' in configuration 'linux-x86_64-server-slowdebug'
Cleaning compile-commands make support artifacts ... done
Updating compile_commands.json
Creating workspace /home/user/jdk/build/linux-x86_64-server-slowdebug/jdk.code-workspace
Finished building target 'vscode-project' in configuration 'linux-x86_64-server-slowdebug'
すると、ビルドディレクトリに jdk.code-workspace というファイルが作成されます。
$ ls build/linux-x86_64-server-slowdebug/jdk.code-workspace
build/linux-x86_64-server-slowdebug/jdk.code-workspace
VS Code でこのファイルを指定して開く必要があるのですが、今回は Docker を使用しているので一工夫必要です。そのまま開くこともできはするのですが、WSL 上にはビルドに必要なツール類をインストールしていないため、VS Code 上でビルドできなくなってしまいます。これでは少々やりづらいので、今回はコンテナ上のファイルを VS Code で開いていくことにします。
具体的には、以下の方法でやってみました。VS Code の Dev Container 機能を使って、実行中のコンテナに VS Code からアタッチすることでワークスペースを開いていきます。
まずはコンテナを起動しっぱなしにします。-d
オプションを指定することでバックグラウンドで起動しています。
$ docker run --rm -it -d -v $(pwd):$(pwd) -w $(pwd) jdkbuild
VS Code を起動し、Dev Containers 拡張機能をインストールします。
左のサイドバーから Remote Explorer を開いて上部のプルダウンから Dev Containers を選択します。jdkbuild が表示されるので右矢印ボタンを押下してアタッチします。
出典: Microsoft Corporation「Visual Studio Code」Primary Side Bar 画面 (2024.12.14)
アタッチに成功したらコマンドパレットから以下のコマンドを実行し、jdk.code-workspace ファイルを指定します。
file: Open Workspace from File...
すると、以下のようにワークスペースを開くことができます。左下を見るとわかる通り、実行環境はちゃんとコンテナになっています。
出典: Microsoft Corporation「Visual Studio Code」ウィンドウ全体 (2024.12.14)
最後に、デバッグ実行も試してみます。先ほどの make vscode-project
コマンド実行時に gtestLauncher
, java
という二つのデバッグ構成が作成されています。java
のほうは java コマンドをデバッグ実行するために用意されているもののようなので、こちらを使用してみます。
出典: Microsoft Corporation「Visual Studio Code」Primary Side Bar 画面 (2024.12.14)
main.c を開いて main
関数にブレークポイントを設定、上記の java
を実行すると、以下の通り停止しました。デバッグ実行成功です。
出典: Microsoft Corporation「Visual Studio Code」ウィンドウ全体 (2024.12.14)
おわりに
本記事では、OpenJDK のビルド方法や GDB、VS Code を用いたデバッグ実行の方法を紹介しました。Docker を使用しているため本筋から逸れた話もところどころ出てきてはおりますが、各種パッケージをローカル環境にインストールする必要がない分、比較的気軽に試すことができるのではないでしょうか。環境さえ準備できれば java や javac コマンドの挙動を深掘りしてみることもできますし、OpenJDK の開発に参加してみることもできると思います。興味がある方はぜひチャレンジしてみてください。
※記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。