Linuxの共有ライブラリ管理:ldconfig、ldd、LD_LIBRARY_PATHの完全ガイド

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

問題:アプリのコンパイルは成功したのに、実行できない

C++ アプリケーションをビルドして、コンパイルがクリーンに通ったとしよう。そして実行してみると:

./myapp
./myapp: error while loading shared libraries: libfoo.so.2: cannot open shared object file: No such file or directory

ライブラリは /usr/local/lib にちゃんとある。目で確認できる。それなのに Linux は見つけられない——まるでシステムが嘘をついているかのようだ。

嘘ではない。Linux は実行時に共有ライブラリを探すためにファイルシステム全体を走査するわけではない。あらかじめ構築されたキャッシュを参照するのだ。そのキャッシュにディレクトリが含まれていなければ、ファイルが 3 つ先のフォルダにあってもダイナミックリンカーは失敗する。この仕組みが理解できれば、謎だった修正方法が一気に明確になる。

Linux が共有ライブラリを解決する仕組み:3 つのアプローチ

Linux が共有ライブラリを探す場所を制御するメカニズムは 3 つある。それぞれスコープ、永続性、適切な使いどころが異なる。

アプローチ 1:ldconfig によるシステム全体への登録

ldconfig/etc/ld.so.conf/etc/ld.so.conf.d/ 以下のドロップインファイルを読み込み、記載されたすべてのディレクトリをスキャンして、バイナリキャッシュを /etc/ld.so.cache に書き込む。起動時、ダイナミックリンカー(ld.so)はそのキャッシュを直接参照する——ファイルシステムの走査なし、高速なルックアップだけだ。

デフォルトの検索パスは /lib/lib64/usr/lib/usr/lib64 だ。これらのディレクトリ外にあるものは明示的な登録が必要になる。

アプローチ 2:LD_LIBRARY_PATH 環境変数

LD_LIBRARY_PATH は、現在のプロセスとその子プロセスのライブラリ検索パスの先頭に追加ディレクトリを付加する。ldconfig のキャッシュを完全に上書きする。

export LD_LIBRARY_PATH=/usr/local/lib/myapp:$LD_LIBRARY_PATH
./myapp

キャッシュの再構築なし。設定ファイルなし。root 権限なし。変更は現在のシェルで即座に反映される。

アプローチ 3:リンク時にパスを埋め込む(rpath)

コンパイル時に -Wl,-rpath を使ってバイナリ自体に検索パスを埋め込むことができる:

gcc -o myapp main.c -L/usr/local/lib -lfoo -Wl,-rpath,/usr/local/lib

そのバイナリは ldconfig や LD_LIBRARY_PATH に関係なく、常にそのディレクトリを最初にチェックする。デプロイ後の外部設定は不要だ。

各アプローチのメリットとデメリット

アプローチ メリット デメリット
ldconfig システム全体、永続的、クリーン、すべてのプロセスが恩恵を受ける root 権限が必要、新しいライブラリ追加後にキャッシュの再構築が必要
LD_LIBRARY_PATH 即時反映、root 不要、テストに最適 セッションのみ有効、グローバル設定はセキュリティリスク、システムライブラリを暗黙的に上書きする
rpath 自己完結型バイナリ、システム設定不要で動作 コンパイル後の変更が困難、ライブラリが移動すると動作しなくなる

LD_LIBRARY_PATH のセキュリティリスクは真剣に受け止める必要がある。そのパスに悪意のあるライブラリを置けば、そのセッションで動作するあらゆるプログラムを乗っ取ることができる。それが、カーネルが setuid バイナリに対して LD_LIBRARY_PATH を完全に無視する理由だ。/etc/environment やシステム全体のログインスクリプトには絶対に追加しないこと。

ほとんどのケースで推奨されるセットアップ

/usr/local に入るライブラリや、パッケージマネージャーでインストールしたものには ldconfig を使おう。クリーンで永続的、システム上のすべてのプロセスから利用可能だ。

ライブラリのバージョンを試したり、ビルドをデバッグしたりするときは LD_LIBRARY_PATH が便利だ——ただし、現在のターミナルセッションのみに限定すること。本番環境の init スクリプトに漏れないよう注意しよう。

スタンドアロンのアプリケーションバンドルやコンテナイメージを配布する場合は rpath が真価を発揮する。バイナリ自身がパス情報を持ち、デプロイ先ホストの ldconfig の状態に依存しない。

私が管理する Ubuntu 22.04 の本番サーバーでは、これらの戦略を組み合わせることで——システムライブラリには ldconfig、バンドルツールには rpath——デプロイ時の摩擦が目に見えて減った。新しいエンジニアが同じマシンをプロビジョニングしても、手動の設定なしでライブラリが解決される。

実装ガイド

ステップ 1:ldd で問題を診断する

毎回ここから始めよう。ldd はバイナリが必要とするすべての共有ライブラリを列挙し、リンカーが各ライブラリを見つけられるかどうかを示す:

ldd /usr/bin/myapp

出力例:

linux-vdso.so.1 (0x00007ffd3e9f7000)
libfoo.so.2 => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b2a200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b2a600000)

この not found の行が調査対象だ。他はすべて解決済みだ。次に、ファイルが実際に存在する場所を探そう:

find /usr -name "libfoo.so*" 2>/dev/null
find /usr/local -name "libfoo.so*" 2>/dev/null

ステップ 2:ldconfig で新しいライブラリディレクトリを登録する

ソースからライブラリをコンパイルして /usr/local/lib に配置した?そのパスはデフォルトの ldconfig リストにないことが多い。1 つのコマンドで追加しよう:

echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/local-libs.conf

キャッシュを再構築する:

sudo ldconfig

動作を確認する:

ldconfig -p | grep libfoo
# 期待される出力:
# libfoo.so.2 (libc6,x86-64) => /usr/local/lib/libfoo.so.2

もう一度 ldd を実行しよう。not found のエントリは消えているはずだ。

ステップ 3:不足しているシンボリックリンクを処理する

ライブラリファイルは存在するのにシンボリックリンクが壊れていたり、存在しなかったりすることがある。共有ライブラリは 3 つの名前を使う慣習がある:

  • libfoo.so.2.1.3 — 実際のバージョン付きファイル
  • libfoo.so.2 — soname シンボリックリンク(プログラムが実行時にリンクするもの)
  • libfoo.so — リンカー名(コンパイル時のみ使用)

バージョン付きファイルのみが存在する場合は、シンボリックリンクを手動で作成しよう:

cd /usr/local/lib
sudo ln -sf libfoo.so.2.1.3 libfoo.so.2
sudo ln -sf libfoo.so.2 libfoo.so
sudo ldconfig

ステップ 4:テスト用に LD_LIBRARY_PATH を使う

システム設定を変更せずに特定のライブラリバージョンをテストしたいときは、現在のシェルで変数を設定しよう:

# 一時的、現在のセッションのみ
export LD_LIBRARY_PATH=/home/user/myproject/libs:$LD_LIBRARY_PATH
./myapp

# または 1 回の実行のみインラインで
LD_LIBRARY_PATH=/home/user/myproject/libs ./myapp

同じライブラリの複数バージョンが存在する場合、リンカーがどれを選ぶかは必ずしも明らかではない。LD_DEBUG を使えば、実際に何が起きているかを確認できる:

LD_DEBUG=libs ./myapp 2>&1 | grep libfoo

ステップ 5:パッケージマネージャーのインストール後に確認する

apt や dnf のようなパッケージマネージャーは、ポストインストールスクリプトで自動的に ldconfig を実行する。tarball からの手動インストールはそうではない。手動でライブラリをインストールした後は、自分で実行しよう:

# 手動でライブラリをインストールした後
sudo ldconfig

# 確認
ldconfig -p | grep "your-library-name"

ボーナス:実行中のプロセスが使用しているものを確認する

実行中のプロセスを検査するために再起動は不要だ。/proc から直接読み取ろう:

# PID を取得する
pgrep myapp

# そのプロセスの全ロード済みライブラリを列挙する
cat /proc/<PID>/maps | grep "\.so"

# または <a href="https://itnotes.dev/ja/lsof%e3%82%92%e4%bd%bf%e3%81%84%e3%81%93%e3%81%aa%e3%81%99%ef%bc%9a%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%e3%82%84%e3%83%9d%e3%83%bc%e3%83%88%e3%82%92%e4%bd%bf%e7%94%a8%e3%81%97%e3%81%a6%e3%81%84/">lsof</a> を使う
lsof -p <PID> | grep ".so"

クイックリファレンス:デシジョンツリー

  1. ldd ./binary を実行して——何が不足しているかを特定する。
  2. find /usr /usr/local -name "libXXX.so*" を実行して——ファイルが存在するか確認する。
  3. ファイルは存在するがリンカーが見つけられない場合:ディレクトリを /etc/ld.so.conf.d/ に追加して sudo ldconfig を実行する。
  4. シンボリックリンクが不足している場合:手動で作成してから sudo ldconfig を実行する。
  5. ライブラリが本当にインストールされていない場合:dev パッケージを入手する(Debian/Ubuntu では apt install libfoo-dev、RHEL/Fedora では dnf install libfoo-devel)。
  6. テスト目的のみ:現在のセッションに限定して LD_LIBRARY_PATH を使用し、それ以外では使わない。

共有ライブラリのエラーは、1 つのことを理解するまで謎めいて見える:Linux はファイルシステムのスキャンではなく、キャッシュを使っているということだ。このメンタルモデルが確立されれば、解決策はほぼ常に上記の 5 つのステップのどれかになる。毎回 ldd から始めよう——リンカーが何を見ているかを正確に示し、推測を排除できる。

Share: