The Man Who Fell From The Wrong Side Of The Sky:2007年1月20日分

[最新版] [一覧] [前月] [今月] [翌月]

2007/1/20(Sat)

リンカとリンクローダは共有ライブラリ間のグローバルシンボルの衝突をどのように扱うか?

前回はGNU binutilsを使ってオブジェクトのグローバルとローカルのシンボルの違いを学びました。
今回はグローバルシンボルの衝突について解説します。

ではさっそくサンプルコードです。

conflict1.c
--
#include <stdio.h>
void bar(void) {
	puts("i'm conflict1::bar!");
}
void buz(void) {
	puts("i'm conflict1::buz!");
}
--

$ gcc -shared -Wl,-soname,libconflict1.so.0 -o libconflict1.so.0 conflict1.c
$ ln -s libconflict1.so.0 libconflict1.so

$ nm libconflict1.so.0 | grep b[au][rz]
0000051c T bar
00000534 T buz
conflict2.c
--
#include <stdio.h>
void bar(void) {
	puts("i'm conflict2::bar!");
}
void buz(void) {
	puts("i'm conflict2::buz!");
}
--

$ gcc -shared -Wl,-soname,libconflict2.so.0 -o libconflict2.so.0 conflict2.c
$ ln -s libconflict2.so.0 libconflict2.so

$ nm libconflict2.so.0 | grep b[au][rz]
0000051c T bar
00000534 T buz

このように同名のグローバルシンボルを持つ共有ライブラリを
同時にリンクしようとすると、リンカやリンクローダはどう処理するのでしょうか?

test_conflict.c
--
extern void bar(void);
extern void buz(void);
int main(void)
{
	bar();
	buz();
	return 0;
}
--
$ gcc -Wl,-rpath=. -L. -lconflict1 -lconflict2 -o test_conflict test_conflict.c
$ nm test_conflict | grep b[au][rz]
	U bar
	U buz

特に何のエラーも出ずにコンパイルとリンクが成功してしまいました。

ちなみに重複シンボルは静的リンク時には検出されエラーになります。

$ gcc -o test_conflict conflict1.c conflict2.c test_conflict.c
/var/tmp//ccKmpTdI.o: In function `bar':
conflict2.c:(.text+0x0): multiple definition of `bar'
/var/tmp//ccERyvW3.o:conflict1.c:(.text+0x0): first defined here
/var/tmp//ccKmpTdI.o: In function `buz':
conflict2.c:(.text+0x18): multiple definition of `buz'
/var/tmp//ccERyvW3.o:conflict1.c:(.text+0x18): first defined here

$ gcc -shared -Wl,-soname,libconflict.so.0 -o libconflict.so.0 conflict1.c conflict2.c
/var/tmp//ccdvsMSW.o: In function `bar':
conflict2.c:(.text+0x0): multiple definition of `bar'
/var/tmp//ccV6VDmW.o:conflict1.c:(.text+0x0): first defined here
/var/tmp//ccdvsMSW.o: In function `bar':
conflict2.c:(.text+0x18): multiple definition of `buz'
/var/tmp//ccV6VDmW.o:conflict1.c:(.text+0x18): first defined here

ldd(1)の結果をみてみましょう。

$ ldd test_conflict
test_conflict:
	-lconflict1.0 => ./libconflict1.so.0
	-lconflict2.0 => ./libconflict2.so.0
	-lc.12 => /usr/lib/libc.so.12

重複するシンボルを持つlibconfict1とlibconflict2どちらもリンクされています。

では実際に実行してみましょう。

$ ./test_conflict
i'm conflict1::bar!
i'm conflict1::buz!

先にリンクしたlibconflict1.so.0のbarとbuzが呼ばれています。

リンクする順序を変えてみましょう。

$ gcc -Wl,-rpath=. -L. -lconflict2 -lconflict1 -o test_conflict test_conflict.c
$ ldd test_conflict
test_conflict:
	-lconflict2.0 => ./libconflict2.so.0
	-lconflict1.0 => ./libconflict1.so.0
	-lc.12 => /usr/lib/libc.so.12
$ ./test_conflict
i'm conflict2::bar!
i'm conflict2::buz!

今度はlibconflict2.so.0が呼ばれました。
つまりリンクローダはシンボルが重複する場合、ldd(1)で上位にくる共有ライブラリ、
すなわち先にリンクされている側のシンボルを優先するということです。

ちなみにリンクローダは環境変数LD_PRELOADによって、強制的にリンクの順番や
リンクしてない共有ライブラリを先に読み込ませることが可能です。
ただしセキュリティの関係上、setuid/setgidされた実行ファイルには有効ではありません。

リンク順によって優先順位を制御できることは判りました。しかしこれで十分でしょうか?
上のサンプルで、barはlibconflict1.so.0を、buzはlibconflict2.so.0を使いたい場合
リンク順序では解決できない問題だということは明白です。

そこで導入されたものがウィークシンボル(weak symbol)というものです。
次回はウィークシンボルについて解説します。

(追記)
Cygwinの場合まったく異なる動作をすることに注意してください。
Cygwin(というかWindowsのDLL)では後からリンクした共有ライブラリのシンボルが
優先される上に、重複の結果ひとつもシンボルが参照されないDLLはリンクされません。
今回の例では-lconflict1 -lconflict2でコンパイルすると

$ objdump -x test_conflict.exe | grep "DLL Name"
        DLL Name: cygwin1.dll
        DLL Name: KERNEL32.dll
        DLL Name: cygconflict2.dll

のように、cygconflict2.dllのみへのリンクになります。


[ホームへ] [ページトップへ]