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/bin
にPATH
は通っていないので手元でビルドした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で解決できるかもしれません。