同名のシンボルを持つ複数のライブラリーをリンクしたときの名前解決
GOT やリンカー・ローダーの仕組みを考えれば、そうなるかな、と納得はできるんだけれど、たいそうショックを受けたことがら。
次のような三つのソースを用意する。
/* a.c */ int puts(const char *str); void foo() { puts("hello"); } void bar() { foo(); } /* A.c */ int puts(const char *str); void foo() { puts("HELLO"); } /* main.c */ void bar(); int main() { bar(); return 0; }
そして a.c と A.c から共有ライブラリーを作成する。
$ gcc -fpic -shared -o liba.so a.c $ gcc -fpic -shared -o libA.so A.c
main.c をコンパイルして A と a にリンクして実行してみる。
$ gcc -o A_a main.c -L. -lA -la -Wl,-rpath=. $ ./A_a HELLO
main で呼び出す bar を定義したのは a.c。 bar は foo を呼び出していて、同じソース内に foo を定義してあるのに、呼び出されたのは A.c の foo になっている。
ライブラリーのリンク順序を a、それから A の順にすると a.c の foo が呼ばれる。
$ gcc -o a_A main.c -L. -la -lA -Wl,-rpath=. $ ./a_A hello
そういうものだったんだ…。 ライブラリーとしてリンクしたときに、できる限りの名前解決はされるものだとおもいこんでいた。 外部にエクスポートされるシンボルの場合、ライブラリー内の定義の有無にかかわらず GOT 経由での呼び出しになるのね。
念のため -fpic -O3 -fomit-frame-pointer での a.c アセンブルリスト
アセンブルリストを出力してみる。
$ gcc -fpic -O3 -fomit-frame-pointer -S a.c
得られた結果の a.s を眺めてみると、 bar の中の foo 呼び出しは jmp foo@PLT、つまり PLT (Procedure Linkage Table) 経由でのジャンプになっている。 同じコンパイル単位に関数が定義されていても、それは無視するんだな…。
.file "a.c" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "hello" .text .p2align 4,,15 .globl foo .type foo, @function foo: .LFB0: .cfi_startproc leaq .LC0(%rip), %rdi jmp puts@PLT .cfi_endproc .LFE0: .size foo, .-foo .p2align 4,,15 .globl bar .type bar, @function bar: .LFB1: .cfi_startproc xorl %eax, %eax jmp foo@PLT .cfi_endproc .LFE1: .size bar, .-bar .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" .section .note.GNU-stack,"",@progbits