1. 概要

LD_PRELOADトリックは、実行時に共有ライブラリのリンクとシンボル(関数)の解決に影響を与える便利な手法です。 LD_PRELOAD について説明するために、まず、 Linuxシステム。

簡単に言うと、ライブラリはコンパイルされた関数のコレクションです。 同じ機能を書き直すことなく、プログラムでこれらの機能を利用できます。 これは、プログラムにライブラリコードを含めるか(静的ライブラリ)、実行時に動的にリンクする(共有ライブラリ)ことで実現できます。

静的ライブラリを使用して、スタンドアロンプログラムを構築できます。 一方、共有ライブラリで構築されたプログラムには、ランタイムリンカー/ローダーのサポートが必要です。 このため、プログラムを実行する前に、必要なすべてのシンボルがロードされ、プログラムの実行準備が整います

2. 実行時実行環境

LD_PRELOADトリックは、プログラム実行準備フェーズで役立ちます。Linuxシステムプログラムld.soおよびld-linux.so (ダイナミックリンカー/ローダー)は[ X176X] LD_PRELOAD は、指定された共有ライブラリをロードします。 特に、他のライブラリの前に、ダイナミックローダーは最初にLD_PRELOAD。にある共有ライブラリをロードします。

セキュア実行モードでは、LD_PRELOADのエントリは無視できることに注意してください。これは、パスにスラッシュが表示された場合に発生します。つまり、ファイルがデフォルトの検索パスにありません。 この場合、ダイナミックローダーは、ライブラリにsetuidビットが設定されている場合にのみシステムライブラリをロードします。

3. LD_PRELOADのユースケース

このセクションでは、LD_PRELOADトリックのいくつかのユースケースを試してみます。 まず、ライブラリをオーバーライドする方法を確認します。 後で、 LD_PRELOAD を使用して介入(ラップアラウンド)します。

LD_PRELOADは環境変数であるため、現在のプロセスにのみ影響します。したがって、絶対パスのみを使用します。

LD_PRELOAD を使用する前に、まずlddコマンドを使用してみましょう。 lddコマンドは、バイナリプログラムまたは共有ライブラリの実行時の依存関係を一覧表示するのに役立ちます

$ ldd /usr/bin/pigz
	linux-vdso.so.1 (0x00007ffd559d7000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa48bcc1000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa48bc9e000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fa48bc82000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa48ba90000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fa48c065000)

ldd、を使用すると、pigzプログラムがいくつかの共有ライブラリに依存していることがわかります。

使用済み共有ライブラリを表示するための他のオプションもあります。

3.1. LD_PRELOADを使用したライブラリの置き換え

ターミナルに戻って、LD_PRELOADのトリックの動作を見てみましょう。

$ LD_PRELOAD=/data/preload/lib/libz.so.1.2.7 ldd /usr/bin/pigz 
	linux-vdso.so.1 (0x00007ffc1d9c4000)
	/data/preload/lib/libz.so.1.2.7 (0x00007f33877d9000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3387674000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3387651000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f338745f000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f3387a32000)

これで、 pigzプログラムのzlib(libz)依存関係が、システムのデフォルトの場所からLD_PRELOADで設定した場所に変更されたことがわかります。

LD_PRELOAD で複数のプログラムを実行する場合は、exportunsetをそれぞれ使用して環境変数を設定およびクリアすることをお勧めします。 ここでこれを実行し、pigzプログラム自体を呼び出して変更を確認しましょう。

$ pigz -vV
pigz 2.4
zlib 1.2.11
 
$ export LD_PRELOAD=/data/preload/lib/libz.so.1.2.7
$ pigz -vV
pigz 2.4
zlib 1.2.7
 
$ unset LD_PRELOAD
$ pigz -vV
pigz 2.4
zlib 1.2.11

ここでは、pigz-vVオプションで呼び出して、pigzバージョンとzlibバージョンの両方を報告するようにしました。

3.2. LD_PRELOADを使用した介入

インターポーザーライブラリを使用して、システム関数の動作を変更してみましょう。 この例では、mallocにいくつかのアカウンティング機能を追加する架空のライブラリを使用します。

LD_PRELOADトリックを使用して、外部共有ライブラリに依存する任意のプログラムの動作を変更できます。 たとえば、lsプログラムはlibcに依存しており、mallocを使用したメモリ割り当てを含む多くのシステム機能を提供します。

$ ls -lh
total 2,8G
-rw-rw-r-- 1 baeldung_user baeldung_user 3,6K Jul  4 20:16 BVF_Density.ipynb
-rwxrwxr-x 1 baeldung_user baeldung_user 2,8G Jul  5 15:59 cuda_11.0.1_450.36.06_linux.run
drwxrwxr-x 2 baeldung_user baeldung_user   25 Jul  8 16:12 h5store
drwxrwxr-x 2 baeldung_user baeldung_user   30 Jul  5 18:47 pandas_processing
drwxrwxr-x 4 baeldung_user baeldung_user  142 Jul 19 15:59 preload
$ LD_PRELOAD=/data/preload/lib/malloc_interpose.so ls -lh
malloc(20000)  call number: 223
malloc(32816)  call number: 226
total 2,8G
-rw-rw-r-- 1 baeldung_user baeldung_user 3,6K Jul  4 20:16 BVF_Density.ipynb
-rwxrwxr-x 1 baeldung_user baeldung_user 2,8G Jul  5 15:59 cuda_11.0.1_450.36.06_linux.run
drwxrwxr-x 2 baeldung_user baeldung_user   25 Jul  8 16:12 h5store
drwxrwxr-x 2 baeldung_user baeldung_user   30 Jul  5 18:47 pandas_processing
drwxrwxr-x 4 baeldung_user baeldung_user  142 Jul 19 15:59 preload

インターポーザーライブラリがプリロードされているため、lsコマンドの出力は異なります

これは、カスタムビルドライブラリのmalloc関数が標準のmalloc関数よりも優先されるためです。 このインターポーザーの例では、 malloc 関数が、特定のメモリ割り当て呼び出しでのサイズと呼び出し数を出力しました。

LD_PRELOAD変数で複数のライブラリを指定することが可能です。 このために、コロンまたはスペースを使用して区切られたライブラリのリストを提供できます。

$ LD_PRELOAD="/data/preload/lib/malloc_interpose.so:/data/preload/lib/free_interpose.so" ls -lh
malloc(20000)  call number: 223
malloc(32816)  call number: 226
total 2,8G
free((nil))  call number: 174
free((nil))  call number: 175
free((nil))  call number: 178
-rw-rw-r-- 1 baeldung_user baeldung_user 3,6K Jul  4 20:16 BVF_Density.ipynb
-rwxrwxr-x 1 baeldung_user baeldung_user 2,8G Jul  5 15:59 cuda_11.0.1_450.36.06_linux.run
drwxrwxr-x 2 baeldung_user baeldung_user   25 Jul  8 16:12 h5store
drwxrwxr-x 2 baeldung_user baeldung_user   30 Jul  5 18:47 pandas_processing
drwxrwxr-x 4 baeldung_user baeldung_user  142 Jul 19 15:59 preload
free((nil))  call number: 180

ここで、別の架空のインターポーザーライブラリを LD_PRELOAD 変数に追加しました。このライブラリは、NULLポインターが渡されるシステム関数freeへの特定の呼び出しについて報告します。

4. これはいつ役に立ちますか?

LD_PRELOAD トリックは、状況によっては役立つ場合があります。

たとえば、2つのライブラリが同じシンボルをエクスポートし、プログラムが間違ったシンボルとリンクしている場合を考えてみます。 この場合、 LD_PRELOAD を使用して、正しいシンボルのライブラリをプリロードできます。 これを行うと、正しいシンボルの解像度が得られます。

もう1つのユースケースは、ライブラリ関数の最適化またはカスタム実装を優先する必要がある場合です。 元のライブラリを変更せずに、この最適化された実装またはカスタム実装をプリロードできます。 さらに、別のバージョンを提供することで、ライブラリ全体を置き換えることもできます。

さらに、さまざまなプロファイリングおよび監視ツールは、インストルメンテーションコードにLD_PRELOADを広く使用しています。 たとえば、パフォーマンスプロファイリングアプリケーションは、主要なシステム機能を挿入します。 これにより、プロファイラーはユーザーアプリケーションから関連データを収集できます。

5. LD_PRELOADの代替

LD_PRELOAD トリックの他に、/ etc/ld.so.preloadファイルを使用する代替があります。 ただし、これはシステム全体の設定です。 ほとんどのシステムでは、このファイルはデフォルトでは存在せず、システム管理者が作成する必要があります。

このファイルを作成し、プリロード用に古いバージョンの zlib を選択してから、pigzプログラムを再度テストします。

$ pigz -vV
pigz 2.4
zlib 1.2.11
 
$ sudo -i
# echo "/data/preload/lib/libz.so.1.2.8" > /etc/ld.so.preload
# exit
logout
 
$ pigz -vV
pigz 2.4
zlib 1.2.8

6. 結論

この記事では、LD_PRELOADトリックを使用するさまざまな方法について説明しました。

これは、特定のシナリオで役立つ可能性のある高度な手法です。 たとえば、開発者はライブラリまたはライブラリ内の一部の関数をすばやくデバッグおよびテストできます。

これは、介在技術を使用したプロファイリングおよび監視ツールでのコードインストルメンテーションのよく知られた方法でもあります。

さらに、システム管理者はこれを使用して、特定のユーザーまたはすべてのユーザーの実行環境を制御することもできます。