ビルドしたバイナリーが使われないときはldd/otool - 2023-08-25 - ククログ

ククログ

株式会社クリアコード > ククログ > ビルドしたバイナリーが使われないときはldd/otool

ビルドしたバイナリーが使われないときはldd/otool

C/C++を使って開発することも多い須藤です。

C/C++を使って開発をしている場合、手元でビルドしている開発用のバージョンとリリースされてパッケージ化されたバージョンが同一環境に混在することがあります。たとえば、Groongaを開発していると、自分でビルドしたGroongaとaptでインストールしたGroongaが混在することがあります。そのような場合、手元でビルドしたバージョンを使っているつもりでもパッケージでインストールしたバージョンが使われてしまって「なぜ手元での変更が反映されないのだ。。。」となってしまいます。このような場合はldd/otoolを使って問題を切り分けていくことができるのでその方法を紹介します。

ビルドしたバイナリーが使われない環境

まずはビルドしたバイナリーが使われない環境を作ってみましょう。

パッケージでGroongaをインストールします。

sudo apt install -y groonga-bin

このGroongaは/usr/以下にインストールされています。

$ which groonga
/usr/bin/groonga
$ groonga --version
Groonga 13.0.5 [Linux,x86_64,utf8,match-escalation-threshold=0,nfkc,mecab,message-pack,mruby,onigmo,zlib,lz4,zstandard,epoll,rapidjson,apache-arrow,xxhash]

開発用にGroongaの最新版を手元でインストールしましょう。このGroongaを/usr/以下にインストールするとパッケージでインストールしたGroongaを上書きしてしまうので/tmp/local/以下にインストールします。

apt install -y g++ git cmake ninja-build
git clone --recursive https://github.com/groonga/groonga.git
cmake -S groonga -B groonga.build -G Ninja -DCMAKE_INSTALL_PREFIX=/tmp/local -DCMAKE_BUILD_TYPE=Debug
ninja -C groonga.build install

普通は/tmp/local/binPATHは通っていないので手元でビルドしたGroongaは見つかりません。

$ which groonga
/usr/bin/groonga

/tmp/local/bin/groongaと絶対パスで指定するか、PATH/tmp/local/binを加えて実行しないと手元でビルドしたGroongaは見つかりません。

$ /tmp/local/bin/groonga --version
Groonga 13.0.5 [Linux,x86_64,utf8,match-escalation-threshold=0,nfkc,onigmo,zstandard,epoll,rapidjson]
$ PATH=/tmp/local/bin:$PATH which groonga
/tmp/local/bin/groonga
$ PATH=/tmp/local/bin:$PATH groonga --version
Groonga 13.0.5 [Linux,x86_64,utf8,match-escalation-threshold=0,nfkc,onigmo,zstandard,epoll,rapidjson]

これで解決のように見えますが、実はそうではありません。実は、現時点の最新版のGroongaのバージョンは13.0.6なのでバージョンが違います。手元でビルドしたGroongaが使われていません。

ということで、ビルドしたバイナリーが使われていない環境ができました。

ビルドしたバイナリーが使われていない状態の調査方法

では、このようなときに何を調べたらいいかというと「どの共有ライブラリーが使われているか」です。これはLinuxではldd、macOSではotool -Lで調べられます。この環境はDebian GNU/Linux bookwormなのでlddで調べましょう。

$ ldd /tmp/local/bin/groonga
ldd /tmp/local/bin/groonga
	linux-vdso.so.1 (0x00007fffa6dfd000)
	libgroonga.so.0 => /lib/x86_64-linux-gnu/libgroonga.so.0 (0x00007f34724d5000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f34722f4000)
	libarrow.so.1200 => /lib/x86_64-linux-gnu/libarrow.so.1200 (0x00007f346ff9c000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f346ff7d000)
	libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007f346fec1000)
	libmsgpackc.so.2 => /lib/x86_64-linux-gnu/libmsgpackc.so.2 (0x00007f346feb6000)
	libxxhash.so.0 => /lib/x86_64-linux-gnu/libxxhash.so.0 (0x00007f346fea1000)
	liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f346fe7b000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f346fc61000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f346fb82000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f346fb62000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f3473554000)
	libbrotlienc.so.1 => /lib/x86_64-linux-gnu/libbrotlienc.so.1 (0x00007f346facf000)
	libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007f346fac2000)
	libprotobuf.so.32 => /lib/x86_64-linux-gnu/libprotobuf.so.32 (0x00007f346f792000)
	libutf8proc.so.2 => /lib/x86_64-linux-gnu/libutf8proc.so.2 (0x00007f346f73b000)
	libre2.so.9 => /lib/x86_64-linux-gnu/libre2.so.9 (0x00007f346f6c2000)
	libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f346f241000)
	libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0 (0x00007f346f22c000)
	libsnappy.so.1 => /lib/x86_64-linux-gnu/libsnappy.so.1 (0x00007f346f220000)
	libabsl_bad_optional_access.so.20220623 => /lib/x86_64-linux-gnu/libabsl_bad_optional_access.so.20220623 (0x00007f346f21b000)
	libabsl_str_format_internal.so.20220623 => /lib/x86_64-linux-gnu/libabsl_str_format_internal.so.20220623 (0x00007f346f202000)
	libabsl_time.so.20220623 => /lib/x86_64-linux-gnu/libabsl_time.so.20220623 (0x00007f346f1f0000)
	libabsl_strings.so.20220623 => /lib/x86_64-linux-gnu/libabsl_strings.so.20220623 (0x00007f346f1d2000)
	libabsl_strings_internal.so.20220623 => /lib/x86_64-linux-gnu/libabsl_strings_internal.so.20220623 (0x00007f346f1ca000)
	libabsl_throw_delegate.so.20220623 => /lib/x86_64-linux-gnu/libabsl_throw_delegate.so.20220623 (0x00007f346f1c3000)
	libabsl_time_zone.so.20220623 => /lib/x86_64-linux-gnu/libabsl_time_zone.so.20220623 (0x00007f346f1a9000)
	libabsl_bad_variant_access.so.20220623 => /lib/x86_64-linux-gnu/libabsl_bad_variant_access.so.20220623 (0x00007f346f1a4000)
	libcurl.so.4 => /lib/x86_64-linux-gnu/libcurl.so.4 (0x00007f346f0f7000)
	libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007f346f0d2000)
	libabsl_int128.so.20220623 => /lib/x86_64-linux-gnu/libabsl_int128.so.20220623 (0x00007f346f0cb000)
	libabsl_base.so.20220623 => /lib/x86_64-linux-gnu/libabsl_base.so.20220623 (0x00007f346f0c5000)
	libabsl_raw_logging_internal.so.20220623 => /lib/x86_64-linux-gnu/libabsl_raw_logging_internal.so.20220623 (0x00007f346f0c0000)
	libnghttp2.so.14 => /lib/x86_64-linux-gnu/libnghttp2.so.14 (0x00007f346f091000)
	libidn2.so.0 => /lib/x86_64-linux-gnu/libidn2.so.0 (0x00007f346f05e000)
	librtmp.so.1 => /lib/x86_64-linux-gnu/librtmp.so.1 (0x00007f346f03f000)
	libssh2.so.1 => /lib/x86_64-linux-gnu/libssh2.so.1 (0x00007f346effe000)
	libpsl.so.5 => /lib/x86_64-linux-gnu/libpsl.so.5 (0x00007f346efea000)
	libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007f346ef41000)
	libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f346eeef000)
	libldap-2.5.so.0 => /lib/x86_64-linux-gnu/libldap-2.5.so.0 (0x00007f346ee8e000)
	liblber-2.5.so.0 => /lib/x86_64-linux-gnu/liblber-2.5.so.0 (0x00007f346ee7e000)
	libabsl_spinlock_wait.so.20220623 => /lib/x86_64-linux-gnu/libabsl_spinlock_wait.so.20220623 (0x00007f346ee79000)
	libunistring.so.2 => /lib/x86_64-linux-gnu/libunistring.so.2 (0x00007f346ecc3000)
	libgnutls.so.30 => /lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007f346eaa7000)
	libhogweed.so.6 => /lib/x86_64-linux-gnu/libhogweed.so.6 (0x00007f346ea5c000)
	libnettle.so.8 => /lib/x86_64-linux-gnu/libnettle.so.8 (0x00007f346ea0e000)
	libgmp.so.10 => /lib/x86_64-linux-gnu/libgmp.so.10 (0x00007f346e98d000)
	libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007f346e8b3000)
	libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007f346e886000)
	libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007f346e87e000)
	libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007f346e870000)
	libsasl2.so.2 => /lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007f346e853000)
	libp11-kit.so.0 => /lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007f346e71f000)
	libtasn1.so.6 => /lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007f346e70a000)
	libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007f346e701000)
	libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f346e6f0000)
	libffi.so.8 => /lib/x86_64-linux-gnu/libffi.so.8 (0x00007f346e6e4000)

共有ライブラリ名 => 共有ライブラリーのパス (アドレス)というフォーマットでどの共有ライブラリーが使われているかが表示されています。しかし、出力が多いので絞り込みましょう。どう絞り込むかというと、インストール先の共有ライブラリーが使われているかで絞り込みます。今回の場合は/tmp/local/以下にインストールしているので/tmp/local/以下の共有ライブラリーが使われているかを確認します。

$ ldd /tmp/local/bin/groonga | grep /tmp/local/
$

なにも表示されません。ということは、/tmp/local/以下の共有ライブラリーは使われていません。

では、どの共有ライブラリーが使われているとよいのでしょうか。/tmp/local/lib/以下にある共有ライブラリーを確認します。(この環境はdocker run --rm debian:bookwormで作っているのでrootユーザーで実行しています。)

$ ls -l /tmp/local/lib
total 30812
drwxr-xr-x 3 root root     4096 Aug 25 02:24 cmake
drwxr-xr-x 3 root root     4096 Aug 25 02:24 groonga
lrwxrwxrwx 1 root root       15 Aug 25 02:24 libgroonga.so -> libgroonga.so.0
lrwxrwxrwx 1 root root       19 Aug 25 02:24 libgroonga.so.0 -> libgroonga.so.0.0.0
-rw-r--r-- 1 root root 31535616 Aug 25 02:24 libgroonga.so.0.0.0
drwxr-xr-x 2 root root     4096 Aug 25 02:24 pkgconfig

libgroonga.soがあるので、libgroonga.soとして/tmp/local/lib/libgroonga.soが使われていて欲しいということがわかります。それではlibgroonga.soはどのパスのものが使われているかを確認します。

$ ldd /tmp/local/bin/groonga | grep libgroonga
	libgroonga.so.0 => /lib/x86_64-linux-gnu/libgroonga.so.0 (0x00007fd07016c000)

/lib/x86_64-linux-gnu/libgroonga.so.0が使われています。Debian GNU/Linux bookwormでは/lib/usr/libへのシンボリックリンクなので、/usr/lib/以下の共有ライブラリーが使われているということです。つまり、パッケージでインストールしたGroongaが使われていたということです。パッケージでインストールしているGroongaは13.0.5なのでgroonga --versionで表示されたバージョンが13.0.5になっていたことの辻褄もあいます。

$ dpkg -l | grep libgroonga
ii  libgroonga0:amd64         13.0.5-1                       amd64        Library files for Groonga
$ /tmp/local/bin/groonga --version
Groonga 13.0.5 [Linux,x86_64,utf8,match-escalation-threshold=0,nfkc,onigmo,zstandard,epoll,rapidjson]

ビルドしたバイナリーを使う方法

では、どうすれば手元でビルドしたバイナリーを使ってもらえるかというと、パスを設定します。コマンドを実行するときにPATHの中からコマンドが探されるように、共有ライブラリーもパスから探されます。共有ライブラリーを探す場所はPATHではなくLD_LIBRARY_PATHで指定できます。ただ、デフォルトではLD_LIBRARY_PATHは空です。この場合はデフォルトのパスだけが使われます。

共有ライブラリーを探すデフォルトのパスがどこかは/etc/ld.so.confを見ればわかります。

$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
$ ls -l /etc/ld.so.conf.d/*.conf
-rw-r--r-- 1 root root  44 Sep 22  2022 /etc/ld.so.conf.d/libc.conf
-rw-r--r-- 1 root root 100 Jul 13 18:07 /etc/ld.so.conf.d/x86_64-linux-gnu.conf
$ cat /etc/ld.so.conf.d/*.conf
# libc default configuration
/usr/local/lib
# Multiarch support
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu

ということで、以下のディレクトリーにある共有ライブラリーを探すということがわかりました。

  • /usr/local/lib
  • /usr/local/lib/x86_64-linux-gnu
  • /lib/x86_64-linux-gnu
  • /usr/lib/x86_64-linux-gnu

/lib/x86_64-linux-gnu/libgroonga.so.0のディレクトリーは/lib/x86_64-linux-gnuですが、たしかに↑の中に含まれています。

LD_LIBRARY_PATHを指定するとまずはそのディレクトリーから検索してくれるようになります。(その後、デフォルトのパスから検索します。詳細はman ld.soを読んでください。)

LD_LIBRARY_PATH=/tmp/local/libを指定して手元でビルドした共有ライブラリーを使ってもらうようにしましょう。

$ LD_LIBRARY_PATH=/tmp/local/lib ldd /tmp/local/bin/groonga | grep libgroonga
	libgroonga.so.0 => /tmp/local/lib/libgroonga.so.0 (0x00007ff8bf002000)

手元でビルドしたバイナリーを使ってくれるようになりましたね。

バージョン情報も確認してみましょう。

$ LD_LIBRARY_PATH=/tmp/local/lib /tmp/local/bin/groonga --version
Groonga 13.0.5-49-g446b7f4 [Linux,x86_64,utf8,match-escalation-threshold=0,nfkc,onigmo,zstandard,epoll,rapidjson]

13.0.5のあとに何かがついて手元でビルドしているものを使っている感がありますね。

まとめ

すべてが含まれたバイナリーの場合は絶対パスでコマンドを指定したりPATHを設定してコマンドを指定すればビルドしたバイナリーを使うことができますが、共有ライブラリーもビルドしている場合はそれだけでは足りません。共有ライブラリーを探すパス(LinuxならLD_LIBRARY_PATH/macOSならDYLD_LIBRARY_PATH)を指定しなければ手元でビルドした共有ライブラリーを使ってもらえません。

手元での変更が反映されない場合は、本当に手元でビルドした共有ライブラリーが使われているかを確認してみてください。その場合はldd/otool -Lを使えます。

おまけ

rpath(run path)を指定すると実行時にLD_LIBRARY_PATHを指定しなくてもビルドした共有ライブラリーを使うことができます。CMakeならCMAKE_INSTALL_RPATHを指定します。

cmake -S groonga -B groonga.build -G Ninja -DCMAKE_INSTALL_PREFIX=/tmp/local -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_RPATH=/tmp/local/lib
ninja -C groonga.build install

rpathを指定するとデフォルトで指定したパスから検索してくれます。

$ ldd /tmp/local/bin/groonga | grep libgroonga
	libgroonga.so.0 => /tmp/local/lib/libgroonga.so.0 (0x00007f8cdbdcb000)

LD_LIBRARY_PATHで別の場所にある共有ライブラリーを検索することもできます。

$ LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/ ldd /tmp/local/bin/groonga | grep libgroonga
	libgroonga.so.0 => /usr/lib/x86_64-linux-gnu/libgroonga.so.0 (0x00007fe131eca000)

rpathが設定されているかどうかは、たとえばobjdump -pで確認できます。

$ objdump -p /tmp/local/bin/groonga | grep RUNPATH
  RUNPATH              /tmp/local/lib

ここではrpathに絶対パスを指定しましたが、$ORIGIN/../libなど対象のファイルからの相対パスを指定することもできます。デフォルトの共有ライブラリーの検索パスを変更したり、LD_LIBRARY_PATHを指定したりできない場合はrpathで解決できるかもしれません。