Advent Calendar 2020 全部オレシリーズ 1日目です。完走できるか心配です。
先日、Homebrew で grpc_cli (grpc) をインストールしたんですけれども、実行すると次のように共有ライブラリが見つからないというエラーになりました。
$ grpc_cli dyld: Library not loaded: @rpath/libgrpc++.1.dylib Referenced from: /Users/teraoka/.homebrew/bin/grpc_cli Reason: image not found zsh: abort grpc_cli
libgrpc++.1.dylib
は /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/lib/libgrpc++.1.dylib
にあって、/Users/teraoka/.homebrew/lib/libgrpc++.1.dylib
への symbolic link もありました。
Mac を使い始めて日が浅いし、Homebrew まかせで自分でコンパイルすることもないので良く分かりませんが、Linux での rpath (LD_RUN_PATH) には覚えがありますね。
Mac の場合は DYLD_LIBRARY_PATH が使えるようです。次のようにすれば実行できました。
DYLD_LIBRARY_PATH=/Users/teraoka/.homebrew/lib grpc_cli
しかし、この環境変数を .zshrc に書くことの影響も読めないので Linux での compile (link) 時の -rpath
指定のようにバイナリに埋め込む path を指定したいものです。
Mac (LLVM) には otool というものがあり、これでバイナリ内の rpath を確認できるっぽいです。
$ otool -l /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli | grep -B 1 -A 2 LC_RPATH Load command 30 cmd LC_RPATH cmdsize 64 path /tmp/grpc-20201123-11869-1aso5q3/cmake/build (offset 12)
rpath はビルド時のテンポラリディレクトリになってるっぽい?
ちなみに、otool -L
で Linux の ldd コマンドみたいな結果が得られました。
$ otool -L /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli: /Users/teraoka/.homebrew/opt/gflags/lib/libgflags.2.2.dylib (compatibility version 2.2.0, current version 2.2.2) /Users/teraoka/.homebrew/opt/protobuf/lib/libprotobuf.25.dylib (compatibility version 26.0.0, current version 26.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) @rpath/libgrpc++.1.dylib (compatibility version 1.0.0, current version 1.33.2) @rpath/libgrpc++_test_config.1.dylib (compatibility version 1.0.0, current version 1.33.2) @rpath/libgrpc.13.dylib (compatibility version 13.0.0, current version 13.0.0) @rpath/libgpr.13.dylib (compatibility version 13.0.0, current version 13.0.0) @rpath/libaddress_sorting.13.dylib (compatibility version 13.0.0, current version 13.0.0) @rpath/libupb.13.dylib (compatibility version 13.0.0, current version 13.0.0) /Users/teraoka/.homebrew/opt/openssl@1.1/lib/libssl.1.1.dylib (compatibility version 1.1.0, current version 1.1.0) /Users/teraoka/.homebrew/opt/openssl@1.1/lib/libcrypto.1.1.dylib (compatibility version 1.1.0, current version 1.1.0) /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11) /Users/teraoka/.homebrew/opt/c-ares/lib/libcares.2.dylib (compatibility version 2.0.0, current version 2.4.2) /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0) /Users/teraoka/.homebrew/opt/re2/lib/libre2.dylib (compatibility version 0.0.0, current version 0.0.0) /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1677.104.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0)
Homebrew で rpath を指定する方法はまだ分からないけど、install_name_tool というコマンドでバイナリ内の rpath (LC_RPATH) を変更できることが分かりました。-add_rpath
で追加するか -rpath
で変更が可能だと分かりました。
install_name_tool はバイナリを書き換えるため、write 権限をつけます。
$ chmod u+w /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli
-rpath
で書き換えます。
$ install_name_tool \ -rpath /tmp/grpc-20201123-11869-1aso5q3/cmake/build \ /Users/teraoka/.homebrew/lib \ /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli
書き換えられました。
$ otool -l /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli | grep -B 1 -A 2 LC_RPATH Load command 30 cmd LC_RPATH cmdsize 48 path /Users/teraoka/.homebrew/lib (offset 12)
-add_rpath
で追加した場合は次のようになります。
$ otool -l /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli | grep -B 1 -A 2 LC_RPATH Load command 30 cmd LC_RPATH cmdsize 64 path /tmp/grpc-20201123-11869-1aso5q3/cmake/build (offset 12) -- -- Load command 33 cmd LC_RPATH cmdsize 48 path /Users/teraoka/.homebrew/lib (offset 12)
どちらの場合も dyld: Library not loaded
エラーを回避できます。インストールの度にやるのは面倒ですね、Homebrew の設定とかでなんとかならないものか。
と、調べていたら気付きました
==> Downloading https://homebrew.bintray.com/bottles/grpc-1.33.2_1.catalina.bottle.tar.gz
あれ? bottles ってお前、バイナリ配布だったのか…
ということで、またその後知った brew edit
というコマンドでで grpc_cli を build する args のところに -DCMAKE_BUILD_RPATH=/Users/teraoka/.homebrew/lib
を追加して
$ brew edit grpc $ brew reinstall --build-from-source grpc
とすることで LC_RPATH が追加されました。
$ otool -l /Users/teraoka/.homebrew/Cellar/grpc/1.33.2_1/bin/grpc_cli | grep -B 1 -A 2 LC_RPATH Load command 30 cmd LC_RPATH cmdsize 48 path /Users/teraoka/.homebrew/lib (offset 12) -- -- Load command 31 cmd LC_RPATH cmdsize 64 path /tmp/grpc-20201130-46642-1oq94ry/cmake/build (offset 12)
めでたし、めでたし。
が、コンパイルに時間かかるし、結局 edit しなきゃいけないしうーむ…
付録
otool –version
$ otool --version llvm-otool(1): Apple Inc. version cctools-949.0.1 Apple LLVM version 11.0.0 (clang-1100.0.33.17) Optimized build. Default target: x86_64-apple-darwin19.6.0 Host CPU: skylake Registered Targets: aarch64 - AArch64 (little endian) aarch64_be - AArch64 (big endian) arm - ARM arm64 - ARM64 (little endian) armeb - ARM (big endian) thumb - Thumb thumbeb - Thumb (big endian) x86 - 32-bit X86: Pentium-Pro and above x86-64 - 64-bit X86: EM64T and AMD64
otool usage
$ otool Usage: /Library/Developer/CommandLineTools/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ... -f print the fat headers -a print the archive header -h print the mach header -l print the load commands -L print shared libraries used -D print shared library id name -t print the text section (disassemble with -v) -x print all text sections (disassemble with -v) -pstart dissassemble from routine name -s print contents of section -d print the data section -o print the Objective-C segment -r print the relocation entries -S print the table of contents of a library (obsolete) -T print the table of contents of a dynamic shared library (obsolete) -M print the module table of a dynamic shared library (obsolete) -R print the reference table of a dynamic shared library (obsolete) -I print the indirect symbol table -H print the two-level hints table (obsolete) -G print the data in code table -v print verbosely (symbolically) when possible -V print disassembled operands symbolically -c print argument strings of a core file -X print no leading addresses or headers -m don't use archive(member) syntax -B force Thumb disassembly (ARM objects only) -q use llvm's disassembler (the default) -Q use otool(1)'s disassembler -mcpu=arg use `arg' as the cpu for disassembly -j print opcode bytes -P print the info plist section as strings -C print linker optimization hints --version print the version of /Library/Developer/CommandLineTools/usr/bin/otool
install_name_tool(1)
INSTALL_NAME_TOOL(1) INSTALL_NAME_TOOL(1) NAME install_name_tool - change dynamic shared library install names SYNOPSIS install_name_tool [-change old new ] ... [-rpath old new ] ... [-add_rpath new ] ... [-delete_rpath new ] ... [-id name] file DESCRIPTION Install_name_tool changes the dynamic shared library install names and or adds, changes or deletes the rpaths recorded in a Mach-O binary. For this tool to work when the install names or rpaths are larger the binary should be built with the ld(1) -headerpad_max_install_names option. -change old new Changes the dependent shared library install name old to new in the specified Mach-O binary. More than one of these options can be specified. If the Mach-O binary does not contain the old install name in a specified -change option the option is ignored. -id name Changes the shared library identification name of a dynamic shared library to name. If the Mach-O binary is not a dynamic shared library and the -id option is specified it is ignored. -rpath old new Changes the rpath path name old to new in the specified Mach-O binary. More than one of these options can be specified. If the Mach-O binary does not contain the old rpath path name in a specified -rpath it is an error. -add_rpath new Adds the rpath path name new in the specified Mach-O binary. More than one of these options can be specified. If the Mach-O binary already contains the new rpath path name specified in -add_rpath it is an error. -delete_rpath old deletes the rpath path name old in the specified Mach-O binary. More than one of these options can be specified. If the Mach-O binary does not contains the old rpath path name specified in -delete_rpath it is an error. SEE ALSO ld(1) Apple, Inc. March 4, 2009 INSTALL_NAME_TOOL(1)