The Man Who Fell From The Wrong Side Of The Sky:2008年10月2日分

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

2008/10/2(Thu)

Linux/glibc2のld.so(1)はシンボルのウィーク属性(STB_WEAK)を無視する

以前書いた「 ウィークシンボルって何ですか?」のエントリについて
メールを頂きましたので、そのお返事おば。

メールから引用。

さて、サンプルを試していて気づいたのですが、weak symbol についての下の部分で、
Fedora8 (glibc-2.7)ではこの通りにはなりませんでした。

>$ ln -sf libconflict1.so.0 libconflict1.so
>$ gcc -Wl,-rpath=. -L . -lconflict1 -lconflict2 -o test_conflict test_confict.c
>$ nm test_conflict | grep b[au][rz]
>        U bar
>        U buz
>$ ./test_conflict
>i'm conflict1::bar!
>i'm conflict2::buz!

その代わり、LD_DYNAMIC_WEAK をセットして実行するとうまく行きました。

$ /lib/libc.so.6
GNU C Library stable release version 2.7, by Roland McGrath et al.
...
$ LD_DEBUG=symbols ./test_conflict
...
    4808:     symbol=buz;  lookup in file=./test_conflict [0]
    4808:     symbol=buz;  lookup in file=./libconflict1.so.0 [0]
	<= ここ
i'm conflict1::buz!                      <= ここ
...
$ LD_DYNAMIC_WEAK=1 ./test_conflict
i'm conflict1::bar!
i'm conflict2::buz!

info ld.so の LD_DYNAMIC_WAEK の説明には、glibc の動作を先祖返りさせるという記述があるので、
どこかの時点で、デフォルトの動作が変わったのでしょうか。

    LD_DYNAMIC_WEAK
        (glibc  since  2.1.91)  Allow  weak  symbols  to  be overridden
        (reverting to old glibc behavior).

ごめんなさい、*BSD以外の話がずっぱり抜け落ちていましたね(滝汗
時間のある時にサンプルは静的リンクを使ったものに修正しておきますです。

Linux/glibc2が動的リンク時のweak属性(STB_WEAK)の挙動を変更した理由は
このメールでUlrich Drepper氏が至極あっさりと説明しています。
以下超訳。

ELF実行時の解釈を"公的見解[*]"に沿うようにするパッチをcommitしました。
これは何かというと、動的リンク時にウィークシンボルの定義を通常の定義と同様に扱うということです。
つまり、weak属性は静的リンク時のみ(それとweak参照でのみ)使われます。

(以下省略)

[*]公的見解 == Solaris、SCOそれにその他のSysVr4由来のUnixはこのやり方で
ELFバイナリを解釈します、ただIrixだけはglibcの以前の挙動と同じですが。

cvswebの変更履歴を追ってみると このcommitの事ですね。

この変更による被害届 その1その2。 とくに前者は仕事だから胃が痛いだろうな、私も似たような経験あるけど。

まぁ Solarisのドキュメントなんかだと「システム以外のアプリでpragma weak使うな」とあるので
自業自得ではありますが、いっそ自前のld.soとlibc、そしてkernelを用意(以下略
まぁふつーdlopen(3)使えってことで *1

しかしDrepper氏の言うように、本当にこの動作が「公式見解」なんでしょうか?
ELF仕様書から一部抜粋してみましょう。

Global and weak symbols differ in two major ways.
  * When the link editor combines several relocatable object files,
    it does not allow multiple definitions of STB_GLOBAL symbols with
    the same name. On the other hand, if a defined global symbol exists,
    the appearance of a weak symbol with the same name will not cause
    an error. The link editor honors the global definition and ignores
    the weak ones. Similarly, if a common symbol exists
    (i.e., a symbol whose st_shndx field holds SHN_COMMON), the appearance
    of a weak symbol with the same name will not cause an error. The link
    editor honors the common definition and ignores the weak ones.
  * When the link editor searches archive libraries, it extracts archive
    members that contain definitions of undefined global symbols.
    The member's definition may be either a global or a weak symbol.
    The link editor does not extract archive members to resolve undefined
    weak symbols. Unresolved weak symbols have a zero value.

以下超訳、ほんとはLinkers & Loadersとかと用語を統一すりゃいいんだけど
いまどこに置いてあるか判りませぬ(CJKV針千本みたいに捨てられてないといいなぁ、しくしく)。

大域およびウィークシンボルの差は大きく二つです。
・リンクエディタは複数の再配置可能なオブジェクトファイルを結合する時
  同名のSTB_GLOBALフラグが立ったシンボルは、これを重複定義として扱い
  許可しません。一方でウィークシンボルと同名の大域シンボルが出現する場合
  これはエラーとはしません。リンクエディタは大域シンボルの定義を尊重し
  ウィークシンボルは無視されます。同じように通常の定義(例えば
  ELF構造体のst_shndxフィールドにSHN_COMMONフラグの立ったシンボル)
  も以下同文。
・リンクエディタがアーカイブライブラリを検索するとき、アーカイブ中より
  未定義の大域シンボルを含むオブジェクトファイルを抽出します。
  抽出されたオブジェクトの定義は大域シンボルかウィークシンボル
  どちらの場合の可能性もあります。リンクエディタは未解決のウィークシンボル
  の為にアーカイブ中のオブジェクトを抽出しようとはしません。
  未解決のウィークシンボルはアドレスを0番地とします。

注意深く読んでいただくと、ウィークシンボルの挙動が定義されてるのは
実のところコンパイル時に静的リンクに使われるld(1)といったlink editorや
lib*.aのようなアーカイブライブラリについてのみだということに
気付くかと思います、そして動的リンクの場合については一切触れられていません。

つまりアプリ実行時に動的リンクの為に使われるld.so(1)のような
runtime link editorやlib*.soのような共有ライブラリについては
仕様を馬鹿馬鹿しいくらいに厳密に解釈するとウィークシンボルの挙動は
「未定義」ということです。

そして先のメールにもある通り、SysV系の多くの実装は
動的リンクの場合はSTB_WEAKフラグは無視するのでDrepper氏はこれが「公式見解」だと。
# まぁ元々ELF自体がSysV由来ですしねぇ。

手元にあるSolaris 8 + SUNWspro WS6U2での挙動。

$ cat >conflict1.c
#include <stdio.h>
#pragma weak foo = _foo
void
_foo(void)
{
	puts("i'm conflict1");
}
^D
$ cc -G -h libconflict1.so.0 -o libconflict1.so.0 conflict1.c
$ ln -sf libconflict1.so.0 libconflict1.so
$ nm libconflict1.so.0 | grep foo
[56]    |      1080|        36|FUNC |GLOB |0    |7      |_foo
[45]    |      1080|        36|FUNC |WEAK |0    |7      |foo

$ cat >conflict2.c
#include <stdio.h>
void
foo(void)
{
	puts("i'm conflict2");
}
^D
$ cc -G -h libconflict2.so.0 -o libconflict2.so.0 conflict2.c
$ ln -sf libconflict2.so.0 libconflict2.so
$ nm libconflict2.so.0 | grep foo
[45]    |      1056|        36|FUNC |GLOB |0    |7      |foo

$ cat >test_conflict.c
extern void foo(void);

int
main(void)
{
	foo();
}
^D
$ cc -L. -lconflict1 -lconflict2 -o test_conflict test_conflict.c
$ nm test_conflict | grep foo
[59]    |    133440|         0|FUNC |GLOB |0    |UNDEF  |foo

$ ./test_conflict
i'm conflict1

libconflict1.soのfoo()が呼ばれていて、確かに動的リンク時には
ウィーク属性は無視されていることがわかります。

*BSD系や昔のLinux/glibcにおいてはSysV系とは異なり
動的リンクの場合も静的リンク時と全く同じ挙動とする扱いだったわけですが
仕様が「未定義」であり、同じSysVでもIrixのような例外ケースもあったのであれば
そのままでも問題ないわけでして。
わざわざ直す必要があったのか正直疑問に感じないでもありません

何か他にも「積極的にウィーク属性は動的リンク時に無視すべき」理由があったとか
情報をお持ちの方は教えていただけると
ひざかたとしちゃんかんげきーちょーっカマ!
キリ。


*1:OpenBSDのようにdlopen(3)が嫌われてたり、NetBSDの__link_set_*相当の
機能が無かったりする環境はまぁBSD Authのよに気合で何とかして頂戴。


異なる-std=*でcompileしたオブジェクトや共有ライブラリは混ぜるなきけん

あとついでですのでもう一点、以前のエントリで触れてなかったのですが
実は今のNetBSDもLinux/glibcもバイナリレベルで互換性を保てないケースが実は存在します。
それは例えば、-std=c89と-std=c99それぞれでcompileされた共有ライブラリをチャンポンで使う場合です。

以下サンプル@Fedora release 8 (Werewolf)

$ cat >foo.c
#include <stdio.h>
void
atoll()
{
	puts("my own atoll!");
}
void
foo()
{
	atoll();
}
^D
$ gcc -std=c89 -shared -Wl,-soname,libfoo.so.0 -o libfoo.so.0 foo.c
$ ln -sf libfoo.so.0 libfoo.so
$ nm libfoo.so.0 | grep atoll
0000045c T atoll

$ cat >bar.c
include <stdio.h>
#include <stdlib.h>

int
bar()
{
	long long int x;

	x = atoll("9999");
	printf("%lld\n", x);
}
$ gcc -std=c99 -shared -Wl,-soname,libbar.so.0 -o libbar.so.0 bar.c
$ ln -sf libbar.so.0 libbar.so
$ nm libbar.so.0 | grep atoll
         U atoll@@GLIBC_2.0

$ cat >test_mixed.c
extern void foo(void);
extern void bar(void);

int
main(void)
{
	foo();
	bar();
}
^D
$ gcc -Wl,-rpath,. -L. -lfoo -lbar -o test_mixed test_mixed.c
my own atoll!
my own atoll!
11400492471025677

わはは。
折角libbar.so.0はatoll@GLIBC_2.0というELF Symbol version付なんだから
ちゃんとlibcのatoll@GLIBC_2.0を呼べないこともない気もするのですが。
当然ながらNetBSDなんかもこういうケースはダメダメです。

COM/COM+/DCOM/ActiveXからはじまってManaged Codeに.Net Frameworkとか
C runtimeはアプリと同じディレクトリとかいいたくなる気持ちは痛いほどよく判るという。
まぁUnixはソースリコンパイル文化のせいで旧態依然としてますやね。
いわゆる.org frameworkってやつか。

今日

お手紙等にはなるべく反応したいと心がけておりますが
時間が無かったりで遅くなること多いです、ご容赦おば。

あとわざわざ メイドが三里塚で成田闘争も日教組冥土への一里塚
誕生日のお見舞い(そーゆーお年頃)のお言葉頂いた方々もありがとうございます。


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