2025年02月04日
glibcはstatic linkしてはいけない
Ubuntu 20.4 LTS でビルドした Clang を 24.04 で実行した所、以下のようなエラーで動作しませんでした。
/home/kmc/test/aarch64/bin/clang: /lib/x86_64-linux-gnu/libpthread.so.0: version `GLIBC_PRIVATE' not found (required by /home/kmc/test/aarch64/bin/clang)以前 18.04 でビルドした Clang が依存する libtinfo のバージョンが、20.04 から libtinfo6 に上がったため動作しないという事例があったのですが(この時は libtinfo5 をインストールで解決)、今回はシステムの共有ライブラリの互換性は失われていないはずなのに動作しないのです。
本題に入る前の予備知識として、前述の libtinfo 互換性問題ですが、「static link すれば良かったのでは?」と、特に Windows プログラマの方は思うかもしれません。マル(アド)ウェアが仕込まれがちだったりと、Windows のインストーラは嫌われがちなので、単体の exe をコピーするだけで動作することが好まれるからです。
実際に Clang をビルドする時、CMake に以下のように static link するようにオプションを指定してみます。
いろいろ小細工をして、無理やり glibc を static link してみます。そのバイナリは、ビルドしたシステムではちゃんと動きます。しかし、別のシステムで動かしてみると…。
これは裏を返せば、dynamic linking している限り、glibc の互換性はかなり高いということです。glibc の本体である libc.so.6 は、以下のように膨大な数のバージョンに対応しています。
Ubuntu 20.04 の glibc のバージョンは 2.31 なので、上記のリストに入ってますし動くはずなのですが、なぜ動かないのでしょうか?
実は弊社のツールチェーンの Clang には様々な独自改造がされていて、そのパッチが使用するライブラリが CMake に追加されていたのですが、その追加の仕方が悪かったのです。
glibc を static link してしまうと、GLIBC_PRIVATE マーキングされている非公開シンボルまでリンクされてしまい、このシンボルの互換性は当然保証されないので、glibc のバージョンが変わると動かなくなる…というのが今回の真相となります。
今回の調査で、なぜ glibc を static link してはいけないのか、どこまでが glibc に含まれるのか(libtinfo は glibc の一部ではないのですが、ちゃんと警告を出してくれるのが親切ですね…)など、いろいろ勉強になりました。
実際に Clang をビルドする時、CMake に以下のように static link するようにオプションを指定してみます。
-DCMAKE_EXE_LINKER_FLAGS="-static"すると、以下のようにエラーになります。
/usr/bin/ld: 動的オブジェクト `/usr/lib/x86_64-linux-gnu/libtinfo.so' に対する静的リンクが試みられました clang: error: linker command failed with exit code 1 (use -v to see invocation) make[2]: *** [utils/TableGen/CMakeFiles/llvm-tblgen.dir/build.make:192: bin/llvm-tblgen] エラー 1 make[1]: *** [CMakeFiles/Makefile2:9421: utils/TableGen/CMakeFiles/llvm-tblgen.dir/all] エラー 2 make: *** [Makefile:156: all] エラー 2実は Ubuntu がシステムの libc として使用する glibc は、本質的に dynamic linking な性質があり、完全な static link はできない仕様なのです。(今回の話題の大前提)
いろいろ小細工をして、無理やり glibc を static link してみます。そのバイナリは、ビルドしたシステムではちゃんと動きます。しかし、別のシステムで動かしてみると…。
$ ldd ./bin/clang not a dynamic executable $ ./clang --version Fatal glibc error: dl-call-libc-early-init.c:37 (_dl_call_libc_early_init): assertion failed: sym != NULL PLEASE submit a bug report to https://bugs.llvm.org/ and include the crash backtrace, preprocessed source, and associated run script. Stack dump:このように、static link された glibc のバージョンと、システムの glibc のバージョンが異なると全く動かないバイナリになります。つまり、static link した方がむしろ互換性は無くなるという性質が glibc にはあるのです。
これは裏を返せば、dynamic linking している限り、glibc の互換性はかなり高いということです。glibc の本体である libc.so.6 は、以下のように膨大な数のバージョンに対応しています。
$ strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC GLIBC_2.2.5 GLIBC_2.2.6 GLIBC_2.3 GLIBC_2.3.2 GLIBC_2.3.3 GLIBC_2.3.4 GLIBC_2.4 GLIBC_2.5 GLIBC_2.6 GLIBC_2.7 GLIBC_2.8 GLIBC_2.9 GLIBC_2.10 GLIBC_2.11 GLIBC_2.12 GLIBC_2.13 GLIBC_2.14 GLIBC_2.15 GLIBC_2.16 GLIBC_2.17 GLIBC_2.18 GLIBC_2.22 GLIBC_2.23 GLIBC_2.24 GLIBC_2.25 GLIBC_2.26 GLIBC_2.27 GLIBC_2.28 GLIBC_2.29 GLIBC_2.30 GLIBC_2.31 GLIBC_2.32 GLIBC_2.33 GLIBC_2.34 GLIBC_2.35 GLIBC_2.36 GLIBC_2.38 GLIBC_2.39 GLIBC_ABI_DT_RELR GLIBC_PRIVATE GNU C Library (Ubuntu GLIBC 2.39-0ubuntu8.3) stable release version 2.39.glibc に含まれるシンボル(関数や変数)には全て上記の GLIBC_2.XX という情報が付いていて、デフォルトの glibc のバージョンが異なる場合でも、適切なバージョンのシンボルが動的リンクされるという、ものすごい互換性維持のための努力を glibc はしているのです。すごい!
Ubuntu 20.04 の glibc のバージョンは 2.31 なので、上記のリストに入ってますし動くはずなのですが、なぜ動かないのでしょうか?
実は弊社のツールチェーンの Clang には様々な独自改造がされていて、そのパッチが使用するライブラリが CMake に追加されていたのですが、その追加の仕方が悪かったのです。
set(Boost_USE_STATIC_LIBS ON) find_package(Boost REQUIRED filesystem thread chrono) 。。。 target_link_libraries(XXX PRIVATE 。。。 ${Boost_LIBRARIES} Threads::Threads rt.a )static link できるものは static link した方が良いという思想で開発メンバーは rt.a と書いたのだと思われますが、実はこの librt は glibc の一部なので、同様に static link してはいけなかったのです。(この .a を付けると static link されるという CMake の仕様も、私は今回の件を調査するまで知りませんでしたし、解決に時間がかかった原因となりました。)
glibc を static link してしまうと、GLIBC_PRIVATE マーキングされている非公開シンボルまでリンクされてしまい、このシンボルの互換性は当然保証されないので、glibc のバージョンが変わると動かなくなる…というのが今回の真相となります。
今回の調査で、なぜ glibc を static link してはいけないのか、どこまでが glibc に含まれるのか(libtinfo は glibc の一部ではないのですが、ちゃんと警告を出してくれるのが親切ですね…)など、いろいろ勉強になりました。