Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

macOSにて同一のシンボル名のダイナミックライブラリをロードしたらどうなるか

macOSにて同一のシンボル名を持つダイナミックライブラリをロードしたらどうなるかを検証します。
前提条件として、ダイナミックライブラリはインストールパスに@rpathが指定されていることとします。
絶対パスが指定されている場合は、全く異なる動作をしますが、今回は扱いません。

構成

GreatApplication.app/Contents/MacOS/GreatApplication
GreatApplication.app/Contents/Frameworks/blockA/libblock.dylib
GreatApplication.app/Contents/Frameworks/blockB/libblock.dylib

2つのlibblock.dylibが@rpath指定されているダイナミックライブラリです。
2つとも、-fvisibility=hidden, -fvisibility-inlines-hiddenが指定されてビルドされているとします。

ハンドル=dlopen(ダイナミックライブラリの絶対パス, RTLD_LAZY|RTLD_GLOBAL)
dlsym(ハンドル, "block")
を用います。

結果その1
ハンドルはA, Bで異なる値となります。
block関数は
__attribute__ ((visibility("default")))
指定をしておき、dysymでアドレス取得できるようにしておきます。
block関数のアドレスはA, Bで異なる値となります。
block関数から呼び出した同じ名前のC++クラス名の呼び出しはA, Bそれぞれのモジュール内のものが呼び出されます。

名前空間なしで定義したグローバル変数
int64_t g_variable;
はシンボル自体はローカルですが、A, Bそれぞれのモジュール内の異なるアドレスを指しています。

ちなみに、macOS環境ではRTLD_GLOBALがデフォルトで、linux環境ではRTLD_LOCALがデフォルトです。

ちょっと説明を読んでみましょう。

RTLD_GLOBAL Symbols exported from this image (dynamic library or bundle) will be available to any images build with -flat_names-
pace option to ld(1) or to calls to dlsym() when using a special handle.

RTLD_LOCAL Symbols exported from this image (dynamic library or bundle) are generally hidden and only availble to dlsym() when
directly using the handle returned by this call to dlopen().

RTLD_GLOBAL このダイナミックライブラリまたはバンドルイメージからエクスポートされたシンボルは(グローバル、ローカル共に)、-flat_names-paceオプションを与えられたld(1)でビルドされた全てのイメージまたは指定されたハンドルで使う時にdlsym()を呼び出すことにより利用できる。

RTLD_LOCAL このダイナミックライブラリまたはバンドルイメージからエクスポートされたシンボル(グローバル、ローカル共に)は一般的に隠され、dlopen()を呼び出して返ったハンドルを直接使用する場合のdysym()によってのみ利用できる。

なんだか良くわかりませんね。

試しにこの状態のまま-fvisibility-hidden, -fvisibility-inlines-hiddenを外してビルドしたらどうなるかを試してみましたが、
全く変化なしです。

結局分かったことは、
インストールパスが@rpathのダイナミックライブラリをdlopenした時は、RTLD_GLOBAL, RTLD_LOCALどちらを指定した時も、
同じシンボル名であっても関数のアドレスやグローバル変数のアドレスがコンフリクトすることはなく、それぞれのものが利用される。
ということです。
シンボル名のコンフリクトに関しては@rpath指定されていれば、オールOKという感じですね。

ちなみに、-fvisibility-hidden, -fvisibility-inlines-hiddenを指定しなかった場合は、
__attribute__ ((visibility("default")))を指定しなかった関数もdlsym()でアドレスを取得できるようになります。
意図せず呼び出されるリスクを減らしたいという場合は指定しておいた方が良さそうです。