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

2007/1/5(Fri)

あけましておめでとうございます

胃潰瘍再発、イテテテ。
痛みで夜中に目が覚めるとか、"Sister Morphine" by Rolling Stones 状態。
しばらくAFKかな、こりゃ。

Fedora Legacy shutting down

現場で使ってるFedoraCore2はとっくに Fedora Legacyにも見捨てられてたので
pkgsrc化してもはや Bluewall Linux状態だったのでどうでもよい。

ちなみにyum upgrade使えばFC2 → FC4くらいは何の問題も無く更新できるんだけどね。

2007/1/10(Wed)

pkgsrc

ここんとこpkgsrc/sj3-server-binがbuild失敗するのが放置プレイなんだけど
もしかしてsj3なんぞ使ってるのってもはや俺だけでつか?

コケる原因自体は こんなpatchで大層な話じゃないんだけど、それとは別にimakeが変な動きしてるのを発見。

rm -f .depend
/usr/pkg/libexec/itools/gccmakedep -f-  -- -I../include  -I/usr/pkg/include   -DCSRG_BASED   -DFUNCPROTO=15 -DNARROWPROTO    -- adddic.c        addelcmn.c      alloc.c         charsize.c      chrtbl.c        cl2knj.c        clstudy.c       cmpstr.c        conjunc.c       connect.c       cvtclknj.c      cvtdict.c       cvtkanji.c      deldic.c        depend.c        dict.c  functbl.c       fuzoku.c        fzkyomi.c       getkanji.c      getrank.c       global.c        hzstrlen.c      init.c  istrcmp.c       memcpy.c        memory.c        memset.c        mk2claus.c      mkbunset.c      mkjiritu.c      mkkouho.c       mknumber.c      mvmemd.c        mvmemi.c        peepdic.c       ph2knj.c        ph_khtbl.c      priority.c      prtytbl.c       s2ctbl.c        selclrec.c      selsuuji.c      setconj.c       setjrec.c       setkouho.c      setubi.c        sj2code.c       skiphblk.c      skipkstr.c      srchdict.c      srchhead.c      srchidx.c       srchnum.c       sstrcmp.c       sstrlen.c       sstrncmp.c      stbtbl.c        stttbl.c        study.c         suujitbl.c      terminat.c      termtbl.c       wakachi.c > .depend
gcc -O2   -I../include  -I/usr/pkg/include   -DCSRG_BASED   -DFUNCPROTO=15 -DNARROWPROTO   -I/usr/pkg/include -L/usr/pkg/lib -Wl,-R/usr/pkg/lib -o depend depend.c    
depend.c:55: warning: conflicting types for built-in function 'malloc'
depend.c: In function 'set_passwd':
depend.c:163: warning: incompatible implicit declaration of built-in function 'strncpy'
depend.c: In function 'set_comment':
depend.c:827: warning: incompatible implicit declaration of built-in function 'strncpy'
/usr/lib/crt0.o: In function `___start':
: undefined reference to `main'
/var/tmp//cc4ETdJM.o: In function `getofs':
depend.c:(.text+0x4e): undefined reference to `Jwork_base'
(中略)
depend.c:(.text+0x1408): undefined reference to `serv_errno'

[wrapper.sh] note: The real command line, after the pkgsrc wrapper, was:
/usr/pkgsrc/inputmethod/sj3-server-bin/work/.gcc/bin/gcc -O2 -I../include -I/usr/pkgsrc/inputmethod/sj3-server-bin/work/.buildlink/include -DCSRG_BASED -DFUNCPROTO=15 -DNARROWPROTO -L/usr/pkgsrc/inputmethod/sj3-server-bin/work/.buildlink/lib -Wl,-R/usr/pkg/lib -o depend depend.c 
*** Error code 1

Stop.
make: stopped in /usr/pkgsrc/inputmethod/sj3-server-bin/work/sj3-2.0.1.20/kanakan

make -kになってるから止まらないので誰も気付かなかったんだろうけど
なんでgcc -M depend.cじゃなくてgcc -o depend depend.cが実行されてるんだろね。
まあ、depend.cとかターゲットとconflictしそうなソース名つけるのが悪いのか。
これも直した方がいいのかなぁ。それまでsend-pr or commitしちゃ駄目?

まあそれより/etc/rc.d/sj3が バグってて 起動前にシステム辞書を消すのには参った... orz

2007/1/17(Wed)

mbrtowc weak symbol

Theoのインタビューの和訳などをされている tamoさんとこより。
それlibcのバグです、mbrtowcを weak-symbolにしないと。

つかNetBSDも同じバグあるわけですが、 以前から気付いてはいたんだけど放置してました。

つーわけでNetBSDの場合の 修正テストケース

このへんのweak symbolとか事前定義マクロ__STDC_VERSION__なんかの話は
解説書いた方が良さそうだな。

2007/1/18(Thu)

どのようにしてlibcは後方互換を保つのか?(その1)

国際標準規格(ISO)としてのCは現在のところ

と3つのバージョンが存在します、これらはすべて後方互換が保障されています。

ソースレベルでの後方互換とは

ということに尽きるのですが、ここでひとつ問題が発生します。それは名前の衝突です。

C90:AMD1やC99の改正ではいくつかの新しい標準関数が追加されました。
代表的なものは

$ man 3 mbrtowc
...
STANDARDS
     The mbrtowc() function conforms to ISO/IEC 9899/AMD1:1995 (``ISO C90,
     Amendment 1'').  The restrict qualifier is added at ISO/IEC 9899:1999
     (``ISO C99'').
$ man 3 atoll
STANDARDS
     The atoll() function conforms to ISO/IEC 9899:1999 (``ISO C99'').

などです。

しかしCにはC++のような名前空間はないですし、標準関数の命名規則やユーザ定義関数名の制限
(例えば"stdc_"ではじまる名前は標準関数に予約しユーザには使用を禁ずるなど)もありません。
つまりmbrtowcやatollという関数名はすでにユーザが別の目的に使っていたかもしれないのです。

以下は簡単なサンプルです。

test.c
--
#include <stdio.h>
#include <stdlib.h>

void
atoll(void)
{
	printf("hello, world.\n");
}

int
main(void)
{
	atoll();
	return 0;
}

atollという関数名はC90の頃は自由に使っていい名前だったはずなのに
FedoraCore6(glibc-2.5 + gcc-4.1.1)ではコンパイルに失敗します。

gcc -o test test.c
test.c:5: error: conflicting types for 'atoll'
/usr/include/stdlib.h:159: error: previous declaration of 'atoll' was here

このままではソース互換とはいえません。

この衝突を防ぐため提供される機能が事前定義マクロ__STDC_VERSION__です。
事前定義マクロというのは、何かヘッダをインクルードせずとも予め定義されるマクロです。

__名前__

のように前後にアンダースコアが2つつきます。
規約にはISO Cのバージョン情報として、以下の事前定義マクロあります。

#define __STDC__		1		/* ISO-Cである		*/
#undef __STDC_VERSION__				/* C90では定義されない	*/
#define __STDC_VERSION__	199409L		/* C90:AMD1の場合	*/
#define __STDC_VERSION__	199901L		/* C99の場合		*/

この値をセットするのはプリプロセッサです。
gccの場合-stdオプションを使うことで__STDC_VERSION__を制御できます。

規約どおりですね。

ではこのマクロをどう使ってるのか、実際にglibc-2.5のヘッダファイルを見てみましょう。 まずは features.hです。

#if (defined _ISOC99_SOURCE || defined _ISOC9X_SOURCE \
     || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L))
# define __USE_ISOC99   1
#endif

__STDC_VERSION__が199901L以上の場合、__USE_ISOC99が定義されます。
ではさっき名前衝突したatoll(3)の宣言を見てみましょう。 stdlib.hです。

#if defined __USE_ISOC99 || (defined __GLIBC_HAVE_LONG_LONG && defined __USE_MISC)
__BEGIN_NAMESPACE_C99
/* Convert a string to a long long integer.  */
__extension__ extern long long int atoll (__const char *__nptr)
     __THROW __attribute_pure__ __nonnull ((1)) __wur;
__END_NAMESPACE_C99
#endif

atoll(3)は__USE_ISOC99が定義されている場合のみ有効になっています。
OpenBSDの場合もほぼ同様です、定義されてるヘッダは sys/types.hになります。

#if defined(_ISOC99_SOURCE) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901)
# undef __ISO_C_VISIBLE
# define __ISO_C_VISIBLE	1999
#endif

同様に stdlib.hです。

#if __ISO_C_VISIBLE >= 1999
long long
         atoll(const char *);
...

※ NetBSDについてはここでは解説しません、理由はのちほど。
つまりgcc -stdc=c89で名前衝突は避けられるということです。

$ gcc -std=c89 -o test test.c
$ nm test | grep atoll
08048364 T atoll
$ ./test
hello, world.

今度は問題なくコンパイルできました。

この他にも

などの名前空間保護もあるのですが、手法は同じなので解説を割愛させていただきます。

次回はバイナリレベルでの後方互換性の話です。

NetBSD

今回なぜFC6使って解説してるかというと...それはNetBSDが(ry

めも

B-Treeでなくskip listsアルゴリズムを使ったDBM、 SkipDB。sleepycat BDB4やqdbmより速いらしい。
ライセンスは3-clause BSDL、そのうち試してみよ。

2007/1/19(Fri)

GNU binutilsで遊んでみよう

前回はソースレベルでの互換性を保つための、事前定義マクロによるトリックを解説しました。
この他にも、libcにはバイナリレベルでもいくつかの仕掛けがあるのですが
それを説明する前に、基礎知識編としてGNU binutilsで遊んでみます。

GNU binutilsはバイナリを操作するツールの詰め合わせパックなのですが
今回はnm(1)とldd(1)で遊びます。

オブジェクトにあるシンボルを確認する為にはnm(1)を使います。
objdump(1)の-tオプションや、オブジェクトがELF形式であれば
readelf(1)の-sオプションでもかまいません。

$ nm /usr/bin/nm
0805b014 A _DYNAMIC
0805b0f4 D _GLOBAL_OFFSET_TABLE_
         w _Jv_RegisterClasses
0805b004 d __CTOR_END__
0805b000 d __CTOR_LIST__
0805b00c d __DTOR_END__
0805b008 d __DTOR_LIST__
(以下略)

わらわらとシンボルの一覧が表示されます。

$ nm /usr/bin/nm
nm: /usr/bin/nm: no symbols

と表示される方は、strip(1)でシンボルが取り除かれています、残念。
私の手元のFedoraCore6がそうみたいです、-Dスイッチを使ってみてください。

nm(1)の出力を簡単に説明します、左からシンボルの

となっています。
グローバルとローカルの違いは他のオブジェクトからの可視性です。
これについては後で説明します。

んで実行ファイルや共有ライブラリの依存関係を表示するコマンド、ldd(1)です。
Cygwinなどではldd(1)がありません、objdump -xの結果をNEEDED(Cygwinの場合DLL Name)でgrep(1)
することで代用してください。

$ ldd /usr/X11R6/bin/xterm
/usr/X11R6/bin/xterm:
	-lX11.6 => /usr/X11R6/lib/libX11.so.6
	-lfreetype.9 => /usr/X11R6/lib/libfreetype.so.9
	(中略)
        -lc.12 => /usr/lib/libc.so.12

出力の読み方については、まあ説明するまでもないでしょう。

では先ほどのグローバルとローカルのシンボルの違いについて。
先ずはグローバルの例です。

global.c
------------------------------
#include <stdio.h>
void foo()
{
	puts("hello, world. i'm global.");
}
 Cygwinの場合は、libglobal.so.0をcygglobal.dllに置き換えてください
$ gcc -shared -Wl,-soname,libglobal.so.0 -o libglobal.so.0 global.c

$ nm libglobal.so.0 | grep foo
000004ec T foo

大文字になっています。

次にローカルの例です。
static修飾子をつけて宣言した関数は他のCソースから使用できなくなります。

local.c
------------------------------
#include <stdio.h>
static void foo()
{
	puts("hello, world. i'm local.");
}
 Cygwinの場合は、liblocal.so.0をcyglocal.dllに置き換えてください
$ gcc -shared -Wl,-soname,liblocal.so.0 -o liblocal.so.0 local.c

$ nm liblocal.so.0 | grep foo
000004dc t foo

ローカルシンボルになりました。

ちなみにtおよびTの意味は、シンボルがテキストセグメント(.text)にあるということです。
テキストセグメントとは?という話は本題とはあんま関係ないので割愛、知らない人は
要するに「シンボルがあるべきとこにある状態」だと思っていてください。

本当に外部から参照できないか、試してみましょう。
まずはlibglobal.so.0とリンクした実行ファイルを作ります。

test.c
------------------------------
extern void foo(void);
int main(void)
{
        foo();
        return 0;
}
$ ln -s libglobal.so.0 libglobal.so
$ gcc -Wl,-rpath=. -L. -lglobal -o test test.c

コンパイルに成功しました、ldd(1)とnm(1)の結果を見てみましょう。

$ ldd test
test:
	-lglobal.0 => ./libglobal.so.0
	-lc.12 => /usr/lib/libc.so.12
$ nm test | grep foo
	U foo

ldd(1)の結果より実行ファイルがlibglobal.so.0に依存していることが判ります。
nmの結果はアドレスが空、型は"U"(未定義/未解決)になっています。
実行ファイルにはfooシンボルの実体がないので、必要な時に外部ライブラリを探すという意味です。

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

$ ./test
hello, world. i'm global.

ちゃんとlibglobal.so.0のfoo()が呼び出されていますね。

今度はliblocal.so.0とリンクした実行ファイルができるかやってみましょう。

$ rm -f test
$ ln -s liblocal.so.0 liblocal.so
$ gcc -Wl,-rpath=. -L. -llocal -o test test.c
/var/tmp//cc8c1Zh2.o: In function `main':
test.c:(.text+0x12): undefined reference to `foo'

リンク時にfooというシンボルが解決できずビルドに失敗しました。
リンカからローカルシンボルは不可視ということです。

ではリンクに成功したlibglobalのfooのグローバルシンボルをローカルに変えてしまったらどうなるでしょうか?
local.cをlibglobal.so.0としてビルドします。

$ gcc -shared -Wl,-soname,libglobal.so.0 -o libglobal.so.0 local.c

$ nm libglobal.so.0 | grep foo
000004ec T foo
$ ./test

NetBSDの場合
./test: Undefined PLT symbol "foo" (symnum = 10)

Linuxの場合
./test: symbol lookup error: ./test: undefined symbol: foo

fooなんか見つからないよと怒られてプログラムは実行できませんでした。
リンクローダからもローカルシンボルは不可視という訳です。

まだまだ続きますが、今日はここまで。

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のみへのリンクになります。

2007/1/21(Sun)

ウィークシンボルって何ですか?

@ 2008/10/09 追記

Linux及び多くのSysV実装(Solaris, AIX etc)では動的リンクの場合、weakをstrongとして扱います。
ですのでサンプルはうまく動かないでしょう。
こちらをご一読ください。

@

さて今回はウィークシンボルのお話。

前回の最後で共有ライブラリlibconflict1.so.0とlibconflict2.so.0で
barとbuzというシンボルが重複する場合

  • barはlibconflict1.so.0のものを
  • buzはlibconflict2.so.0のものを

使いたいような場合は、リンク順では解決できないという話をしました。
この場合ウィークシンボルを使えば解決できます。

では実際に、前回のconflict1.cのbuzをウィークシンボルにしてみましょう。
一度gas(1)形式のアセンブラソースに落とす必要があります。
これにはgcc(1)の-Sスイッチを使います。

$ gcc -S -o conflict1.s conflict1.c

では出力された conflict1.sの中身を見てみましょう。

	.file	"conflict1.c"
	.section	.rodata
.LC0:
	.string	"i'm conflict1::bar!."
	.text
.globl bar
	.type	bar, @function
bar:
	pushl	%ebp
(中略)
	.section	.rodata
.LC1:
	.string	"i'm conflict1::buz!."
	.text
.globl buz
	.type	buz, @function
buz:
	pushl	%ebp
(以下略)

このうち

.globl bar
.globl buz

としている部分がbarおよびbuzをグローバルシンボルにするという指令です。

ではbuzをウィークシンボルに書き換えます、やることは

  • .globl指令を.weak指令に変更
  • ラベルの名称をbuz:から_buz:に変更
  • buzの参照先をラベル_buz:に変更

です。

--- conflict1.s.orig	2007-01-21 03:26:03.000000000 +0900
+++ conflict1.s	2007-01-21 03:34:19.000000000 +0900
@@ -20,9 +20,10 @@
 .LC1:
 	.string	"i'm conflict1::buz!."
 	.text
-.globl buz
+.weak buz
+	buz = _buz
 	.type	buz, @function
-buz:
+_buz:
 	pushl	%ebp
 	movl	%esp, %ebp
 	subl	$8, %esp

次に書換後の conflict1.sをコンパイルしてみましょう。

$ gcc -shared -Wl,-soname,libconflict1.so.0 -o libconflict1.so.0 conflict1.s
$ nm libconflict1.so.0 | grep b[au][rz]
00000534 t _buz
0000051c T bar
00000534 W buz

buzの型が"T"から"W"に変わっています。
名前を_buzに変更したラベルはローカルシンボルとなっています。

$ 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!

barは変わらずlibconflict1.so.0が呼ばれますが、ウィークシンボルにした
libconflict1.so.0のbuzは呼ばれずlibconfict2.so.0が呼ばれました、成功です。

ではlibconflict2からbuzを消した場合どうなるでしょうか。

conflict2.c
--------------------
#include <stdio.h>
void bar(void) {
	puts("i'm conflict2::bar!.");
}
$ gcc -shared -Wl,-soname,libconflict2.so.0 -o libconflict2.so.0 conflict2.c
$ nm libconflict2.so.0  | grep b[au][rz]                                       
000004ec T bar
$ ./test_conflict
i'm conflict1::bar!
i'm conflict1::buz!

ローカルシンボルに変更したlibconflict1.so.0の_buzが呼ばれています。

以上でお判りの通り、リンクローダはリンク順が上位であっても
ウィークシンボルであればシンボル解決を一旦留保します。
下位のライブラリにあればそちらを使いますが、解決できなければ
ウィークシンボルは参照先を呼びます。
いいかげんに書いた フローチャート

ウィークシンボルは何につかうのですか?

実際にこのウィークシンボルはどのように使われているのでしょうか?
それはlibcの内部でも呼び出すけど実装そのものは別ライブラリに追い出したいという場合です。

NetBSDやOpenBSDの場合、pthread(3)の関数をlibcからlibpthreadに移す為に使われてたりします。
NetBSDの pthread.hには

#define pthread_mutex_lock	__libc_mutex_lock
...

の定義がありますのでlibcをnm(1)した結果を__libc_mutex_でgrep(1)してみると

$ nm /lib/libc.so.12.150 | grep __libc_mutex_
000220a4 T __libc_mutex_catchall_stub
...
000220a4 W __libc_mutex_lock

ウィークシンボル化され、参照先はスタブ(__libc_mutext_catchall_stub)になっています。

スタブの中身を見てみましょう、 thread-stub.cです。

 61 extern int __isthreaded;
 62
 63 #define DIE()   (void)kill(getpid(), SIGABRT)
 64
 65 #define CHECK_NOT_THREADED_ALWAYS()     \
 66 do {                                    \
 67         if (__isthreaded)               \
 68                 DIE();                  \
 69 } while (/*CONSTCOND*/0)
 70
 71 #if 1
 72 #define CHECK_NOT_THREADED()    CHECK_NOT_THREADED_ALWAYS()
 73 #else
 74 #define CHECK_NOT_THREADED()    /* nothing */
 75 #endif
...

 82 __weak_alias(__libc_mutex_init,__libc_mutex_init_stub)
...

 95 int
 96 __libc_mutex_init_stub(mutex_t *m, const mutexattr_t *a)
 97 {
 98 	/* LINTED deliberate lack of effect */
 99 	(void)m;
100 	/* LINTED deliberate lack of effect */
101 	(void)a;
102
103 	CHECK_NOT_THREADED();
104
105 	return (0);
106 }
...


中身は__isthreaded変数をチェックして真なら自らをkill(2)するだけです。

82行目の__weak_aliasマクロは上の演習でアセンブラに落として行った
.globlから.weak指令への書き換えをCのインラインアセンブラでやるマクロです。
sys/cdefs_elf.hに定義されています。

#define __weak_alias(alias,sym)                                         \
    __asm(".weak " _C_LABEL_STRING(#alias) "\n"                 \
            _C_LABEL_STRING(#alias) " = " _C_LABEL_STRING(#sym));

OpenBSDも同名のマクロが sys/cdefs.hに定義されてます、glibcの場合は libc-symbol.hあたりにいろいろあるようです。

世の中のほとんどのアプリケーションはマルチスレッド対応は必要のないものです。
使用頻度の低いpthread(3)の関数は別ライブラリにしてしまえばlibcのサイズは小さくなります。
しかしここでひとつ問題があります、libcの一部の関数はpthread(3)に依存しているのです。

例えばfgetc(3)などのファイル操作関数は、内部でflockfile(3)を呼び出します。
fgetc.c

50 int
51 fgetc(fp)
52 	FILE *fp;
53 {
54 	int r;
55
56 	_DIAGASSERT(fp != NULL);
57
58 	FLOCKFILE(fp);
59 	r = __sgetc(fp);
...

これはマルチスレッドの場合、他のスレッドとの競合状態(レースコンディション)を防ぐ為です。
競合状態って? という人は 読んでください。
flockfile(3)の実装はpthread_mutex_lock(=__libc_mutex_lock)を使って行われています。
flockfile.c

 67 void
 68 flockfile(FILE *fp)
 69 {
 70
 71 	__flockfile_internal(fp, 0);
...

105 void
106 __flockfile_internal(FILE *fp, int internal)
107 {
108  
109 	if (__isthreaded == 0)
110 		return;
111         
112 	mutex_lock(&_LOCK(fp));
...

__isthreadedが偽の場合もせず終了しますが、mutex_lock(=__libc_mutex_lock)を
呼んでることに変わりありません、単純にlibcからpthread(3)を追い出すと
libcのコンパイル時にリンカは__libc_mutex_lockのシンボルを解決できずリンクに失敗します(*1)。
ですのでリンカをだます為、__libc_mutex_lockをウィークシンボルにしてlibc自身に定義しておく必要があるのです。

ここまでの話をもっと詳しく知りたい(あるいはお前の話じゃ判んねーYO!)という方は

あたりの本を読んでください。

次回は本題に戻り、どのようにしてlibcは後方互換性を保つのか?(その2)の予定。


(*1)
実は最近のGNU ld(1)だとエラーにならず、"U"(未定義グローバルシンボル)になるみたい。
これ困ったことに、例えば_DIAGASSERTというマクロを打ち間違えて
_DIAGASSSERTとかミススペルをしてもリンク時に発覚せず、実行時に

./test: Undefined PLT symbol "_DIAGASSSERT" (symnum = 10)

とエラーになるので、時々libcが壊れてるのに気付かずインストールしてハマります...


(補足)
シンボルの競合の解決にはもっと複雑なケースが考えられます。
場合によっては同一アプリケーションの中でlibconflict1.so.0のbarと
libconflict2.so.0のbarを使い分けたいケースもあるでしょう。
これは当然ウィークシンボルでは不可能です。

そんな場合には、SunOS由来のdlopen(3), dlsym(3)が使えるかもしれません。
Cygwinの場合ならWin32APIのLoadLibrary, GetProcAddressでしょうか。
これらについては本題とはあまり関係がないので別の機会に譲ります。

2007/1/23(Tue)

どのようにしてlibcは後方互換を保つのか(その2)

その1では、C90とC90:AMD1、C99のそれぞれのバージョン間で後方互換を保つということは

ということを説明しました。
今回はバイナリレベルではlibcはどのようにして名前衝突を避けるか、を説明します。

libcはld(1)によって暗黙的にリンクされます、この場合リンク順は一番最後になります。
(libcをリンクしたくない場合、例えばkernelのビルドなどの場合は-ffreestandingスイッチが必要です)

libcのリンク順が一番最後である場合、名前衝突は発生しません。
前に説明した通り、シンボルが重複する場合は実行ファイルに静的リンクされてる同名シンボル、
あるいは動的リンクされた共有ライブラリの内で、リンク順が最も先のものが使われるからです。

復習がてらサンプルを作って確認してみましょう。
C90:AMD1のmbrtowc(3)と重複するシンボルを持つ共有ライブラリをC90でコンパイルします。

user_mbrtowc.c
----------------------------------------
#include <stdio.h>
void mbrtowc()
{
	puts("hello, world.");
}
$ gcc -std=c89 -shared -Wl,-soname,libmbrtowc.so.0 -o libmbrtowc.so.0 user_mbrtowc.c
$ nm libmbrtowc.so.0 | grep mbrtowc
000004ec T mbrtowc

次はこのユーザ定義mbrtowcを呼び出すアプリケーションを用意します。

test_mbrtowc.c
----------------------------------------
extern void mbrtowc(void);
int main(void)
{
	mbrtowc();
	return 0;
}
$ ln -sf libmbrtowc.so.0 libmbrtowc.so
$ gcc -std=c89 -Wl,-rpath=. -L. -lmbrtowc -o test_mbrtowc test_mbrtowc.c
$ nm test_mbrtowc | grep mbrtowc
	U mbrtowc

それでは実行してみましょう。

$ ./test_mbrtowc
hello, world.

libmbrtowc.so.0のmbrtowcが呼ばれています。
つまりlibcの後方互換性はちゃんと守られているということです。

ではlibcのリンク順を明示的に指定した場合はどうでしょうか?

$ gcc -g -std=c89 -Wl,-rpath=. -L/usr/lib -lc -L. -lmbrtowc -o test_mbrtowc test_mbrtowc.c
$ ldd test_mbrtowc
test_mbrtowc:
	-lc.12 => /usr/lib/libc.so.12
	-lmbrtowc.0 => ./libmbrtowc.so.0
$ gdb ./test_mbrtowc
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386--netbsdelf"...
(gdb)
Starting program: /home/tnoazki/tmp/test_mbrtowc

Program received signal SIGSEGV, Segmentation fault.
0xbbbbc65a in mbrtowc () from /usr/lib/libc.so.12
(gdb)

libcのmbrtowc(3)が呼ばれ、Segmentation faultが発生しました。
つまりlibcのバイナリレベルでの後方互換を保つ為には
libcのリンク順を明示的に指定してはならないということです。
普通のアプリケーション開発者はそれさえ守っていればまず問題はおきないでしょう。

しかしlibcの開発者はまだまだ注意しなければならないことがあります。
C90の関数からC90:AMD1やC99の関数を呼び出すなんていうことは良くあることなのですが
この場合、ユーザ定義のシンボルにより上書きされる可能性を考慮しなければなりません。

例としてNetBSDとOpenBSDの場合を説明します。
C90の関数であるprintf(実装の本体はvfprintf)は、フォーマット文字列の解析に内部的に
C90:AMD1の関数であるmbrtowc(3)を呼び出しています。
vfprint.c

197 int
198 __vfprintf_unlocked(fp, fmt0, ap)
...

345 		while ((n = mbrtowc(&wc, fmt, MB_CUR_MAX, &ps)) > 0) {
346 			fmt += n;
347 			if (wc == '%') {
...

何も対策をしないままだと、これはユーザ定義のmbrtowcに置き換えられてしまいます。
先ほどのユーザ定義mbrtowcを修正して、puts(3)でなくprintf(3)に変更してみましょう。

----------------------------------------
--- user_mbrtowc.c.orig 2007-01-22 01:08:26.000000000 +0900
+++ user_mbrtowc.c      2007-01-22 01:46:42.000000000 +0900
@@ -1,6 +1,6 @@
 #include <stdio.h>
 void mbrtowc()
 {
-       puts("hello, world.");
+       printf("hello, world.");
 }
----------------------------------------
$ gcc -std=c89 -shared -Wl,-soname,libmbrtowc.so.0 -o libmbrtowc.so.0 user_mbrtowc.c

ではリンク順を修正して、test_mbrtowcを実行します。

$ rm -f test_mbrtowc
$ gcc -g -std=c89 -Wl,-rpath=. -L. -lmbrtowc -o test_mbrtowc test_mbrtowc.c
$ gdb ./test_mbrtowc
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386--netbsdelf"...
(gdb) run
Starting program: /home/tnozaki/tmp/test_mbrtowc

Program received signal SIGSEGV, Segmentation fault.
0xbbbb8314 in __vfprintf_unlocked () from /usr/lib/libc.so.12
(gdb)

Segmentation faultが発生しました。
これは__vfprintf_unlocked()内で、libcのmbrtowc(3)ではなくユーザ定義シンボルが呼び出されたのが原因です。
明らかにバグです(← つかさっさと直せ、俺)。

それでは正しい対策がされている例を見てみましょう。
C90の関数であるscanf(実装はvfscanf)は、入力された文字列を数値に変換する為に
内部的にC99の関数であるstrtoimax(3)を呼び出しています。
vfscanf.c

136 int
137 __svfscanf_unlocked(fp, fmt0, ap)
...

152 	uintmax_t (*ccfn) __P((const char *, char **, int));
...

251 		case 'd':
252 			c = CT_INT;
253 			ccfn = (uintmax_t (*) __P((const char *, char **, int)))strtoimax;
...

しかしこちらの場合はユーザ定義のstrtoimaxで置き換えても正しい動作をします。
この違いは何故でしょうか?

NetBSDとOpenBSDの場合 namespace.hというものを使って名前空間の保護を行っています。

65 #define strtoimax	_strtoimax
66 #define strtold	_strtold
67 #define strtoll	_strtoll
...

プリプロセッサでstrtoimaxを_strtoimaxに置換するだけの非常にシンプルなものです。

慣例上アンダースコアで始まるシンボル名はlibcが内部実装用に使うものとして予約され、
アプリケーション開発者は使ってはならないことになっています。
#あくまで慣例上なのでexternすれば簡単に呼べてしまいますが...
#つか呼ばないで下さい、おながいしまふ。

vfscanf.cは44行目でnamespace.hをインクルードしています

42 #endif /* LIBC_SCCS and not lint */
43
44 #include "namespace.h"
45
...
$ cd /usr/src/lib/libc/stdio
$ gcc -I../include -E vfscanf.c | grep strtoimax
intmax_t _strtoimax(const char * __restrict,
   ccfn = (uintmax_t (*) (const char *, char **, int))_strtoimax;
   ccfn = (uintmax_t (*) (const char *, char **, int))_strtoimax;
...

よってnamespace.hをインクルードすることで、直接strtoimax(3)を呼ぶのではなく
libcの内部実装用の_strtoimax呼ぶように自動的に書き換えられているわけです。
これならばユーザ定義のstrtoimaxによってシンボルが上書きされたとしても影響は受けません。

内部実装用の_strtoimaxは strtoimax.c

59 intmax_t
60 _strtoimax(nptr, endptr, base)
61 	const char *nptr;
62 	char **endptr;
63 	int base;
64 {
...

に定義されています。

そしてもうひとつの工夫です、strtoimax(外部向け)はこの_strtoimax(内部向け)を
_strtoimax.cにあるように

41 #include <inttypes.h>
42 intmax_t	_strtoimax(const char *, char **, int);
43
44 intmax_t
45 strtoimax(const char *nptr, char **endptr, int base)
46 {
47
48	return _strtoimax(nptr, endptr, base);
...

と呼び出すだけの関数として実装すると、関数呼び出しのコスト(分岐命令、スタックの積み替え etc)が無駄です。
このオーバーヘッドを回避するいい方法はないのでしょうか。

ここで 前回解説したウィークシンボルの応用です。
先ほどの strtoimax.cをもう一度見てみましょう。

49 #ifdef __weak_alias
50 __weak_alias(strtoimax, _strtoimax)
51 #endif
...

strtoimaxをウィークシンボル化として定義し、参照先は_strtoimaxにしてあります。
nm(1)の出力を見ると、strtoimaxは_strtoimaxと同じアドレスになっています。

$ nm /usr/lib/libc.so.12.150 | grep strtoimax
000717d4 T _strtoimax
000717d4 W strtoimax

これならばリンクローダがウィークシンボルを解決する時のオーバヘッドだけで済み、
strtoimaxが繰り返し呼ばれる場合も無駄なコストはかからない、という訳です。

glibcも同じアイデアです、差は内部実装の名前が__strtol_internalとなっているくらいだと思います。
stdlib.h

289 #ifndef __strtol_internal_defined
290 extern long int __strtol_internal (__const char *__restrict __nptr,
291 				    char **__restrict __endptr,
292 				    int __base, int __group)
293      __THROW __nonnull ((1)) __wur;
294 # define __strtol_internal_defined	1
295 #endif
...

vfscanf.c

1544               if (number_signed)
1545                 num.l = __strtol_internal (wp, &tw, base, flags & GROUP);
1546               else
...

次回(最終回)はlibcのsoname変更問題、NetBSDの__RENAMEマクロや
SolarisやglibcのELF symbol versioningについてを予定しています。