SSブログ

ライブラリのpath指定がやっぱりよくわからん [aravis]

困ったことが起こった。自分用のframeworkを作って、自分で作ったアプリケーションバンドルに内包させた。そのアプリはビルドしたマシンでしか動かなかった。調べてみるとframeworkの中にさらにgnu由来のライブラリを埋め込んだんだけど、そのライブラリがさらに依存しているライブラリが見つからないらしい....

ややこしいので、具体的に書くと、
0710directories.png
のようにA.appというアプリバンドルの中にFrameworksというディレクトリがあって、そこに自作のF.frameworkを内包している。F.frameworkはそいつが使うためのL1.dylibというライブラリをLibrariesというディレクトリに持っている。L1.dylibはL2.dylibという別のライブラリを参照しているので、同じLibrariesディレクトリにコピーした。

このL2.dylibというライブラリの参照が実行時に解決されないといけない。ビルドマシンではこれがLibrariesディレクトリの中のL2.dylibではなくて、もとのL2.dylibがあった位置を参照していた。otoolというコマンドで
$ otool -L L1.dylib
L1.dylib:
        /usr/local/lib/L2.dylib (compatilibity version 1.0, current version 1.4)
とかとなっているので、これをリンクしようとするんだろう。別のマシンではこのディレクトリにL2.dylibがないので、実行時にライブラリがみつからない、ということになったらしい。この、otoolで出力されるpathはライブラリそのもの(この場合L1.dylib本体)に埋め込まれたもので、install_name_toolというコマンドで書き換えることができるようである。

しかし、いろいろなライブラリを同じように埋め込んで、依存関係を手動で切り替えるのは、ライブラリが増えてくると非常に煩わしい。そもそもライブラリは検索の優先順位があって本体に埋め込まれた絶対pathにないと他のところを探すはずだった。macOSではまた違っているようである。

stackOverflowやその他のサイトでは断片的な情報(わりとみんなworkaroundが得られればそれで良しとしてる)しか手に入らなかった。めんどくさいけどしょうがないのでreferenceを探す。Xcodeのそれらしいヘルプトピックがあったのでクリックするとアーカイブの方へ飛ばされる。リバイズされていないらしい。これしかないなら諦めて見てみるとThe Library Search Processに、ダイナミックローダがライブラリの場所を探すとき
  1. LD_LIBRARY_PATH
  2. DYLD_LIBRARY_PATH
  3. The process’s working directory
  4. DYLD_FALLBACK_LIBRARY_PATH
を、上から順に参照するとある。working directory以外は環境変数で、一番下のDYLD_FALLBACK_LIBRARY_PATHにだけ、デフォルト値
$HOME/lib;/usr/local/lib;/usr/lib
が与えられている(fallbackというぐらいだからな)。ただしこの環境変数は「実行時」に設定されている必要がある。

最初、この環境変数のどれかにFrameworkのdylibがあるパスを設定すればいいのかな、と思って、XcodeのBuild Settingsのタブの中を探していた。
$ xcodebuild -showBuildSettings > settings.txt
$ grep DYLD_LIBRARY_PATH settings.txt 
$ grep DYLD_FALLBACK_LIBRARY_PATH settings.txt 
などとしてみても出てこない。そりゃそうだ、ビルド時に環境変数を設定しても意味がなかった。

Build Settingsでそれらしい変数を探すとLD_RUNPATH_SEARCH_PATHSというのがある。このquick helpに
This is a list of paths to be added to the `runpath` search path list for the image being created. At runtime, `dyld` uses the `runpath` when searching for dylibs whose load path begins with `@rpath/`. See [Dynamic Library Programming Topics]
とあって、[Dynamic Library Programming Topics]のアドレスをみるとやっぱり同じアーカイブに飛ばされた。ひでぇ。

このquick helpにある「dyld」というのがダイナミックローダらしい(つい「dyld」を「ディルド」と発音してしまう。アメリカ人でダイナミックローダを知ってる人のうち上品な人ならきっと「ダイエルディ」とか発音してるだろうな)。しょうがないのでdyldのman pageを見ることにする。man pageにはreferenceにあった3つのうちDYLD_LIBRARY_PATHとDYLD_FALLBACK_LIBRARY_PATHの環境変数があって、ちゃんと読まれるようである。そこにはまずDYLD_LIBRARY_PATHを探して、みつからなければDYLD_FALLBACK_LIBRARY_PATHを探すとある。一応referenceの記述と一致している。

man pageの下の方に「DYNAMIC LIBRARY LOADING」という節があって
Unlike many other operating  systems,  Darwin  does  not  locate dependent  dynamic  libraries via their leaf file name.  Instead the full path to  each  dylib  is  used  (e.g.  /usr/lib/libSystem.B.dylib).   But  there  are  times  when  a full path is not appropriate; for instance, may want your binaries to be  instal- lable in anywhere on the disk.  To support that, there are three @xxx/ variables that can be used as a path prefix.   At  runtime dyld substitutes a dynamically generated path for the @xxx/ prefix.
とある。「 leaf file name」というのはxxx.dylibの「xxx」のことだろうか。ようするに名前だけではライブラリを探す気は無い、と言っているらしい。それでは上の環境変数との関係はどうなってるんだ。とにかく絶対pathで指定しないといかん、しかし絶対pathがわからんときがある、そのために@xxxがある、と言って
  • @executable_path
  • @loader_path
  • @rpath
のみっつがあると言っている。この「@」で始まる変数はビルド時に解決される(文字列が置き換えられて絶対pathが確定する)のではなく、実行時にそのままdyldに渡されて、dyldが解決するらしい。

このうち@executable_pathはわかりやすい。アプリケーションバンドルの中の実行ファイル、例えば普通にインストールされたアプリなら
/Applications/xxx.app/Contents/MacOS/xxx
へのpath
/Applications/xxx.app/Contents/MacOS
と置き換えられる。ここを起点にバンドルディレクトリの中を辿ればいい。例えば普通の埋め込みframeworkでは
/Applications/xxx.app/Contents/Frameworks/
の下に置かれるので、たとえばyyy.frameworkのトップレベルは
@executable_path/../Frameworks/yyy.framework/
としておいて、dyldに@executable_pathを展開してもらえばいい。

@loader_pathはもう少し難しい。man pageにはプラグインの場合が書いてある。プラグインの場合、それを使うアプリとは別の場所にインストールされることがある。その場合プラグインのバンドルにライブラリがあってリンクしたくても@executable_pathから辿ることはできない。@loader_pathはプラグインがロードされるときに解決される。プラグインもframeworkと同じバンドル構造になっていて、たとえばMyFilterプラグインの実行ファイル本体は
/some/path/Myfilter.plugin/Contents/MacOS/Myfilter
にあって、@loader_pathはこの場合
/some/path/Myfilter.plugin/Contents/MacOS
と置き換えられるようになっているので、@executable_apthと同じように、
@loader_path/../Frameworks/yyy.framework
などとすればいい、と書いてある。

ローダブルバンドルの場合もdyldがロードするのかどうかよくわからないけど、少なくとも同じメカニズムでロードされるようである。

最後の@rpathはわかりにくい。man pageの記述によると@executable_pathや@loader_pathは単一の置き換え(同じ実行ファイルに対してはpathはひとつだけ)しかしないけど、@rpathは「run path list」というスタックになっていて複数設定できる。スタックへはリンカ(ld)へのオプションとして「-rpath/xxx」と指定する。従ってコンパイル時点では「-Wl,-rpath/xxx」みたいにすればいい。複数個設定するには環境変数のようなコロン「:」で区切るのではなくて、rpathオプションを複数つけないといけないらしい。

それはまあわかった。しかし@rpathはデフォルトで何になってるんだ?このman pageには何の記述もない。先のアーカイブにも記述があるけどなんだかよくわからない。

XcodeのBuild settingsに「Runpath Search Path」という項目があって、これには複数個設定できるようになっている。最初の方に書いたLD_RUNPATH_SEARCH_PATHSの設定である。ようするにここにsearch pathを書くと@rpathにプッシュされるらしい。

でもここに
@loader_path/Frameworks
などと書いてビルドして、アプリケーションにembedして(ちゃんとビルドできた)別のマシンで動かすと、@loader_path/Frameworksのpathを探してくれなくて、dyldのエラーが
dyld: Library not loaded: /usr/local/Cellar/glib/2.60.5/lib/libgthread-2.0.0.dylib
となる。これは実行ファイルのバイナリに埋まっているpathである(homebrewでインストールした)。つまり優先順位の高いpathをまず探すんだけど、そこになかったらおしまいになってる。ダメじゃん。referenceもman pageもウソじゃん。LD_RUNPATH_SEARCH_PATHSの設定が悪いのか、それともdyldに渡っていないのか、全然わからない。

webを見てるとたとえばこの人のようにinstall_name_toolでバイナリを書き換えるスクリプトをbuild phaseに追加しているひとに行き当たる。なんだかすごく難しいことをしているような気がする。でもそうしてるひとはひとりやふたりではない。それにApple製品以外のアプリケーションで、アプリバンドルの中にdylibがembedされているものでは、そのdylibをotoolでみてもだいたいそうなっている(embedされたdylib本体に@loader_pathなんかが埋まっている)。

さらには、DYLD_LIBRARY_PATHなんかの環境変数がmacOSのバージョンによっては設定できないようになってる、と言っているサイトも見られる。やっぱりinstall_name_toolでpathを埋め込まないとないといけないんだろうか。もしそうならなんでXcodeがそういう面倒を見ないのか。XcodeにとってはBuild Phaseの設定でなにを埋め込みたいのかわかってるはずだし、install_nameの変更が必ず必要ならXcodeがやるべきではないのか?

もうわけわからん。困った。
nice!(0)  コメント(2) 

nice! 0

コメント 2

Kiwamu

とても、参考になりました。Big Surのbeta 4あたりから、linkするLibraryのcodesignが必要になり、そのあたり、installのパッケージで今まで指定したLIBRARY_SEARCH_PATHからlibをロードするのではなく、otoolで出てくるパスを参照しているようなので、otoolの事知らなかったので助かりました。
by Kiwamu (2020-09-02 12:05) 

koichi

もともとあまり理解しないまま使っていたのですが、自分のプログラムが本当に動かなくなっていろいろ調べました。で、このサイトにも来たわけですが、やはりちゃんとした情報はほとんどないようです。
今回やっと理解したのは、@rpathはdylib側に埋め込む、ということです。Xcodeであれば、ライブラリのビルド時にBuild Settingsの中の Deployment / Installation Directory に@rpath(という文字列)をセットします。すでにあるライブラリをmodifyするならinstall_name_toolを使うのだと思います。
App等、ライブラリを使う側のビルド時に Linking / Runpath Search Paths に@loader_pathその他のリストをセットするとリンク時にこのリストが -rpath オプションでAppのbinaryに組み込まれてdyldによって展開される、ということになります。つまり、@rpathは自動的に何かに展開されるわけではなく、自分でリストをセットするとリンクされたdylibに渡されるというメカニズムらしいです。一応自分の問題はこれで解決しました。

by koichi (2023-07-17 18:31) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。