- TL;DR
- ファイルを見る
- 「Build ID」とは
- 「debuginfo」 とは
- debuginfoとBuild IDの関係
- 複数バージョンのdebuginfoをシステムへ導入する
- Build IDの応用
Red Hatの森若です。
rpmパッケージに含まれるファイル一覧を rpm -ql glibc
のようにして表示すると、/usr/lib/.build-id ディレクトリ以下に多数のファイルがあることに気付きます。
「このファイルは何ですか?」 という素朴な疑問に答えるため、背景知識をふくめて紹介していきます。
$ rpm -ql glibc (中略) /lib64/librt.so.1 /lib64/libthread_db.so.1 /lib64/libutil.so.1 /sbin/ldconfig /usr/lib/.build-id /usr/lib/.build-id/03 /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 /usr/lib/.build-id/06 /usr/lib/.build-id/06/3f74dd93e6bd487beebd93975dd7d6a75e7c4d /usr/lib/.build-id/06/f56ed55884abd425ff0fd12872bf3fae0793b8 /usr/lib/.build-id/0f /usr/lib/.build-id/0f/90095799730a30406eb56af85830689679b356 /usr/lib/.build-id/1e /usr/lib/.build-id/1e/acb7c50f7ed20ef1fefda3aa9c67377686acf5 /usr/lib/.build-id/22 /usr/lib/.build-id/22/f2295089fd568f1ec1ff6f40c51b3ef4dd528a /usr/lib/.build-id/31 /usr/lib/.build-id/31/9dab886fc7c7c8bc15b4aa33ed5b08fb84a579 (以下略)
TL;DR
- /usr/lib/.build-id にあるシンボリックリンクはELFファイル(実行ファイルや共有ライブラリ、カーネルモジュール)に含まれる Build IDに対応している
- Build IDはELFファイルに格納されているIDで、内容のハッシュ値。
Build IDの用途は大きくわけて2つ。/usr/lib/.build-idは後者のために利用されている。
- デバッグに必要な情報を提供する debuginfo パッケージ内のファイルと実行用のELFファイルが対応していることの確認
- コアダンプに保存したBuild IDからELFファイルを検索
RHEL 7までは /usr/lib/.build-id に相当するファイルは debuginfo パッケージに含まれていた
- RHEL 8以降は、複数バージョンのdebuginfoパッケージをインストールできるように通常のrpmにbuild-idが含まれるようになった
- 複数バージョンのdebuginfoパッケージをインストールしたい主なシチュエーションはコンテナ利用時
ファイルを見る
まずはファイル /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60
を対象として少し調べてみましょう。
$ ls -l /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 lrwxrwxrwx. 1 root root 33 Sep 7 03:35 /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 -> ../../../../lib64/libnss_dns.so.2
→ ライブラリへのシンボリックリンクになっています
fileコマンドで見てみます。
$ file /lib64/libnss_dns.so.2 /lib64/libnss_dns.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=03f4fad34e4c1a28712ec68f3c106fc200e25c60, for GNU/Linux 3.2.0, stripped
→ "BuildID[sha1]=03f4fad34e4c1a28712ec68f3c106fc200e25c60" これがファイル名の 03/f4fad34e4c1a28712ec68f3c106fc200e25c60 に対応しています!
ライブラリに含まれている「Build ID」というものに対応していることがわかります。ディレクトリ名からも正しそうですね。
「Build ID」とは
Build IDは Fedora 8から導入された比較的古い機能で、FedoraやRHELでELF形式の実行ファイルや共有ライブラリ、カーネルモジュールに付与されているユニークなIDです。 FedoraやRHELではファイルの内容のSHA1ハッシュ値を使って生成しています。そのため内容が完全に同じであれば同一ですが、そうでなければ別のBuild IDが付与されます。
Fedora Wikiを見るとFedoraにBuild IDを導入した時の機能ページがあります。 fedoraproject.org
どのような課題を解決するための機能であるかという「The Problems」を見ると、debuginfoの素早い検証(Quick debuginfo validation)と、ダンプに対応するバイナリの発見(Finding binaries for dumps)の2項目が記載されています。
「debuginfo」 とは
RHELやFedoraでC言語などでのアプリケーションの開発・デバッグを行なっていない方には「debuginfo」は耳慣れない言葉かと思います。
debuginfo パッケージはrpmパッケージの一種です。C、C++言語などのソースレベルデバッグに使うデバッグ情報とソースコードを提供しています。FedoraとRHELのELFバイナリを含むrpmには、それぞれ対応するdebuginfoパッケージが提供されています(debugsource パッケージもありますがここではまとめて「debuginfoパッケージ」と呼びます。)。現在実行中のプログラムについてデバッグをしたい場合には、実行中のプログラムを含むrpmに対応するdebuginfoパッケージをインストールすることで、対応するソースコードを確認しながらデバッグができます。
実際にdebuginfoをインストールしてデバッグ作業につかうには、dnfのdebuginfo-installコマンドでもとのパッケージ名を指定してインストールします。
$ sudo dnf debuginfo-install glibc (中略) Dependencies resolved. ================================================================================================================ Package Architecture Version Repository Size ================================================================================================================ Installing: glibc-debuginfo x86_64 2.35-17.fc36 updates-debuginfo 4.5 M glibc-debugsource x86_64 2.35-17.fc36 updates-debuginfo 5.3 M Transaction Summary ================================================================================================================ Install 2 Packages Total download size: 9.8 M Installed size: 54 M Is this ok [y/N]:
debuginfoパッケージがデバッグに利用するものだとわかりました。
debuginfoとBuild IDの関係
debuginfoパッケージとBuild IDとの関係をみていきます。
C言語等で典型的にはソースコードから、コンパイル、リンクという処理を経てELFファイル(実行ファイルや共有ライブラリなど)を作成します。 機械語とソースコードの対応関係や、メモリ上のデータと変数の対応関係などがデバッグ情報として機械語とともに生成され、ELFファイルに格納されます。 Build IDはリンク時に生成します。 FedoraやRHELでは、その後実行に必要な部分だけを含むELFファイルと、デバッグ時に必要な情報だけを含むdebuginfoパッケージ用のELFファイルを生成します。
debuginfoパッケージを別のパッケージに分離することは技術上必須の作業ではありません。しかしサイズがかなり大きい(たとえば glibc パッケージは1.9MBに対して glibc-debuginfoとglibc-debugsource をあわせると9.8MB)ため、分離して配布されています。
「debuginfoの素早い検証」
実行に使っているrpmパッケージとdebuginfoパッケージのバージョンがずれてしまうことがあります。異なる内容のdebuginfoは間違った地図のようなもので、デバッグの役に立ちません。ここでBuild IDが活躍します。 実行ファイルのBuild IDと、debuginfoパッケージのELFファイルのBuild IDを対照して同じである場合にだけ利用します。 rpmパッケージのバージョンが対応することを利用しても確認できますが、この仕組みの方が簡単に行えます。(rpmのバージョン比較ではうまく処理できないケースにも対応できます。)
「ダンプに対応するバイナリの発見」
linuxはアプリケーションの異常時に「coredump」とよばれるプログラムのメモリの状態を出力します。このときに、利用している各ELFファイルのBuild IDを保存することで、新旧ふくめたどのELFファイルであるかを発見できるようになるというものです。Build ID登場以前には、ファイル名しか保存されていなかったため調査が難しくなることがありました。 /usr/lib/.build-id はこの Build IDからELFファイルへの逆引きのために利用されています。
以下はFedora 36で sleep コマンドへSIGQUITを送って出力させた coredump の情報です(注: systemd-coredumpによる情報収集によりcoreに含まれない情報も表示されています)。 build-id という欄で /usr/bin/sleep のBuild IDがわかり、"Module xxx with build-id xxxxxxxxxxxxx" のような行でライブラリのBuild IDもわかります。
PID: 36828 (sleep) UID: 11956 (kmoriwak) GID: 11956 (kmoriwak) Signal: 3 (QUIT) Timestamp: Wed 2022-09-14 17:13:06 JST (14s ago) Command Line: sleep 4 Executable: /usr/bin/sleep Control Group: /user.slice/user-11956.slice/user@11956.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-86bbb4b6-93e6-42cc-ae07-043f95a3625f.scope Unit: user@11956.service User Unit: vte-spawn-86bbb4b6-93e6-42cc-ae07-043f95a3625f.scope Slice: user-11956.slice Owner UID: 11956 (kmoriwak) Boot ID: 91c9b907c35f4893bb93d164afcaca94 Machine ID: b6024ba9744641fdb43d59bec9a81a4d Hostname: kmoriwak.usersys.redhat.com Storage: /var/lib/systemd/coredump/core.sleep.11956.91c9b907c35f4893bb93d164afcaca94.36828.1663143186000000.zst (present) Disk Size: 20.5K Package: coreutils/9.0-8.fc36 build-id: d5198b0c63efcb64e548b22d75f0349f8cac8e30 Message: Process 36828 (sleep) of user 11956 dumped core. Module linux-vdso.so.1 with build-id ef533bb262f1a045f813981c3d60075160f0756a Module ld-linux-x86-64.so.2 with build-id 1eacb7c50f7ed20ef1fefda3aa9c67377686acf5 Module libc.so.6 with build-id 9c5863396a11aab52ae8918ae01a362cefa855fe Module sleep with build-id d5198b0c63efcb64e548b22d75f0349f8cac8e30 Metadata for module sleep owned by FDO found: { (以下略)
複数バージョンのdebuginfoをシステムへ導入する
数年前(Fedora 26, RHEL 7)まで、複数バージョンのdebuginfoをシステムに同時にインストールすることはできませんでした。 実際にはシステムで同じパッケージの複数バージョンが動作しますので、これにあわせて複数バージョンのdebuginfoをインストールできるように 仕組みが改善されました。冒頭の /usr/lib/.build-id は、このタイミングで導入されています。
同じソフトウェアの複数バージョンが動作するのはどんな時でしょう。
- コンテナを利用している
- i686とx86_64 のような複数アーキテクチャのプログラムが同時に利用されている
- パッケージを更新したが古い実行ファイルで起動したプロセスの実行が続いている
さまざまなケースがありますが、OCIコンテナが普及して、コンテナイメージとホストOSの更新タイミングが一致しないケースが増えています。 そのような場合でもデバッグが容易になるよう、複数バージョンのdebuginfoをインストールできるようにする必要がありました。
/usr/lib/.build-id の通常rpmへの移動
以前のdebuginfoパッケージには、対応する実行用のrpmパッケージ内の実行ファイルや共有ライブラリ等へのシンボリックリンクが /usr/lib/debug/.build-id/ 以下に 含まれていました。もしも複数バージョンのdebuginfoをインストールできるように制限だけをゆるくすると、実行ファイル等と実際には対応しないシンボリックリンクが多数作成され、coredumpに含まれるBuild IDからバイナリを発見することが難しくなります。
そのため、Build IDから実行ファイルを発見するためのシンボリックリンクを実行用のrpmパッケージが持つように変更されました。
Build IDの応用
Fedora 31以降やRHEL 8.5以降、RHEL 9.0以降では、この記事で登場したBuild IDを応用した debuginfod を利用できます。詳しくは下の記事をご覧ください。