続・玩式草子 ―戯れせんとや生まれけん―

第31回共有ライブラリと依存関係[3]

前回は、システムに存在する共有ライブラリのデータベースを作り、そのデータベースからライブラリの依存関係を検索するget_depends.pyとquery_depends.pyの基本的な使い方を紹介しました。今回はこのツールを使って、ディストリビューションにとって厄介な"library not found"のエラーを検出する方法と、それに関わるいくつかの話題を紹介しましょう。

"library not found"問題

インストールしたシステムを、パッケージ更新や自前でソフトウェアをインストールしたりしながら使い込んでいると、こんなエラーメッセージに遭遇することがよくあります。

$ qdoc
qdoc: error while loading shared libraries: libclang.so.10: cannot open shared object file: No such file or directory

前回までに紹介してきたように、このエラーは「指定されたバイナリ(qdoc)に記録されている必要な共有ライブラリ(libclang.so.10)が見つからない」という意味で、原因も対策もはっきりしているものの、実際にそのバイナリを実行するまでトラブルの存在に気づけないので、結構厄介です。

このエラーを出しているバイナリファイルをlddで調べると、必要なライブラリが見つからなかった場合、"not found"が返ります。

$ ldd /usr/bin/qdoc 
      linux-vdso.so.1 (0x00007ffe6ac7e000)
      libclang.so.10 => not found
      libQt5Core.so.5 => /usr/lib/libQt5Core.so.5 (0x00007f530f6b8000)
      libpthread.so.0 => /lib/libpthread.so.0 (0x00007f530f697000)
      libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f530f48b000)
      ...

一方、前回紹介したget_depends.pyスクリプトでは、lddの結果を単なる文字列としてそのまま依存関係DBに書き込むので、ライブラリのフルパス名ではない"not found"という結果でも、そのままDBに書き込んでしまいます。

$ ./query_depends.py -p /usr/bin/qdoc 
/usr/bin/qdoc needs these libraries
  linux-vdso.so.1 (0x00007ffd639d5000)()
  libclang.so.10(not found)
  libQt5Core.so.5(/usr/lib/libQt5Core.so.5)
  libpthread.so.0(/lib/libpthread.so.0)
  ...

一見、これは問題になりそうな仕様なものの、逆に見ると、通常はそのバイナリを実行するまで気づかない"library not found"なエラーを、"not found"を手掛かりにDBで調べればあらかじめチェックできる、ということになります。

この"not found"な情報は、DBのrealpathフィールドに記録されるので、query_depends.pyでは"-r"オプションで指定します。さて、それではPlamo-7.3をインストールした直後の環境で、"library not found"なエラーが潜在しているかチェックしてみましょう。

$ ./query_depends.py -r 'not found' | cat -n
     1        not found used by these binaries
     2          appstream-builder(/usr/bin/appstream-builder)
     3          appstream-builder(/usr/bin/appstream-builder)
     ...
     7          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
     8          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
     ...
     26         libxul.so(/usr/lib/firefox/libxul.so)
     27         libxul.so(/usr/lib/firefox/libxul.so)
     ....
    139         libpostgresql-sdbc-impllo.so(/opt/libreoffice7.1/program/libpostgresql-sdbc-impllo.so)
    140         libpostgresql-sdbc-impllo.so(/opt/libreoffice7.1/program/libpostgresql-sdbc-impllo.so)

おやおや、思ったよりも大量のバイナリから「ライブラリが見つからない」旨のエラーが報告されました。また、該当するバイナリは"samba"や"firefox"、"libreoffice"といった特定のジャンルに集中しているようです。これは一体どうしたことでしょう?

共有ライブラリのサーチパス

実はこの問題は共有ライブラリのサーチパスに由来します。というのも、lddは必要な共有ライブラリを探す際、/etc/ld.so.confに指定されたディレクトリのみを対象とし、それ以外の場所に置かれた共有ライブラリは検索しません。

一方、sambaやfirefox,libreofficeといった大規模なプロジェクトでは、自前で提供する共有ライブラリは/usr/lib/samba//usr/lib/firefox/といった専用のディレクトリに収めてシステムの汎用的な共有ライブラリとは区別しておき、コマンドを実行する際に$LD_LIBRARY_PATH環境変数に自前の共有ライブラリの置き場を設定して必要なライブラリを見つける、という方法を取っています。

そのため、これらのプロジェクトの提供するバイナリを単独でlddで調べてみると"library not found"となるものの、実際にそのバイナリを動作させる際には必要な共有ライブラリはちゃんと見つかり、問題なく実行できる、ということになります。

ざっと調べたところ、Plamoのデフォルト環境では、"samba"、"firefox"、"thunderbird"、"java(openjdk)"、"tetex"、"libreoffice"といったパッケージが、このスタイルで自前の共有ライブラリを提供しているので、とりあえず、これらのパッケージに由来するバイナリの"not found"は無視することにします。すると残りは6つくらいのバイナリに絞られてきました。

$ ./query_depends.py -r 'not found' | grep -v samba | grep -v firefox | grep -v thunderbird | \
     grep -v java | grep -v libreoffice | cat -n
     1        not found used by these binaries
     2          appstream-builder(/usr/bin/appstream-builder)
     3          appstream-builder(/usr/bin/appstream-builder)
     4          appstream-builder(/usr/bin/appstream-builder)
     5          appstream-util(/usr/bin/appstream-util)
     6          appstream-compose(/usr/bin/appstream-compose)
     7          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
     8          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
     9          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
    10          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
    11          libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
    12          libappstream-glib.so.8.0.10(/usr/lib/libappstream-glib.so.8.0.10)
    13          Libproxy.so(/usr/lib/perl5/site_perl/auto/Net/Libproxy/Libproxy.so)

「入れるべきか、入れざるべきか……」

これくらいまで絞り込んだ"library not found"なバイナリを改めてlddで確認してみると、libsmbldap.so.2はsambaパッケージが提供する/usr/lib/samba/以下の共有ライブラリを要求し、Perl5のモジュールであるLibproxy.soは同じ"perl"パッケージに含まれている/usr/lib/perl5/CORE/libperl.soを要求しているので、どちらもfirefox等の場合と同じく、必要なライブラリは存在するものの、標準的な場所にはないために見つからなかったようです。

一方、"appstream-builder"や"appstream-util"は"librpm.so.9"等を必要とするものの、この共有ライブラリを提供する"rpm"パッケージは意図的にインストールしていません。

$ ldd /usr/bin/appstream-builder | grep found
      librpm.so.9 => not found
      librpmio.so.9 => not found
      librpmio.so.9 => not found

もちろん、appstream-builderやappstream-utilをビルドした環境には、rpmパッケージも入っていて、/usr/lib/librpm.so.9も参照できていました。しかし確認してみると、これらのライブラリはGNOME方面で開発されているメディアプレイヤーlollypopappstream-glibを必要とするためインストールされたものの、実のところ、lollypopの動作にはappstream-glibは必要ではなく、ビルドの最終段階でこのパッケージに含まれるappstream-utilコマンドを使い、Appstream用のメタ情報を作っているだけでした。

それならばわざわざappstream-glibのためだけに、かなり規模が大きいrpmパッケージをインストールして、不要な依存関係を増やす必要も無いだろう、と判断し、rpmパッケージはお好みでインストールするcontrib/utis/以下に置くことにしたのでした。

もっとも、こういう風に整理してみると「そもそもappstream-glibも不要では?」という気もしてきましたが、そのあたりの判断は次の更新の際に先送りすることにしましょう(苦笑)


先に、標準的な場所にインストールされていないライブラリを"not found"の対象から外すために、"| grep -v firefox | grep -v java |..." みたいな操作で出力結果を掃除しました。一方、これらのライブラリがインストールされている場所を/etc/ld.so.confに追加して、改めてldconfigした上で依存情報データベースを作り直す、という方法もあります。

最近では、ldconfigの検索場所を追加する場合、直接/etc/ld.so.confに書き足すのではなく、/etc/ld.so.conf.d/ディレクトリに"xxxx.conf"の名前で追加するようになっているので、たとえばこのようにしておけば、query_depends.pyの出力を操作する必要は無くなるでしょう。

$ sudo cat << EOF > /etc/ld.so.conf.d/append_libs.conf
/usr/lib/firefox
/usr/lib/thunderbird
/usr/lib/samba/
/usr/java/jdk/lib
/usr/java/jdk/lib/server
/opt/texlive/2019/lib
/opt/libreoffice/program
EOF
$ sudo ldconfig -v
$ rm -f depends.sql3
$ sudo ./get_depends.py

もっとも上述のように、これらのディレクトリをlddの検索対象に追加しなくても運用上は問題ないので、依存関係DBのためだけに設定を変更するのも何だかな…… というところです。

おすすめ記事

記事・ニュース一覧