Not only is the Internet dead, it's starting to smell really bad.:2007年01月中旬

2007/01/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/01/17(Wed)

mbrtowc weak symbol

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

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

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

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

2007/01/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/01/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なんか見つからないよと怒られてプログラムは実行できませんでした。
リンクローダからもローカルシンボルは不可視という訳です。

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