The Man Who Fell From The Wrong Side Of The Sky:2013年11月分

2013/11/11(Mon)

[Unix C][*BSD][な闇深] sys/cdefs.hとは何ですか? (その1)

@ 誰も教えてくれない謎のヘッダファイル

sys/cdefs.hはUnix Cプログラミング初心者にとっては混乱の源です。

#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)abort.c     8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: abort.c,v 1.14 2011/05/18 19:36:36 dsl Exp $");
#endif
#endif /* LIBC_SCCS and not lint */

「お・ま・じ・な・い(滝川Christos)」と脈絡もなく突然includeされた後は、 初心者が書店で一番最初に手にするようなC言語本 *1には載っていない謎のマクロが延々と続きます。

うーんこのぐうの音も出ない畜生、これではソースを読もうというその意志いきなり挫かれますやね、そしてソースコードをVisualC++のIDEにコピペしてみれば

fatal error: sys/cdefs.h: No such file or directory

の無情なエラーメッセージが表示され、コンパイルすることすら許されないのか(絶句)。

@ いつものように歴史のお勉強

元々BSDの標準Cコンパイラは2.9BSDの頃から長らく pcc(Portable C Compiler)でしたが、4.3BSDの頃になると

  • 衰退しつつあるVAX*2からそれ以外のアーキテクチャ *3への移植が求められたこと
  • プログラミング言語C自体も1989年のANSI C89*4そして翌年にISO C90 *5が成立し *6し、これらに準拠することが求められたこと

といった理由でより優れたCコンパイラが求められるようになりました、前者だけならpccでも良かったんだろうけど。

そんなわけで 4.3BSD-Renoの頃よりシステム標準のCコンパイラを gcc(当時はGNU C Compilerの略、今はGNU Compiler Collection)に変更する作業がはじまり、 4.3BSD Net/2にて 完成します。

ここでpccはソースツリーから完全に取り除かれることとなります *7、ちなみにpccが再び*BSDのソースツリーに帰還するのは 16年後ですな、OpenBSDはもう飽きたようでさっさとツリーから消したけど、NetBSDでは まだある

まぁそんな努力も空しくその後( 震え声)

@ 這い回る混乱の唯一の生き残り

話を戻すとsys/cdefs.hは正にこの

  • プログラミング言語Cの仕様差異(K&R → ISO C90)
  • Cコンパイラの実装差異(pcc → gcc)
  • C++との互換性

を埋める為のportability layerとして 導入されました、要するにpccにあわせて書かれていたコードを書き直さずマクロの黒魔術でgccでもコンパイルできるようにしたってことです。

@ sys/cdefs.hのポータビリティは?

もちろんない(断言)、そもそもWindowsはまだしもSolarisとかにだってsys/cdefs.hはないんやで。

*BSD以外だとCygwinやglibc2にはあるけど、前者はNetBSDのlibcからforkしたnewlibをベースにしてるので有って当然。

後者についてはglibc2のコードを

The Regents of the University of California

でgrepすれば判るように、多くのコードをBSDから パチ移植しているので、移植の手間を省く為に用意してますやね。

まぁどれもある程度は同じマクロが使えるけど、完全に互換性があるわけではないので注意。

@ 次回は

実際にsys/cdefs.hの中身について解説する予定(確定とは言っていない)。

*1:著者名はお察しください
*2:VAXが衰退したとは露にも思ってない 人達もいますが…
*3:4.3BSD-TahoeでPower 6/32に移植←わかる 現在←これわかんねぇなもう
*4:ANSI X3.159-1989
*5:ISO/IEC 9899:1990
*6:それ以前の1988年にもPOSIX.1すなわち IEEE Std 1003.1-1988が成立してますな。
*7:同時にデバッガもadb(absolute debugger、Androidのアレとは無関係でSolarisとかではまだ現役)からgdbに変更されています。

2013/11/12(Tue)

[Unix C][*BSD][な闇無用] sys/cdefs.hとは何ですか? (その2)

その1においてはsys/cdefs.hという謎のヘッダファイルが生まれた背景を説明しましたが、今回は実装について説明します。

@ __P() マクロ (*BSD/glibc2/newlib)

ANSI-C89/ISO-C90においてヘッダファイル等における関数宣言は

#include <stdio.h>
#include <stdlib.h>

/* 関数プロトタイプ宣言 */
void foo(char *);

int
main(void)
{
	foo("hello, world.");
	exit(EXIT_SUCCESS);
}

void
foo(char *msg)
{
	puts(msg);
}

のように引数リストの型(名前は省略可)を明記します、これを関数プロトタイプといいます。

ところがK&Rの時代にはこのプロトタイプの機能はまだ存在せず、関数宣言では引数リストは省略します。

#include <stdio.h>

/* 関数宣言 */
void foo();

main()
{
	foo("hello, world.");
}

void
foo(msg)
    char *msg;
{
	puts(msg);
}

プロトタイプが必要になった理由は、コンパイラーの引数型チェック強化によるエラー検出などですが、ここでは省略。

@ __P()マクロの使い方

この差異を埋めるため、__P()マクロは使用されます。

#include <stdio.h>

/* 関数プロトタイプ宣言 */
void foo __P((char *));

main(void)
{
	foo("hello, world.");
}

void
foo(msg)
    char *msg;
{
	puts(msg);
}

以上のように、引数リストを__P()マクロで囲みます。

sys/cdefs.hでは__Pマクロは

#if defined(__STDC__) || defined(__cplusplus)
#define	__P(protos)	protos		/* full-blown ANSI C */
...
#else	/* !(__STDC__ || __cplusplus) */
#define	__P(protos)	()		/* traditional C preprocessor */
...
#endif	/* !(__STDC__ || __cplusplus) */

のように宣言されていますので

  • 事前定義マクロ__STDC__が定義されている = ANSI-C89/ISO-C90準拠のコンパイラである
  • 事前定義マクロ__cplusplusが定義されている = C++コンパイラである

の場合には

void foo __P((char *));

はCプリプロセッサによって

void foo(char *);

のように関数プロトタイプ宣言として展開されますし、旧来のK&Rなコンパイラであれば

void foo();

として関数宣言になります、事前定義マクロとはなんぞという人は 過去回読んでどうぞ。

その1でsys/cdefs.hの目的として挙げた「コードを書き直さず」は達成できませんでしたが、このマクロの黒魔術によってK&RとANSI-CどっちのCコンパイラでもこれでビルド可能になったわけです。

@ __P()マクロの現状

そもそもK&R文法しか受け付けないコンパイラは、わざわざひっぱり出してでもこない限り無い(断言)ので、NetBSDではobsoleteとしてみなされます。 よって 見次殺。過去のcommit mailを「 de-__P」で検索すれば大量に見つかると思います。

FreeBSDやOpenBSDも同様つーかNより3年くらい先行してやってます、げに恐るべきはNetBSD時間。

@ EXFUN() マクロ (gcc/newlib)

オマケネタ。

このマクロはsys/cdefs.hのものではないのですが、 gccにはほぼ同目的のK&RとANSI-Cのポータビリティレイヤである ansidecl.hというものが存在します。

またansidecl.hとほぼ同等のものがnewlibにも _ansi.hとして存在したり、こっちは_EXFUN()のようにアンスコがprefixに付きますが。

EXFUN()は先ほど説明した__P()と同じ目的で導入されたマクロです。

#if defined (__STDC__) || defined(__cplusplus) || ...
...
#define EXFUN(name, proto)              name proto
...
#else   /* Not ANSI C.  */
...
#define EXFUN(name, proto)              name()
...
#endif  /* ANSI C.  */

こちらについては、obsoleteというわけではないようでガチガチに使われています。

私もかつてCitrusのwcs系関数を Cygwinのえらい人(定冠詞)に マージして頂いた時にもガッツリ_ansi.h使って書き直したり。style(9)は大事だからしょうがないね。

@ 次回予告

今回書ききれなかったのでもうちょいK&RとANSI/ISO-Cの関数定義の違いを吸収するマクロの話をします。

テスト

一時壊れたRSS吐いてたのを直したらfeedlyが更新されなくなってるのは何故なんだぜ

2013/11/14(Thu)

[Unix C][*BSD][申あ絶N] sys/cdefs.hとは何ですか? (その3)

その2では__P()マクロというもので

ことを学びました。

@ K&RとANSI/ISO-Cの関数定義における仮引数の宣言方法の差異

ANSI/ISO-Cでの構文の変更な大きなものとしてもうひとつ、関数定義における仮引数の宣言の方法があります。

まずこちらがK&R

foo(arg1, arg2)
char *arg2;
{
	...
}

bar()
{
	...
}

こいつは

  • 戻り値の型(これを型指定子といいます)を省略するとint扱い
  • 仮引数のリストは()内に「変数名(, 変数名...)」だけ宣言(これを識別子並びといいます)
  • 関数本文の{}ブロックの前に引数を(通常の変数と同じ形式で)宣言(これを宣言並びといいます)
  • ↑を省略すると識別子の型はint扱い

といったところが文法の特徴ですかね、これはALGOLの文法で

integer procedure foo(arg1, arg2);
integer arg1;
value arg2;
begin
	...
end

のように関数定義することの影響ですやね、いやよう知らんけど。

んでこっちがANSI/ISO-C

int
foo(int arg1, char *arg2)
{
	...
}

int
bar(void)
{
	...
}

こっちはいつも見慣れたCのコードですが

  • 型指定子はintであっても書く事が推奨(gccでは-Wreturn-typeでチェック可能)
  • 仮引数のリストは()の中に「型 変数名(, 型 変数名...)」で宣言(これを仮引数型並びといいます)
  • 仮引数が無い場合は仮引数にvoidを指定することを推奨

という文法ですやね。

@ ソースの後方互換性は?

実はこの関数定義での引数の宣言方法は、ANSI/ISO-CにおいてもK&Rと同じ文法を許容しています。 おかげで「6.9.1 関数定義」あたりの文章が致命的に理解しがたい悪文の見本 *1になっとりますな。

ですのでソースをK&Rのままにしておけばどっちの規格準拠のコンパイラでも問題なくビルドできます。 実際にNet/2のソースを見ると

  • 関数宣言は__P()マクロで書き直した
  • 関数定義については「何もしない」でK&Rのまま放置*2

という状態ですな、 実例

@ 関数定義仮引数のスタイルの現状は?

関数宣言での__P()マクロが抹殺された時よりは緩やかですが、こっちも 索敵殲滅の対象です。 commit logを「 ANSIfy」あたりで引っかけると大量の屍があるんじゃないかな、なぁにノロいからな。

というのもANSI/ISO-C以後に書かれたソースをわざわざK&Rに書き直すようなコストは払えないので K&Rにしか対応してないコンパイラの為にいつまでもそんなメンドイことをやってらんねーつーか。

ところが…

@ DEFUN(), DEFUN_VOID() マクロ (gcc/newlib)

前回も触れたgccの ansidecl.hやnewlibの _ansi.hではやってるんだなこれが。

#if defined (__STDC__) || defined(__cplusplus) || ...
...
#define DEFUN(name, arglist, args)	name(args)
#define DEFUN_VOID(name)		name(void)
#define AND		,
...
#else   /* Not ANSI C.  */
...
#define DEFUN(name, arglist, args)	name arglist args;
#define DEFUN_VOID(name)		name()
#define AND		;
...
#endif  /* ANSI C.  */

こんな感じで使います。

int
DEFUN(foo, (arg1, arg2),
	int arg1 AND
	char *arg2)
{
	...
}

int
DEFUN_VOID(bar)
{
	...
}

もう(可読性もクソも)ないじゃん...

なぜここまでしてK&Rとのソース互換をとることに執心するのか、実例知りたいですな。

@ 次回予告

今回ぜんぜんsys/cdefs.hの話じゃなかったな。

なんかあんまり読んで誰も得しない(書いて俺も仕事しろ)な記事な気がしてきましたが、空気読まずにまだまだK&Rの話は続く予定。

*1:ほーん、お前の口からそれ出るのか。
*2:ただし varargs.h から stdarg.hと可変個引数への移行が絡むところでは事前定義マクロ__STDC__を使って宣言しわけたりはしてます、 これ

2013/11/15(Fri)

[Unix C][*BSD][や我N1] sys/cdefs.hとは何ですか? (その4)

その3ではK&RとANSI/ISO-Cでの関数定義の仮引数宣言について学びました。

さてNet/2で導入された時点でのsys/cdefs.hのマクロの残りをざっと片づけてしまいましょう。

@ __CONCAT() マクロ

これはANSI/ISO-Cで導入されたCプリプロセッサの「トークン結合演算子(6.10.3.3 ##演算子)」をラップ(LL Cool J感)するものです。

今時なのかアナクロなのかようわからんですが、Cでパッケージとか名前空間みたいな事をするのに

#define COMPAT(name)	compat_##name

struct COMPAT(data) {
	...
};

int
COMPAT(data_init)(struct COMPAT(data) *data)
{
	...
}

こんなマクロ書いたりしますやね、これを展開すると

struct compat_data {
	...
};

int
compat_data_init(struct compat_data *data)
{
	...
}

となりますが、これをK&RとANSI/ISO-Cどちらでもプリプロセス可能にするため

#define COMPAT(name)	__CONCAT(compat_,name)

と書くわけです。

実際にコメント読んでみると__CONCAT()マクロの使用例として

 The __CONCAT macro is used to concatenate parts of symbol names, e.g.
 with "#define OLD(foo) __CONCAT(old,foo)", OLD(foo) produces oldfoo.

とあるので後方互換の為に古い実装を残す時なんかに使用することを想定していたっぽいですな。

__CONCAT()マクロの実装はANSI/ISO-Cの場合は

#define __CONCAT(x,y)   x ## y

です。

K&Rの場合は若干苦し紛れですが

#define __CONCAT(x,y)   x/**/y

となっています。

ですのでマクロの引数にスペース入れないでくれとコメントに書いてありますな。

The __CONCAT macro is a bit tricky -- make sure you don't put spaces in between its arguments.

@ __STRING() マクロ

これもANSI/ISO-Cで導入されたCプリプロセッサの「文字列化演算子(6.10.3.2 #演算子)」をラップ(Snoop Doggy Dog感)するものです。

よくある使い方としては、デバッグ文とかエラーログに変数名を埋め込むとかですな。

#define ASSERT_NOT_NULL(arg)				\
do {							\
	if (arg == NULL)				\
		errx("%s is null.", #arg);		\
} while (/*CONSTCOND*/0)

int
foo(const char arg1, const char *arg2)
{
	ASSERT_NOT_NULL(arg1);
	ASSERT_NOT_NULL(arg2);
}

これを__STRING()マクロに置き換えると

#define ASSERT_NOT_NULL(arg)				\
do {							\
	if (arg == NULL)				\
		errx("%s is null.", __STRING(arg));	\
} while (/*CONSTCOND*/0)

ってだけっす。

__STRING()マクロの実装はANSI/ISO-Cの場合は

#define __STRING(x)     #x

です。

K&Rの場合は単なるダブルクオートで

#define __STRING(x)     "x"

となっています。

@ __CONCAT(), __STRING() マクロの現状

これも所詮K&RとANSI/ISO-Cの双方向ソース互換の為のマクロですやね。前回までに説明したように*BSDではANSI/ISO-Cに対応しないコンパイラなんぞサポートしないことに決めて、__P()マクロやK&R式関数定義の仮引数定義を消して回ってます。

ですのでこいつらも同様に一気に消しちゃっても問題ないと思うんですが、なぜかそういう動きはないですな。不思議。

@ __BEGIN_DECLS, __END_DECLS マクロ

デフォルトコンパイラがgccになったのでC++にも対応したことで、標準Cライブラリのリンケージに関数宣言に

extern "C" {
...
};

なんかが必要になるのですが、当然こいつらCコンパイラは理解してくれません、ですので

#if defined(__cplusplus)
#define __BEGIN_DECLS   extern "C" {
#define __END_DECLS     };
#else
#define __BEGIN_DECLS
#define __END_DECLS
#endif

というマクロを導入しました。

まぁC++03では #include <stdio.h> → #include <cstdio>に改められて別ファイルになったわけですが、まぁ黒歴史ですな。 ただし現在でもC++98ソース互換を保たないと #include <stdio.h> する旧C++會もいるのでこれは消せません、ちーん。

んでこのマクロなんですが/usr/includeで公開するヘッダ以外で使う必要はほぼ無いです。 ですのでlibc内部で必要なだけのこういう ヘッダの関数宣言には使ってません。

ですが必要もないのに使っちゃってるとこもありますやね、 こことか。

src/lib/libc/citrus以下はは元々独立したライブラリにする予定だったので使ってますけどね。

@ const, inline, signed, volatile キーワード

こいつらもANSI/ISO-Cで導入されたキーワードなので、K&Rの場合はではざっくりマクロで消し去ります。

#ifdef __GNUC__
#define const           __const         /* GCC: ANSI C with -traditional */
#define inline          __inline
#define signed          __signed
#define volatile        __volatile

#else   /* !__GNUC__ */
#define const                           /* delete ANSI C keywords */
#define inline
#define signed
#define volatile
#endif  /* !__GNUC__ */

つーか4.4BSDの時点では対応してるコンパイラが無いので常に消してますな、gccだけは試験実装(__ではじまるキーワード)があるので それに置き換えています。

ちなみに現在では逆にgccの__ではじまるキーワードの方をANSI/ISO-Cのキーワードに置換してますな。

#define	__const		const		/* define reserved names to standard */
#define	__signed	signed
#define	__volatile	volatile
#if defined(__cplusplus) || defined(__PCC__)
#define	__inline	inline		/* convert to C++/C99 keyword */
#else
#if !defined(__GNUC__) && !defined(__lint__)
#define	__inline			/* delete GCC keyword */
#endif /* !__GNUC__  && !__lint__ */
#endif /* !__cplusplus */

__inlineだけはgccの場合そのまま喰わせてます。

@ 次回予告

おっしこれでNet/2での導入当初のsys/cdefs.hに実装されてたマクロの話がすべて終わったので、後は如何にその後の*BSDで「無秩序に」機能追加されたかの話だぜ!

2013/11/18(Mon)

[Unix C][*BSD][絶許、訴訟] sys/cdefs.hとは何ですか? (その5)

4.3BSD Net/2で「K&RとANSI/ISO-Cのポータビリティレイヤ」として初めて導入されたsys/cdefs.hですが、前回触れた ANSI/ISO-Cのキーワード周り

というifdefが入ってしまったことで、その後の*BSDにおいて「Cコンパイラの実装の差異を吸収する」役割に変化します。

@ 4.3BSD Net/2 → 4.4BSD(聖域化)

Cコンパイラをpccからgccに置き換えたNet/2をベースに、フリーでは 386BSDそして商用では BSD/386が生まれるわけですが

ここちょっと消えてますね、44番という選手がいたんですけど 。

行方不明だったBSD選手(背番号44)は ライトにコンバートされて発見されますが この時点では sys/cdefs.hには更にgcc独自機能のラッパー(Dr. Dre感)マクロが追加されています。

/*
 * GCC has extensions for declaring functions as `pure' (always returns
 * the same value given the same inputs, i.e., has no external state and
 * no side effects) and `dead' (nonreturning).  These mainly affect
 * optimization and warnings.  Unfortunately, GCC complains if these are
 * used under strict ANSI mode (`gcc -ansi -pedantic'), hence we need to
 * define them only if compiling without this.
 */
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define __dead __volatile
#define __pure __const
#else
#define __dead
#define __pure
#endif

@ __dead マクロ

以下の例のように、関数内でexit(3)やabort(3)などを呼ぶ事で決して元の関数に戻らないケースは多々あります。

#include <stdlib.h>

void
suicide(void)
{
	exit(EXIT_FAILURE);
}

int
main(void)
{
	suicide();
}

このコードをコンパイルしようとするとgccは警告を出します *1

$ gcc -Wall test.c
test.c: In function 'main':
test.c:13:1: warning: warning: control reaches end of non-void function

プログラミング言語C 戻り値を返さず死亡、というシナキーンいかんでしょ(9800点)なミスはありがちです。

Cコンパイラにpccを使用していた頃はこのようなミスを発見する 静的コード解析ツールはlint(1) *2コマンドの出番でした(もちろん今でも残っていますが)。

$ lint test.c
test.c:
...
test.c(13): warning: function main falls off bottom without returning value [217]
...
Lint pass2:

しかしプログラムにおいてmain関数には戻らずイベントハンドラで終了というのはよくあることです *3。ですのでこの警告が煩わしい場合が多々あります。

lint(1)の場合はコメントにNOTREACHEDコマンドを埋め込むことでこの警告を抑制可能です。

int
main(void)
{
	suicide();
	/*NOTREACHED*/
}

ところがgccは当然ですがこのlint(1)のコマンドを認識しません、gccでこの警告を抑制するには

extern void suicide(void) __attribute__((noreturn));

のように返らない関数に対してノータリンじゃなくてnoreturn属性を指定する必要があります。 この属性(__attribute__)というのはgcc独自機能です。

古いgcc(2.5以前)ではnoreturn属性は存在しなかったので、以下のようにvolatileとして宣言する必要がありました。

extern void volatile suicide(void);

今のvolatile型修飾子(最適化抑止)とは意味合いが違うことに注意、ですので今時のコンパイラではエラーになります。 gcc.infoには

    The `volatile' keyword tells the compiler to assume that `fatal'
cannot return.  This makes slightly better code, but more importantly
it helps avoid spurious warnings of uninitialized variables.

    It does not make sense for a `volatile' function to have a return
type other than `void'.

と書いてありますやな。

実際の使用例としてはinfoに「fatal」とある通り、 err.hあたりの致命的エラーでメッセージ吐いて死ぬ関数とかですな。

__dead void	err __P((int, const char *, ...));
__dead void	verr __P((int, const char *, _BSD_VA_LIST_));
__dead void	errx __P((int, const char *, ...));
__dead void	verrx __P((int, const char *, _BSD_VA_LIST_));

現在の実装は、gcc2.5以下ならvolatileキーワード、それ以上ならnoreturn属性使っとりますやね。

#if __GNUC_PREREQ__(2, 5)
#define __dead          __attribute__((__noreturn__))
#elif defined(__GNUC__)
#define __dead          __volatile
#else
#define __dead
#endif

@ 次回予告

今回書ききれんかった__pureマクロについて、そして gcc(1) と lint(1) の危うい関係についてお話します。

*1:ちなgcc4.5.3なのでエラーメッセージは当時と違うと思われ、環境作るのめんどいわー。
*2:CentOSとかCygwinをお使いであればsplintをどうぞ
*3:GUIプログラミングなんかだと例えばXtAppMainLoop(3)とかですね。

2013/11/19(Tue)

[Unix C][*BSD][な特雑コ] sys/cdefs.hとは何ですか? (その6)

昨日の記事に引き続き4.4BSD-Liteで入った変更です。

@ __pure マクロ

__pureマクロはその関数に副作用が無い事を保証する属性です、ガタッと音をたてて椅子から立ち上がった関数型言語ラーの皆様、こちらではありませんのでお座り下さい。

@ 副作用とは?

あんま説明するまでもないですが、副作用ありの例

int value = 0;

int
foo()
{
	return value++;
}

int
main(void)
{
	printf("%d\n", foo());
}

関数fooを呼び出した結果グローバル変数valueが書き換わりました、実行前と実行後でこいつの値が変わった事=副作用です。

勘違いしやすいですが、グローバル変数を参照するだけなら副作用はありません *1

int value = 0;

int
bar()
{
	return value + 1;
}

int
main(void)
{
	printf("%d\n", bar());
}

関数bar()には副作用はありません(まだよわい)。

最もわかりやすい例としては、引数だけ参照するケースとかすな。

int
buzz(int value)
{
	return value + 1;
}

int
main(void)
{
	int value = 0;
	printf("%d\n", buzz(value));
}

関数buzzには副作用はありません(つよい)。

何も参照しない完全勝利した淫夢君UCのガッツポーズはスルーして、と。

@ 「副作用なし」をコンパイラが知る事のメリット

副作用なしだとコンパイラに教えてあげると何かいいことしてくれるの?というと

  • コンパイラで最適化してくれるかも(ループ最適化など)
  • 並列プログラミング…あっそこの関数型言語に一言あるお客様、お静かにお願いいたします

みたいな感じ、後者はこの時点ではまだコンパイラでどーこーはアレだったと思われる。

@ __pure マクロの実装

現在のgccでは昨日のnoreturnと同様に、__attribute__の機能を使ってconstあるいはpure属性を指定します。

extern int bar() __attribute__((pure));
extern int buzz(int) __attribute__((const));

const属性は厳格で引数のみを参照の場合、pureはもうちょい緩くて引数とグローバル変数を参照の場合です。

古いgcc(2.5以前)ではnoreturn属性は存在しなかったので、以下のようにconstとして宣言する必要がありました。

extern int const bar(int);

これも昨日のvolatile同様、今のconst型修飾子(変更不可)とは意味合いが異なるので今時のgccではエラーに。

   Many functions do not examine any values except their arguments, and
have no effects except the return value.  Such a function can be subject
to common subexpression elimination and loop optimization just as an
arithmetic operator would be.  These functions should be declared
`const'.  For example,

     extern int const square ();

says that the hypothetical function `square' is safe to call fewer
times than the program says.

すでにこんな機能constキーワードでなく#pragma使えやとdissられてますな。

   Some people object to this feature, suggesting that ANSI C's
`#pragma' should be used instead.

それに対して#pragmaじゃアカンという反論がありますが割愛、またC11の話する時に__attribute__とか__declspecの話交えて書くかも。

__pureマクロの実際の 使用例はこちら。

__pure   int abs(int);
...
__pure long
         labs(long);

現在の実装はこんな感じ、const属性は2.5から、pure属性は2.96なのでこのifdefです。

#if __GNUC_PREREQ__(2, 96)
#define	__pure		__attribute__((__pure__))
#elif defined(__GNUC__)
#define	__pure		__const
#else
#define	__pure
#endif

@ lint(1)の闇

ここでひとつ オスシコンパイラ業界のえらい人の誤解を解いておくと「この時点(pcc時代)」だけでなく「現在(gcc/clang時代)も」lint(1)は*BSDのビルドシステムの中で使われています。

例えばNetBSDのmake(1)に付属する ドキュメントにも書いてありますが

 LINT		C program verifier.  [lint]

 LINTFLAGS	Options to ${LINT}.  [-chapbxzFS]

という変数が定義され、またMakefileターゲットとして

	lint:
		run lint on the source files

が用意されて(かつallターゲットに含まれる)、ビルド時にはソースコードチェックが必ず実行されます。

ですのでNetBSDの コーディングスタイルガイドには

  • lint(1)の警告にひっかかるようなコードがある
  • → だがしかしそれは意図したコーディングである場合
  • → コメントにlintコマンド入れて消せ

という決まりになってます。

コマンドの一覧はlint(1)の マニュアル参照、このガイドで使えとあるのは以下のやつです *2

  • CONSTCOND … ifなんかの条件式が定数なのは(ifdefプリプロセッサ命令を知らないからでなく)意図的である
  • NOTREACHED … 関数の戻り値を返さないのは(書き忘れたからではなく)意図的である
  • FALLTHROUGH … switch文でbreakしてないのは(Pascalのcase文と混同してるからではなく)意図的である

実際のコード例は↓

#define MACRO(v, w, x, y)                                               \
do {                                                                    \
        v = (x) + (y);                                                  \
        w = (y) + 2;                                                    \
} while (/* CONSTCOND */ 0)

...

        while ((ch = getopt(argc, argv, "abn:")) != -1) {
                switch (ch) {           /* Indent the switch. */
                case 'a':               /* Don't indent the case. */
                        aflag = 1;
                        /* FALLTHROUGH */

		...

                default:
                        usage();
                        /* NOTREACHED */
                }

@ 元々lint(1)の開発はpccと一体だった

lint(1)はCのソースコードを処理するパーサ部はpccと同一コードベースです、 READMEにもその旨書いてありますな。

Most of lint's source files are shared with the portable
compiler: they are found in /usr/src/lib/mip.
The files here are only those which are unique to lint.

なもんでpccの時代であれば、仕様に改定があった場合pcc側のCパーサ( cgram.yとか scan.c)に手をいれるだけで OKだったんですが、pccを捨ててgccを導入した事でそうもいかなくなってしまったのよね *3

なのでgccの__attribute__((noreturn)) と lint(1)の/*NOTREACHED*/ 両方書かないとアカンようになってしまい、同じことを2度やるのが嫌いな怠惰プログラマーには許せん状況になっています。

そもそもANSI/ISO-Cで関数プロトタイプが導入されたことで、lint(1)使わなくてもコンパイラ側で引数の間違いがチェック可能になった時点で それ以外のlint(1)でやってるエラーチェックもすべてコンパイラ側に寄せて、lint(1)は引退させるべきだった気がするんだけど、後の祭りですな。

ちなみに現在のlint(1)ですがC99にすらまだ完全対応してません、マニュアルより。

     -S                C9X mode.  Currently not fully implemented.

ragge@による 新生pccにはlint(1)は含まれないし、更にこれからC11対応もあるのに誰がやるのよレベル *4。おそらく今後数年でlintを無視するための事前定義マクロ__lint__でお茶を濁しまくって可読性下がるんじゃないかな。

以下はlint(1)がC99対応してないせいで #ifdef __lint__ してる 、つか今はもうこれ大丈夫な気もするけど。

#if defined(_REENTRANT) && !defined(__lint__) /* XXX lint is busted */
#define STDEXT { ._lock = MUTEX_INITIALIZER, ._lockcond = COND_INITIALIZER }
struct __sfileext __sFext[3] = { STDEXT,
                                 STDEXT,
                                 STDEXT};
#else
struct __sfileext __sFext[3];
#endif

そしてNetBSDは Coverityの支援を受けていて、ビルドシステムとは別途に静的ソース解析も実施しているので、lint(1)要らなくね?という気がしてなりません。

まぁそうするとエラーチェックがgccベタベタになって困るってのもあるけど、どーせみんなgcc互換にするよね?(威圧)

@ 次回予告

いよいよNetBSD/FreeBSDの時代はじまるよー(社会の授業が近現代については自習みたいに、ここで連載終わりにしていいかな?)。

*1:グローバル変数1万1000個に過剰反応するプログラマさんオッスオッス。
*2:うーん本当はARGSUSEDも使わないと引っかかるんだけど、インタフェースプログラミングが必要なとこ以外は関係ないせいで漏れてるのかな…
*3:gccのCパーサを使うよう変更するっても、まだ当時はpccと同じyacc/lexで書かれてたとはいえ一から書き直しレベルの作業の上にライセンスが…
*4:NetBSDの常でそういう誰もやらない作業はcore、特にchristos氏あたりの作業になりまんな。

2013/11/20(Wed)

[Unix C][*BSD][ま君壊] sys/cdefs.hとは何ですか? (その7)

@ 前回の補足

FreeBSDでは__dead/__pureに加えてgcc2.6への変更に伴って__dead2/__pure2が 追加されています、これは

extern void __dead foo() __dead2;

extern int __pure bar(int) __pure2;

と書く事でGCC2.5以前なら

extern void volatile foo() ;

extern int const bar(int) ;

それ以降だと

extern void  foo() __attribute__((noreturn));

extern int  bar(int) __attriute__((const));

になるって寸法ですな、これはFreeBSDの実装の方が正しいね。

NetBSDだと

__dead void foo();

__pure int bar(int);

なので、GCC2.5以前なら

volatile void foo();

const int bar(int);

それ以降だと

__attribute__((noreturn)) void  foo();

__attriute__((const)) int  bar(int);

のように、あまり推奨されない場所にマクロが展開されてしまいます。

gcc2.5以下向け対応なんてスッキリザックリ消していいような気もするんですが、 こういう失敗で、いつまでも古いgccから逃げられない人用なんですかね、まぁgccでサポート切られたCPUで頑張ってる人もいるのかも。

@ まえがき

前回で 4.4BSD-Liteまでの変更履歴を追いました。今回はNetBSD1.0〜1.1で入った変更を取り上げます。

こんなチラシの裏読んでる人には常識と思われますが、NetBSDは4.4BSD-Liteからではなく4.3BSD Net/2を元にした386BSDから派生してます。

4.4BSD-Lite相当の変更を取り込んだのはUCL vs BSDi訴訟が終結してからなのでいろいろと変更が前後したりすることに注意 *1、NetBSDへの取込作業は以下の でやったという話



Branch:		magnum
Description:	?
Status:		?
Start Date:
End Date:
Base Tag:	magnum-base
Maintainer:	?
Scope:		kernel
Notes:

44→44マグナムの連想らしい、全米ライフル協会さんオッスオッス。
ただscopeがkernelとある通り、userlandについては各担当が必要最小限でしかやってないっぽいです。

まぁそのおかげでFreeBSDと違って4.4BSD Runeの 呪い互換性を気にしなくて良かったわけですがね。

@ __warn_references() マクロ

このマクロについては、以前の 記事で軽く触れましたが

  • セキュリティ上の問題がある関数を、愚かにも呼び出そうとする痴れ者に対し、最後の警告をする
  • バイナリ互換性の為に残してある関数あるいは変数であり、ユーザが直接呼出したり参照して欲しくないので警告する

場合に使われます。

@ セキュリティ上問題がある関数

産まれたこと自体が罪の関数としては

  • gets
  • mktemp
  • tempnam/tmpnam

が挙げられます。

gets(3)に関して、C11においては 既報ですが「ここちょっと消えてますね gets(3)という選手がいたんですけど」状態です。

mktemp(3)に関しては TOGの例のアレ

 APPLICATION USAGE

	Between the time a pathname is created and the file opened, it is possible for some other process to create a file with the same name.
	The mkstemp() function avoids this problem and is preferred over this function.

 FUTURE DIRECTIONS

	This function may be withdrawn in a future version.

危険性があること、将来のPOSIXでは撤回される可能性について触れられています。

また tempnam/ tmpnamについても同罪

 APPLICATION USAGE

	This function only creates filenames. It is the application's responsibility to create and remove the files.
	Between the time a pathname is created and the file is opened, it is possible for some other process to create a file with the same name. 
	Applications may find tmpfile() more useful.

まぁこっちは将来の話は出てないけど。

@ バイナリ互換性の為に残してある関数

これは 過去記事で詳しく説明したので割愛。

@ __warn_references() マクロの使い方

ユーモア欠落症患者のために、ウィキペディアの専門家たちが「 gets(3)」の項目を執筆しています。

例えばGCCでは、getsを使用するプログラムをコンパイルすると「the `gets' function is dangerous and should not be used. 
('gets' 関数は危険なため使うべきではありません。)」と警告される。

うーんこの、これはgccが警告出してるわけではなくリンカすなわちbinutilsのld(1)が出してます。実際問題Cygwinではこんなメッセージでえへんし、NetBSDでは違うメッセージです。

このメッセージ自体はglibc2のものですやね、 ソース見ると最後に

#ifdef _LIBC
link_warning (gets, "the `gets' function is dangerous and should not be used.")
#endif

とあります、このlink_warningというマクロは今回取り上げてる__warn_referencesと同じ機能を持つマクロで、こいつがld(1)に警告を出させるトリックです。

NetBSDの getsだと

__warn_references(gets, "warning: this program uses gets(), which is unsafe.")

です。

@ __warn_references() マクロの実装(現在)

objformatによって実装が異なります、ですのでsys/cdefs.hは以下のように分割されています。

  • sys/cdefs_elf.h
  • sys/cdefs_aout.h

もうnativeでa.outバイナリ作る必要もないしそもそも作れるか?問題もあるから消していいと思うんだよな、それともまだa.outなportってあるんだっけ?

FreeBSDではa.outサポートは 消されてますな。

ちなELFでの__warn_references()の実装は

#define __warn_references(sym,msg)                                      \
    __asm(".pushsection .gnu.warning." #sym "\n"                        \
          ".ascii \"" msg "\"\n"                                        \
          ".popsection");

gccはインラインアセンブラでgas(1)の疑似命令を使い、セクション .gnu.warning.<sym> に msgを文字列として埋め込んでます。objdumpで覗いてみると

$ objdump -s -j .gnu.warning.gets /lib/libc.so.12.181

/lib/libc.so.12.181:     file format elf64-x86-64

Contents of section .gnu.warning.gets:
 0000 7761726e 696e673a 20746869 73207072  warning: this pr
 0010 6f677261 6d207573 65732067 65747328  ogram uses gets(
 0020 292c2077 68696368 20697320 756e7361  ), which is unsa
 0030 66652e                               fe.

んでld(1)はコンパイルしたオブジェクトとlibcをリンクする際に、この警告を拾って表示するわけです。

@ __warn_references() マクロの実装(過去)

NetBSD1.1の時点ではobjformatはa.outだし、なぜかMIでなくMDと判定され ${MACHINEARCH}/include/cdefs.h行きになっとりますな、 .gnu.warningsでなく.stabs使って同じことをやっています。

a.out→ELFの話はまたいずれ。

@ __warn_references() の有効性

そもそもコンパイラやリンカがエラーメッセージ出しても一切気にしないのがCプログラマとして大手振ってま(以下略

ところによっては、この警告を見たくなけりゃ.gnu.warningセクション消せとかいう 猛者もいるし(白目)

@ 次回予告

微妙に飽きつつある、まだこんなどうでもいいネタ続き読みたい人いるのか。

次回「遡行」人は流れに逆らい、そして力尽きて流される。

*1:また一部のリビジョンはCVSから意図的に消されています。

2013/11/21(Thu)

[Operating System Source Code Secrets Volume 334][な阪関無] sys/cdefs.hとは何ですか? (その8)

@ 訂正記事を書けば免許証を返していただけるんですね?

4.4BSDでの変更箇所をNetBSDがいつ取り込んだのかについて、 曽田さんから おいゴラァ修正しろよ!あくしろよご指摘頂きました。

文中のcgdとは cgd(4)の事で CryptoGraphic Disk driver がしゃべった!?という超常現象です。

ではなくChris G. Demetriou <cgd AT NetBSD DOT org> 氏の事を指します。ちなみに 元coreです、そういえば長いこと姿見てない気がする。

あと Mutt開発者tamoさんからも 誤字のご指摘ありがとうございました(野獣の眼光)、やべぇよ…やべぇよ…

@ 前回まで

NetBSD1.1までの変更、__warn_referencesマクロの機能について学びました、今回はNetBSD1.1〜1.3で入った変更です。

そういえば今のNetBSDのバージョン命名規則とこの時期って違うんですが若い人って知らなかったりして説明必要ですかね、それとも若いNetBSDerとか 非実在青少年ですかそうですか。

@ __weak_references() マクロ

rev1.13で__weak_referencesという名で導入されたマクロですが、このマクロは変身する度に実装が混乱し、その変身をあと2回も俺は残している!

  • __indr_references… 単に名前が気に入らなかったっぽい
  • __weak_alias… NetBSD 1.4でobjformatがELF化された時にこっそり追加、__indr_referencesを捨てた理由はやっぱり単に名前が疑惑

過去記事 ( その1) ( その2) で__weak_alias マクロについてはあらかた説明済ですが、__weak_references/__indr_referencesも合わせてまた回を改めて説明する予定です。

@ __kprintf_attribute__() マクロ (削除済)

gccは関数にformat属性というものを付けることが出来ます、これはprintfっぽい関数のコンパイル時にフォーマット文字列をデリケートにバリデートしてくれる優しい機能です。

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>

void kprintf(const char *, ...) __attribute__((format(printf, 1, 2)));

void
kprintf(const char *a, ...)
{
        va_list ap;
        va_start(ap, a);
        vprintf(a, ap);
        va_end(ap);
}

int
main(void)
{
        kprintf("%b");
        exit(EXIT_SUCCESS);
}

ところがkprintf(9)ではprintf(3)には存在しない変換指定子「%b」をサポートしているので、こいつがエラーと判断されてしまうのでこの属性を付けられなかったのよね。 上記のコードをコンパイルすると警告がでます。

$ gcc -Wall test.c
test.c: In function 'main':
test.c:19:2: warning: unknown conversion type character 'b' in format [-Wformat=]
  kprintf("%b");
  ^

だもんげ、カッとなってgccを 改造した、今は反省している。

...
  41 /* Format kinds */
  42 #define F_USER  0x1             /* Format used in user-land printf/scanf */
  43 #define F_KERN  0x2             /* Format used in kprintf/scanf etc. */
...
  47 #define FORMAT_CONTEXT(p) (((p)->format_kind & (F_USER|F_KERN)))
...
 557             if (TREE_CODE (format_type) == IDENTIFIER_NODE
 558                 && (!strcmp (IDENTIFIER_POINTER (format_type), "printf")
 559                     || !strcmp (IDENTIFIER_POINTER (format_type),
 560                                 "__printf__")))
 561               format_kind = F_USER;
 562             else if (TREE_CODE (format_type) == IDENTIFIER_NODE
 563                      && (!strcmp (IDENTIFIER_POINTER (format_type), "kprintf")
 564                          || !strcmp (IDENTIFIER_POINTER (format_type),
 565                                      "__kprintf__")))
 566               format_kind = F_KERN;
...
 692 typedef struct {
 693   char *format_chars;
 694   int format_kind;
...
 712 } format_char_info;
 713
 714 static format_char_info print_char_table[] = {
 715   { "di",       F_USER|F_KERN,  0, T_I, T_I,    T_L,    T_LL,   T_LL,   "-wp0 +"        },
 716   { "oxX",      F_USER|F_KERN,  0, T_UI,T_UI,   T_UL,   T_ULL,  T_ULL,  "-wp0#"         },
 717   { "u",        F_USER|F_KERN,  0, T_UI,T_UI,   T_UL,   T_ULL,  T_ULL,  "-wp0"          },
...
 727   { "n",        F_USER,         1, T_I, T_S,    T_L,    T_LL,   NULL,   ""              },
 728 /* Kernel bitmap formatting */
 729   { "b",        F_KERN,         1, T_C, NULL,   NULL,   NULL,   NULL,   ""              },
...
1046           else if ((FORMAT_CONTEXT(info) & F_KERN) != 0)
...

564行目で__attribute__((format))の引数にkprintf/__kprintf__を追加しています。

んで

  • 引数にprintfが指定されてる場合はF_USER
  • 引数にkprintf属性ならF_KERN

フラグをセットしとります。

んで714行目フォーマット文字列中の変換指定子のチェック用定義テーブルprint_char_tableで

  • format(printf)で許可される変換指定子ならF_USER
  • format(kprintf)で許可される変換指定子ならF_KERN

↑ビットを立ててとります。

最後に1046行目でformat_kindとprint_char_table.format_kindを比較しF_KERNビットが立ってれば、kprintf用のチェックを実施します。

前述の%bには729行目でF_KERNだけが立ってるので、kprintf(9)でのみサポートされとるちゅーことですね。

ちゅーことでさっきのコードはこう書き換えることで%bについてもgccはエラーチェックしてくれるようになったと。

void kprintf(const char *, ...) __attribute__((format(kprintf, 1, 2)));

@ 現在の kprintf(9)

現在は %b に関しては bitmask_snprintf(9)を使って文字列に変換したうえで %s 使えとなっとりますので、userlandと同様の__attribute__((format(printf, m, n)))が使えます、よってこのマクロも改造も過去の話です。

ちなみにkprintf(9)の マニュアルにbitmask_snprintf(9)のマニュアル見ろとおもいっくそありますが、そんなものは無い模様。

the typical NetBSD style we did not document/advertise it.

平常運転です。

つーか今調べたらbitmask_snprintf(9)もobsoleteで、libutilの snprintb(3)使えじゃねーか。

あと余談ですがtech-kernelあたりではtime_tをprintf(9)で表示しようにも適切な変換指定子無いよね、って文句が時々出てきます。userlandであれば strftime(3)で文字列に変換できるわけですが、src/sys/kernelにはstrfrime(9)は無いからのう。

現時点では

	time_t time;

	printf("%"PRIdMAX"\n", (intmax_t)time);

で逃げるかですが、libsaの場合 subr_prf.cがLIBSA_PRINTF_LONGLONG_SUPPORT抜きだったりするとアカンですやな。

@ 次回予告

2013/11/22(Fri)

[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その9)

@ まえがき

飽きた、 引き続きNetBSD1.3までに追加されたマクロです。

@ _C_LABEL()マクロ

昔の環境のlibcにおいてシンボルは全てアンスコが付いてました。テニスの下着の方でないアンダースコアすなわち「_」です。

NetBSD 1.3.3だとこんな感じ。

$ uname -sr
NetBSD 1.3.3

$ nm /usr/lib/libc.so.12.20 | grep vfprintf
0004adb4 T _vfprintf

なぜこんなことやってんのかちゅーと、みんな大好き Linker & Loadersの「1.6 リンクの実例」に

大域シンボルの先頭にはそれぞれアンダースコアが付加されている。この理由については第5章で説明する

と書かれてます。

んでんでんで、興奮を抑えきれずに 淫夢5章をめくると「5.3.1 CとFortranの単純な名前の変形」にすっげーわかりにくい説明があります、ザックリ要約すると

  • 元々 Unix のライブラリはアセンブラで書かれて動いていた
  • そこに後から C や Fortran で書かれたライブラリが誕生した
  • これら3つをチャンポンで使うと大域シンボルが衝突して宇宙ヤバイ
  • OS/360とか今となっては誰も知らん環境について熱く語ってるのが余計なんだよなぁ…

ということです、カレー臭くさい(確信)。

@ name mangling の導入

そんでこの大域シンボル衝突を回避するためにどうしたらいいか、と頭悩ませた結果

そうだ、名前をまんぐり返そう(真剣)

という事になったわけですやね。

ルールとしては

  • アセンブラではそのまんまん foo → foo
  • Cでは前にアンスコ付けて foo → _foo
  • Fortranでは前後にアンスコ付けて foo → _foo_

というようになっとります、C++のネームマンドリルより簡単ですな。

@ _C_LABEL() マクロの使用例

アンダースコア付への変換はCコンパイラが勝手にやるので通常は意識しません。ただしアセンブラソースや淫乱アセンブラからCの大域シンボル参照する時とかには手マンする必要があります。

例えば こ↑こ↓の2nd stage boot loaderのコードですが

…
	movzbl	%dl, %edx
	push	%ebp			/* high 32 bits of first sector */
	push	%ebx			/* first sector of bios partition */
	push	%edx			/* bios disk */
	call	boot2			/* C bootstrap code */
…

boot2 は C ABI なのでアンスコ付けてやらんとならんわけですが

	call	_boot2			/* C bootstrap code */

わざわざ手マンせず _C_LABEL() マグロを使って↓と書くわけですな

	call	_C_LABEL(boot2)		/* C bootstrap code */

つーかタイプ量10倍になっとるじゃんとお思いでしょうが

  • 将来的にアンダースコアつける以外のまんぐり返しが必要になった場合もコード書き直し不要
  • Cの名前空間であることが、ソース読む人間に一目瞭然である
  • エディタの検索機能でのひっかけ易くなる

ちゅー意味があるわけでして。

@ 加藤良三、ライブラリインタフェースをCに統一

Unixは移植性の為にプログラミング言語Cで書き直されることになります *14th Editionあたりでその作業はほぼ完了し、アセンブラで書かれたライブラリ絶滅しました。もはやアセンブラとそれ以外を区別するための前アンスコは不要になったのです。

そうなると前アンスコ撲滅したくなるのが人情、特にシンボル名8文字制限(当時)のうち常に1文字アンスコに奪われるのは嫌だもんげ。しかしながら実行するには

  • Cコンパイラを修正してアンスコを付けないようにする
  • その上すべてのライブラリを再コンパイル

ちゅー難題が待っております、これ現実的には無理なんで長らく放置されることになります。

@ 成し遂げたぜ。

まずCコンパイラの修正は gcc に -fleading-underscore/-fno-leading-underscore というスイッチが実装されname mangle有り無しを切り替え可能になりました。

あとはライブラリを全て再コンパイルするタイミングなのですが、これはもっと時代が後になります。バイナリフォーマットをa.outからELFへスイッチするタイミングであれば互換性は全部切り捨てられるわけで、それが選ばれました。

ちなみにELF化されたNetBSD 1.5.3だとこんな感じ。

$ uname -sr
NetBSD 1.5.3

$ nm /usr/lib/libc.so.12.62.1 | grep vfprintf
00041ef0 T vfprintf

前アンスコが無くなったのがお分かり頂けますでしょうか?

現在のコードはこんな感じ

#ifdef __LEADING_UNDERSCORE
#define	_C_LABEL(x)	__CONCAT(_,x)
#else
#define	_C_LABEL(x)	x
#endif

-fleading-underscoreの場合は前アンスコあり、-fno-leading-underscoreの場合はなしで_C_LABEL()は展開されます。

Fortranも前アンスコは不要になりました、ただし後アンスコア健在なはず。詳しくはそこら歩いてる数学屋をとっ捕まえて聞いてください。

@ 次回予告

ヒッ

*1:そのことで なぜ localtime(3) の引数は time_t のポインタなのか?という不条理も生まれましたが、気にしたら負け。

2013/11/24(Sun)

[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その10)

@ バージョン管理いまむかし

突然ですが、ちょっと寄り道してソースコードのバージョン管理について。

このチラシの裏ネタをリアルタイマーではない *1ワイが書けるのも、NetBSD をはじめとしてプロジェクトの開始時よりソースコードをバージョン管理してくれてるからです。

ソフトウェアの世界でソースの履歴管理には SCM(Source Code Management) というツールを使用しますが、リポジトリをどこに置くかで大別して

  • 中央集権、クライアントサーバ(Client/Server)型
  • 分散(Distributed)型

の2種類に分類できます、それぞれメリット・デメリットがあるのでご自分のプロジェクトに合わせてお好きなものをどうぞ。

@ 中央集権型

いまどきの中央集権型は Subversionの一強ですが、過去には

とかいろいろなものが使われていました。

ヤバイなこれ、並べたらゲシュタルト崩壊してきた。これ平仮名だったら

  • ねぬぬね … Source Code Control System
  • ぬねねぬ … Compatibly Stupid Source Control
  • はぬね … the Revision Control System
  • けはぬね … the Project Revision Control System
  • ぬすね … ConVenience Store
  • すぬぬ … Visual Source Safe

じゃねーか、ラテン語圏狂ってるな。

@ 分散型

分散型は21世紀入った後くらいからオープンソースな実装が雨後の筍の如くでしたが、今や

の二強であらかた趨勢は決まりましたな、ドベカスこと

あたりはもう永遠に知らんでいいと思います、Subversion と互換性のある SVK には期待してたんだけどね(ゲッソリ)。

なお、BCリーグには Fossil以下どんだけお前ら車輪再発明するのかと。

@ *BSDにおけるソースコード管理

NetBSD や OpenBSD では前述の CVS がバージョン管理ツールとして使われています。

ソースコードの先頭には「$NetBSD$」あるいは「$OpenBSD$」ではじまるメタデータが埋め込まれています。

/*	$NetBSD: init_main.c,v 1.453 2013/08/28 12:50:18 riastradh Exp $	*/

これは別に手書きでやってるわけではなく、CVSの キーワード置換という機能で実現しています。

/*	$NetBSD$	*/

とキーワード(前後を$でクオート)をソース中埋め込んでおくと、CVSからソースを取り出す(checkout)時にリポジトリの情報を元に自動的にメタデータに置換されるわけです。

このように埋め込まれたメタデータはふつー「RCSID」と呼ばれます。なぜ CVS なのに "RCS"ID というかちゅーとそもそも CVS は元々 RCS の上位互換として作られらから。

このキーワード置換機能も RCS 時代から存在してるのですよな、 マニュアル

$ man co

で表示されます、さあ大きな声で復唱しましょう、まんこ *2

 KEYWORD SUBSTITUTION
       Strings of the form $keyword$ and $keyword:...$ embedded in the text
       are replaced with strings of the form $keyword:value$ where keyword and
       value are pairs listed below.  Keywords can be embedded in literal
       strings or comments to identify a revision.
...

へいへーい、CVS と同じキーワード置換がサポートされてますな。

ちなみに元の仕様には $NetBSD$ というキーワードはありません。同等のキーワードは「$Id$」になるのですが、*BSD ではソースコードの貸し借りとかforkによる派生が多いので

/*	$OpenBSD: cdefs.h,v 1.34 2012/08/14 20:11:37 matthew Exp $	*/
/*	$NetBSD: cdefs.h,v 1.16 1996/04/03 20:46:39 christos Exp $	*/

のように異なるリポジトリにおけるメタデータを保持したいケースがあるので、リポジトリの設定ファイルである CVSROOT/config に

tag=NetBSD

と指定してやると $Id$ の代わりに $NetBSD$ を置換するようわざわざ CVS 側を改造しとります *3

そして FreeBSD、かつては CVS *4を使っていましたが、今は Subversion に移行しています。

Subversion もデフォルトでは有効でないのですが、 キーワード置換の機能を持っています、ですので事情は NetBSD/OpenBSD と一緒ですちゅーことになります、 ソース

__FBSDID("$FreeBSD: head/sys/kern/init_main.c 255708 2013-09-19 18:53:42Z jhb $");

こっちはキーワード任意に指定できるので改造は不要ですが。

そしてRCSには ident(1)というコマンドが含まれています、これは引数に与えられたファイルから「$keyword$:〜」というパターンを全て検出し、標準出力に表示するものです。

$ ident /usr/src/sys/kern/init_main.c
/usr/src/sys/kern/init_main.c:
     $NetBSD: init_main.c,v 1.441.2.3 2013/03/14 16:33:10 riz Exp $
     $NetBSD: init_main.c,v 1.441.2.3 2013/03/14 16:33:10 riz Exp $

FreeBSDはこの事忘れて一度はRCSを 削除しますが、案の定 炎上して 復活させとりますな *5

@ UCB/CSRG(University of California, Berkeley/Computer Systems Research Group)におけるソースコード管理

386BSDより以前、バークレー時代についてのバージョン情報はライセンス文の下くらいにあります、 こ↑こ↓

 *	@(#)init_main.c	8.16 (Berkeley) 5/14/95

この「@(#)」ではじまる部分は SCCS での キーワード置換によって埋め込まれたメタデータです。

 *	%A%

とソースに入れておくとRCS同様、リポジトリの情報を元に自動的にメタデータに置換されるわけです。これをSCCSIDと一般的には呼びます。

またRCSのident(1)と同様のコマンド what(1)というものがありまして、こいつも引数に与えられたファイルから「@(#)〜」というパターンを全て検出し、標準出力に表示するものです。

$ what /usr/src/sys/kern/init_main.c
/usr/src/sys/kern/init_main.c
        init_main.c     8.16 (Berkeley) 5/14/95
        init_main.c     8.16 (Berkeley) 5/14/95

what(1)は元々SCCSが不自由なソフト(本虫感)であった為、BSDLで書き直されています。今ではオリジナルの実装もSCCSがOpenSolarisの一部として公開されたことにより The Heirloom Development Toolsの一つとして入手可能です。勿論SCCS互換のCSSCにも含まれます。

@ Linux kernelにおけるソースコード管理

オマケ。

最近の若者には中央集権型はえらい嫌われとります、その元凶はとってもお若い精神年齢をお持ちである Linus Torvalds氏ですな、彼いわく( 映像)( テキスト)

  • 俺はCVSを憎悪している
     I hate it with a passion.
    
  • (ベターCVSを目指す限り)Subversionは産まれる前から無意味だった
     I see Subversion as being the most pointless project ever started.
    
  • マジでアンタがCVSが好きなら、居るべき場所は今こ↑こ↓じゃねーよ、精神病院にでも行ってどうぞ
     if you actually like using CVS, you shouldn't be here. You should be in some mental institution, somewhere else.
    

うーんこのぐう畜生発言、ぜひソースをzipで固めて管理してるデスマ量産SIerの現場 *6で同じセリフが言えるか訊いてみたいものです 。

アッハイ、本当にLinusって最初の10年間tarballとpatchで管理してたの(絶望)。

For the first 10 years of kernel maintenance, we literally used tarballs and patches, which is a much superior source control management system than CVS is.

この人の話は長くなるだけなのでもうおしまい、グッバイ BitKeeperフォーエバー BitKeeper

@ 次回予告

10回も書いてまだNetBSD 1.3すら終わらないのか(白目)

次回はこのバージョン管理のお話の知識を前提に

  • __IDSTRING
  • __RCSID/__KERNEL_RCSID/__FBSDID
  • __SCCSID/__KERNEL_SCCSID
  • __COPYRIGHT/__KERNEL_COPYRIGHT

マクロを説明します。

*1:若者なので Linux なんて Linux Japanで初めて知ったわー、FreeBSD なんて 徹底入門で初めて知ったわー、NetBSD なんてUNIXUSERの付録から入れたわー、にわかだわー
*2:酒の席でくっそえげつない下ネタ披露する人妻SEでも「ちょっとさすがに抵抗があります」だそうです。
*3:ちなRCSの方は改造されてないので、これらのキーワードは認識しません。
*4:周辺プロジェクトでは Perforce という商用のSCMを使ってました
*5:ちなみにNetBSD では security.conf(5)のバックエンドに使われてたりもしてて、うちも削除しようか?の 提案即NAKされてたり、/etcの下とか/root/.* 飛ばしたときは/var/backupの下見ると幸せになれるかもよ。
*6:他にもVisualSourceSafeにチェックインする前にタイムスタンプをtouch(1)的なもので変更して 年=major 月=minor 日時分=revision のようにバージョン番号として扱う謎ルールで、ふつーの感覚でファイルいじってチェックインするとリブ管が血相変えて飛んでくるとかあったな(白目)

2013/11/26(Tue)

[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その11)

@ 前回まで

前回RCSに含まれる ident(1)SCCSwhat(1)で、ソースコードからバージョン履歴メタデータを抽出する方法を解説しました。

@ バージョン履歴メタデータはいたるところに

RCSIDやSCCSIDってのは埋められるのは別にCのソースファイルだけでなく、etc以下の設定ファイルなんかにも入ってます。

$ head -n 5 /etc/rc.conf
#       $NetBSD: rc.conf,v 1.96 2000/10/14 17:01:29 wiz Exp $
#
# see rc.conf(5) for more information.
#
# Use program=YES to enable program, NO to disable it. program_flags are

もちろんシェルスクリプトにも。

$ head -n 5 /etc/rc.d/sshd
#!/bin/sh
#
# $NetBSD: sshd,v 1.21 2011/07/25 03:04:23 christos Exp $
#

つーか kernel やlibc そして実行ファイルなどバイナリにすら埋め込まれてます。

$ ident /netbsd | head -n 5
/netbsd:
     $NetBSD: GENERIC,v 1.348.2.6 2012/08/15 15:32:59 sborrill Exp $
     $Revision: 1.348.2.6 $
     $NetBSD: std.amd64,v 1.7 2008/12/11 05:42:18 alc Exp $
     $NetBSD: std,v 1.14 2011/11/22 21:25:42 tls Exp $

$ ident /lib/libc.so.12.181  | head -n 5
/lib/libc.so.12.181:
     $NetBSD: crti.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
     $NetBSD: crtbegin.S,v 1.2 2010/11/30 18:37:59 joerg Exp $
     $NetBSD: yperr_string.c,v 1.7 2005/11/29 03:12:01 christos Exp $
     $NetBSD: yp_master.c,v 1.13 2003/12/10 12:06:25 agc Exp $

$ ident /bin/ksh | head -n 5
/bin/ksh:
     $NetBSD: crt0.S,v 1.3 2011/07/01 02:59:05 joerg Exp $
     $NetBSD: crt0-common.c,v 1.7 2011/06/30 20:07:35 matt Exp $
     $NetBSD: crti.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
     $NetBSD: crtbegin.S,v 1.2 2010/11/30 18:37:59 joerg Exp $

このメタデータは主にバグ報告と現象の再現に、そして原因の特定に役に立ちます、例えば NetBSD の バグレポートには

Environment (output of "uname -a" on the problem machine):

という欄がありますが

  • 報告対象がリリース版である
  • カーネル及びユーザランドがすべてヴァニラ(ゲリラ、スリラ、連れてってマニラ*1)な配布物である

なら uname(1) の結果だけでどのソースがどのバージョンか自明なんですが、CVS HEAD なんかだと変更が激しいわけで、バグを特定するのにこれらの情報を送りつけてやると解決が早いです(バグが直るとは言っていない)。

@ すごいな、(バイナリにメタデータ埋め込むの)どうやったんだ?

これはindent(1)のマニュアルに「コンパイル済バイナリにもメタデータ埋め込もう(提案)」ちゅーコード例が書いてあります。

       ident works on text files as well as object files and dumps.  For exam-
       ple, if the C program in f.c contains

              #include <stdio.h>
              static char const rcsid[] =
                "$Id: f.c,v  $";
              int main() { return printf("%s\n", rcsid) == EOF; }

@ そんなのlint(1)が許さない

しかしですね、↑の例だとrcsidをprintf(3)の引数にしてるから問題ないのですが、ふつーこんなメタデータはプログラムで使い道はないわけで。

どこでも使われないままだと その6で解説した lint(1) が、この 変数rcsid はどこでも使われてないっすねと警告を出します。

$ cat >test.c
static char const rcsid[] = "$Id$";

int
main()
{
        return 0;
}
^D
$ lint test.c
test.c:
test.c(1): warning: static variable rcsid unused [226]
Lint pass2:
$

観葉植物静的ソース解析ツールくんは全てを見ていた!

しゃーないので

#if defined(lint)
static char const rcsid[] = "$Id$";
#endif /*!lint*/

とlint(1)除けでもしておくしかないっすね、これはマニュアルにもそう注意があります。

       If a C program defines a string like rcsid above but does not  use  it,
       lint(1)  may  complain,  and  some  C  compilers will optimize away the
       string.  The most reliable solution is to  have  the  program  use  the
       rcsid string, as shown in the example above

ただしこれでも最適化かけると消えてなくなる問題はどーにもなりません、実際 gcc -O しただけでメタデータ消えてしまいますな。

$ cat >test.c
#include <sys/cdefs.h>
static const char *rcsid = "$MyId: test.c,v $";
int
main()
{
	return 0;
}
^D

[最適化なし]
$ gcc a.c
$ ident a.out | grep MyId
     $MyId: test.c,v $

[最適化あり]
$ gcc -O a.c
$ ident a.out | grep MyId | wc -l
       0

まぁこんなもんです。

@ バークレー時代の SCCSID の宣言方法

この頃は lint(1) 除けだけしか対策してません。

例えば これCライブラリ関数のabort(3)のコードですが、indent(1)のマニュアル例と同様に *2バイナリにメタデータを埋め込んでます。

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)abort.c	8.1 (Berkeley) 6/4/93";
#endif /* LIBC_SCCS and not lint */

同様に こっちはシステムコールのbrk(2)のコードで、GNU asの.asciz疑似命令でメタデータ埋めとります。

#if defined(SYSLIBC_SCCS) && !defined(lint)
	.asciz "@(#)brk.s	8.1 (Berkeley) 6/4/93"
#endif /* SYSLIBC_SCCS and not lint */

んでLIBC_SCCS/SYSLIBC_SCCS マクロは Makefile 中でON/OFFしてます、デフォルトはONです。

# All library objects contain sccsid strings by default; they may be
# excluded as a space-saving measure.  To produce a library that does
# not contain these strings, delete -DLIBC_SCCS and -DSYSLIBC_SCCS
# from CFLAGS below.  To remove these strings from just the system call
# stubs, remove just -DSYSLIBC_SCCS from CFLAGS.
LIB=c
CFLAGS+=-DLIBC_SCCS -DSYSLIBC_SCCS

これはバイナリサイズ減らしたいから余計なメタデータ要らねーって時にセットするわけです。

@ NetBSDでのメタデータ宣言方法

前回も説明した通り、NetBSDはバージョン管理ツールにCVSを採用したので 新スタイル導入しました。

#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char *sccsid = "from: @(#)abort.c	5.11 (Berkeley) 2/23/91";
#else
static char *rcsid = "$NetBSD: abort.c,v 1.6 1995/12/28 08:51:57 thorpej Exp $";
#endif
#endif /* LIBC_SCCS and not lint */

ただsccsidをif 0して、rcsidを追加しただけなんですけどね。

そして毎回毎回

static const char *rcsid = "$NetBSD$";

と書くのがめんどくさくなったのでマクロを導入します。

@ __IDSTRING(), __RCSID() マクロ

もうほんとただマクロにしただけ、 これ

#define __IDSTRING(name,string) \
	static const char name[] __attribute__((__unused__)) = string

#ifndef __RCSID
#define __RCSID(s) __IDSTRING(rcsid,s)
#endif

gccの未使用変数警告を消すために__attribute__((unused))を追加したくらいしか何もありません、単純にタイプ量が減っただけです。

またこの実装だとヘッダファイルにメタデータを埋め込むことができません。

#ifndef HOGE_H_
#define HOGE_H_

#include <sys/cdefs.h>
__RCSID("$NetBSD: hoge.h,v $");

#include <stdio.h>

static __inline void
hello()
{
        puts("hello");
}
#endif /*HOGE_H*/

とヘッダ用のRCSIDを宣言し

#include <sys/cdefs.h>
__RCSID("$NetBSD: fuga.c,v $");
# include "hoge.h"

int
main(void)
{

        hello();
        return 0;
}

さらにCソースでRCSIDを宣言するとstatic const char *rcsidの重複定義になってしまいます。

$ gcc fuga.c
In file included from fuga.c:3:0:
hoge.h:4:20: error: redefinition of 'rcsid'
fuga.c:2:20: note: previous definition of 'rcsid' was here
$

まだよわいクソザコナメクジ、オランウータンの餌レベル。

@ __SECTIONSTRING() マクロ

しかし現在ではこの「コンパイラの最適化によるメタデータ消滅」を回避するまで改良されています、それは__SECTIONSTRING()マクロ導入で実現されなした(つよい)。

#define	__SECTIONSTRING(_sec, _str)					\
	__asm(".pushsection " #_sec "\n"				\
	      ".asciz \"" _str "\"\n"					\
	      ".popsection")

#define	__IDSTRING(_n,_s)		__SECTIONSTRING(.ident,_s)

#define	__RCSID(_s)			__IDSTRING(rcsid,_s)

さっきも出てきた GNU as の .asciz 疑似命令を使用してインラインアセンブラでオブジェクトの中に文字列を配置してやるようにしたわけです。 これならpass1での最適化で消されることは無いですやね。

そしてさっきのサンプル、ヘッダとCソースで別々に__RCSID()を宣言しても

$ gcc fuga.c

$ ident a.out
a.out:
     $NetBSD: crt0.S,v 1.3 2011/07/01 02:59:05 joerg Exp $
     $NetBSD: crt0-common.c,v 1.7 2011/06/30 20:07:35 matt Exp $
     $NetBSD: crti.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
     $NetBSD: crtbegin.S,v 1.2 2010/11/30 18:37:59 joerg Exp $
     $NetBSD: fuga.c,v $
     $NetBSD: hoge.h,v $
     $NetBSD: crtend.S,v 1.1 2010/08/07 18:01:34 joerg Exp $
     $NetBSD: crtn.S,v 1.1 2010/08/07 18:01:35 joerg Exp $

コンパイルは成功し、どっちのメタデータも保持できてますな。

@ FreeBSD では __RCSID() ではなく __FBSDID() マクロを使う

こちらはFreeBSDで導入された__RCSID()マクロの高機能版です。

#if defined(__GNUC__) || defined(__INTEL_COMPILER)
#define __IDSTRING(name,string) __asm__(".ident\t\"" string "\"")
#else
/*
 * The following definition might not work well if used in header files,
 * but it should be better than nothing.  If you want a "do nothing"
 * version, then it should generate some harmless declaration, such as:
 *    #define __IDSTRING(name,string)   struct __hack
 */
#define __IDSTRING(name,string) static const char name[] __unused = string
#endif
	
/*
 * Embed the rcs id of a source file in the resulting library.  Note that in
 * more recent ELF binutils, we use .ident allowing the ID to be stripped.
 * Usage:
 *      __FBSDID("$FreeBSD$");
 */
#ifndef __FBSDID
#if !defined(lint) && !defined(STRIP_FBSDID)
#define __FBSDID(s)     __IDSTRING(__CONCAT(__rcsid_,__LINE__),s)
#else
#define __FBSDID(s)     struct __hack
#endif
#endif

やってることは一緒ですが、gccでない環境を考慮して

static const char __rcsid__行番号[] = "$FreeBSD: メタデータ $";

という行番号を変数名に含めるトリックを入れることで、さっきのrcsid重複エラーを回避してるわけです。

正直そこまでやる必要あるんですかね…

@ OpenBSDでは

OpenBSDではこの手のバイナリへのメタデータ埋め込みは 止めました

RCSID()は死ぬべき by theo

こわい(失禁)、ヤクルト宮本の引退試合に集まった PL組事務所なみの恐怖を感じます。

ですので

$ ident /bsd
ident warning: no id keywords in /bsd

ちゅーこと。

openbsd-miscでRCSID削除について議論残ってないかなと思って探したのですが、横浜の先発ピッチャーと同様 見つかりませんでした

ただしソースコードや設定ファイルそしてスクリプトには継続して残しているので完全否定っていうわけでもないでしょう。

私が思いつく理由についいては

  • そもそもバイナリのサイズ的に大いなる無駄である
  • OpenBSD は NetBSD が __RCSID() を実装する前にforkしているので、__RCSID()でコンパイル止まる→要らん消せという思想
  • こんなに知らないとBSD屋さんにはなれない」的なソースの「おまじない」を少しでも減らしたい(つーかtheoは大のマクロ嫌いなのよね…)
  • 悪意のあるプログラムが脆弱性のあるバージョンで攻撃に使えるかのチェックにお手軽に使えちゃうじゃん

あたりですかね、あくまで推測ですが。最後のはそれならスクリプトも消すべきなので違うよな…

んでident(1)が使えないことの代償つーか開き直りつーかで、OpenBSDへのバグ報告はリリース配布物で起きたものでないとまず無視されます。 まぁ 元々お約束事が多いので、なかなかバグ報告するのも敷居が高い(釣り用語)のですが。

リリース使うやつは開発者だ、開発版を使う奴は良く訓練された開発者だ、ホントOSSは地獄だぜ!

つーてもコミュニティの人数考えると必要な割り切りですやね。

@ 突然ですが lint(1) の闇 (リプライズ)

おまけ。

前にlint(1)の説明をした時に書くのを忘れてましたが、lint(1)は以下の事前定義マクロをセットします。

  • __LINT__
  • lint
  • __lint
  • __lint__

これらのマクロはあくまでlint(1)の為だけのものなので

とか、コンパイル通すために-D__lint__とか#define __lint__ 1 とか勝手に定義してはいけません。

前も書いた通りlint(1)は

  • C99に完全対応してない
  • C11への対応は誰やんのよの世界
  • そもそもエラーとか警告についてgccとの互換性が低い

という世界なので「lint(1)を騙さなきゃ(義務感)」というチート行為がまかり通ってます。とーぜん騙したコードはチェックを無理矢理通すためだけのもの *3なので正常には動きません。

そういう意味でもlint(1)撲滅してーなと思うんですがねぇ…

@ 次回予告

パトラッシュ、僕はもう疲れたよ

*1:ドレ、アレ、ソレ、ソウ! だからCIAに言えよ!さっさと出てけってな!
*2:ただしここでは RCS でなく SCCS ですな。
*3:SIerいた頃は実装の終わってないとことかバグの残ってるとことか無理矢理「コメントアウトしてテスト通しました進捗100%です」とか言いだすピーばっかで、モックとかスタブそういうチャチなもんじゃねぇ、もっと恐ろしいものの片鱗を味わう日々だったなぁ…

2013/11/27(Wed)

[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その12)

@ 前回まで

続きです(あらすじになってないがもう書くのめんどくさい)。

@ __SCCSID() マクロ

前回までに UCB/CSRGが SCCS を使ってバージョン管理を行い、ソースとバイナリにバージョン履歴メタデータを埋め込んでたことを説明しました。

んでNetBSDでは __RCSID()の導入と同じ理由、つまり

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)abort.c	8.1 (Berkeley) 6/4/93";
#endif /* LIBC_SCCS and not lint */

を書くのがめんどくさいから

#include <sys/cdefs.h>
__SCCSID("@(#)abort.c	8.1 (Berkeley) 6/4/93");

とマクロで書けるようにしようと、表題のマクロを導入したわけです。

…しましたが、実際にソースコードで使用してるところは 淫夢皆無です。

やるやる詐欺 コミットメッセージ

add __SCCSID() and __SCCSID2() macros for userland, and __KERNEL_SCCSID() for
the kernel.  They're not used yet, but will be shortly.

この時点ではすでに UCB/CSRG は解散しており、ソースコードリポジトリを継承したのはおそらく、開発者だった魔球先生こと Marshall Kirk McKusick先生らが関わる直系の子孫、BSD/OSですがそっちはプロプラエタリでソースは公開されていないわけで。

もし NetBSD と BSD/OS でソースをシェアできる状況だったのであれば、NetBSD の sys/cdefs.h では

#define __RCSID(_s)	__IDSTRING(.ident, _s)
#define __SCCSID(_s)

一方 BSD/OS の sys/cdefs.h では

#define __RCSID(_s)
#define __SCCSID(_s)	__IDSTRING(.ident, _s)

とすることでどっちの履歴をバイナリに埋めるかを選択できたわけなんだけどね、獲らぬ狸のなんとやら。

しかも__SCCSID2()まで導入してるんだけど、これ全く説明が無いし書いた本人 *1にしか理由は説明できなさげ、もしかして 3rd Party全てに__SCCSID[0-9]+()するつもりだったんだろうか…

というわけで、とっとと消せばいいと思います(他人事)。

@ BSD/OSの最期

余談ですが、BSD/OS のコードの諸権利を有する BSDi社 *2はその後 WindRiver社に買収され、BSD/OSのコードをFreeBSDにフィードバックするような宣言もしてたのですが、結局 BSD/OSの開発中止だかんね、ビジネスは厳しいね。

そのWindRiver社も最近 Intel社に買収されましたな、Intel社にはここは太っ腹でBSD/OSのコードをリポジトリごとジャンジャンバリバリ大開放して頂きたく *3

ちなみに BSDi社自体は iXsystems社と名前を変えまだ生き残ってるし、魔球先生は FreeBSD の開発者やったり 2013年ベイスターズで1勝してますが。

@ __COPYRIGHT() マクロ

Cソースには皆さん見慣れた著作権者表示がファイルの先頭にありますな、例えばこれ。

$ head -n 5 /usr/src/bin/kill/kill.c
/* $NetBSD: kill.c,v 1.27 2011/08/29 14:51:18 joerg Exp $ */

/*
 * Copyright (c) 1988, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *

そして実はバイナリにもこの著作権者表示は埋め込まれてます。

#include <sys/cdefs.h>
#if !defined(lint) && !defined(SHELL)
__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
 The Regents of the University of California.  All rights reserved.");
#endif /* not lint */

これも実装は__RCSID()みたいにバイナリに文字列を埋め込むマクロで、異なるのは

  • __COPYRIGHT() は .copyright セクション
  • __RCSID(), __SCCSID() は .ident セクション

に埋め込むかの違いだけで、実装はどっちも前回説明した __IDSTRING()及び __SECTIONSTRING()マクロです。

4.4BSDの頃の実装は ここにある通り

#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1991, 1993\n\
	The Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

となってて、SCCSID/RCSIDと同様に最適化で消えちゃうってのも同じです。

注意深い人は NetBSD と 4.4BSD のソースコード見比べて気づくかと思われますが、どっちも先頭「@(#)」ではじまってる SCCS の what(1) でしか扱えない形式で、RCS ident(1)では拾えないことに気づきました?

$ uname -sr
NetBSD 6.1_STABLE

$ what kill
/bin/kill
         Copyright (c) 1988, 1993, 1994 The Regents of the University of California.  All rights reserved.

$ ident /bin/kill | grep Copyright | wc -l
       0

はい what(1) では著作権表示は表示できるんですが ident(1) では出ません。

4.4BSDからNetBSDになって、バージョン管理がSCCSからCVSになったことは前回説明しましたが、ここだけはSCCSのまま放置されてるわけで直した方がいいと思うんだけどね。

そもそもSCCSもRCSもキーワード置換機能に著作権表示があるわけでなくて、これ単純にwhat(1)で引っかけれるよう4.4BSDの頃は「%Z%」キーワード入れただけだと思う。

static char copyright[] =
"%Z% Copyright (c) 1991, 1993\n\
	The Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

というわけで別にSCCSでないとダメってわけじゃないんで、綺麗な解決策としては copyright(1) とか作ればいいんじゃないかな(適当)、単純にELFから.copyrightを読むツールなんて簡単に作れるわけで。

@ BSDライセンスの第2項と __COPYRIGHT() マクロ

そもそもBSDLにはバイナリフォームの場合からも著作権者情報を復元できないとアカン条項ありますやね。

 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.

なのでこの著作権者情報のメタデータ部分をソースやバイナリから削除したりするとライセンス違反です。

ただし全てのファイルの著作権者情報を出してるわけではないです、binやusr.binなどのアプリケーションについては通常 main 文のあるファイルのみで宣言してます。

/*
 * Source code revision control identifiers appear after any copyright
 * text.  Use the appropriate macros from <sys/cdefs.h>.  Usually only one
 * source file per program contains a __COPYRIGHT() section.
 * Historic Berkeley code may also have an __SCCSID() section.
 * Only one instance of each of these macros can occur in each file.
 * Don't use newlines in the identifiers.
 */
#include <sys/cdefs.h>
__COPYRIGHT("@(#) Copyright (c) 2008\
 The NetBSD Foundation, inc. All rights reserved.");

コーディングガイドにもそう書いてありますな。

コーディングガイド的には libc なんかの場合は __COPYRIGHT() は入れないっぽいのですが、なぜか hcreate.cでは宣言されてて、まるで(hcreate.cの作者である) cgd@ ひとりが libc の著作権者かのような状態でワロタ。

$ what /lib/libc.so.12.181
/lib/libc.so.12.181
         Copyright (c) 2001 Christopher G. Demetriou.  All rights reserved.

そもそもそれすら入れてないくせにBSDLなアプリだって山ほどあります。

$ objdump -s -j .copyright /usr/bin/iconv

/usr/bin/iconv:     file format elf64-x86-64

objdump: section '.copyright' mentioned in a -j option, but not found in any input file

はい、Citrus関係のやつ入れ忘れとりますな。つーかこれはコーディングガイドに書いてないのと 新井が悪い

@ もしかして無い場合は自分で入れないとライセンス違反?

んでここでひとつ疑問がわきます、最初からこの__COPYRIGHT()マクロで著作権者情報埋め込んでないものを利用する場合、さっきの

Redistributions in binary form must reproduce the above copyright notice

を厳密に解釈すると、自分で埋め込んどかないとライセンスに抵触するような気がしてきますね、はい。ワイ専門家じゃないので法律的な判断はできかねますがこれどうなんですかね(チラッチラッ。

ワイの心情的にはコードに入れ忘れた権利者が悪いので、あくまで最初に書いた通りソースの__COPYRIGHT()の部分を消したり実行ファイルから.copyrightセクション削ったりしなければいいんじゃね?と思うし、そもそも1-clause BSDLでバイナリ条項削っても問題ないような気がするんだけど。

@ 次回予告

また今回も__KERNEL_RCSID(), __KERNEL_SCCSID(), __KERNEL_COPYRIGHT() マクロ残ってしまったので、その説明と他未定。

*1:explorer@だけど近頃見ないなと思ったらresignedだった、辞める人多いねー(棒)。
*2:Berkeley Software Design, Inc.なので、馬が乗馬的表現ですな
*3:スラドによくいる葬式乞食並みの感想、たぶんWinampネタ来週くらいにスレ建ちそう)

2013/11/28(Thu)

[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その13)

@ 前回まで

そりゃ*BSDみたいな世界で3人しか興味ない話で長文書くより、Linusが中指立てた話をちょこっと書く方がウケるよね

笛吹おじさん「おっ、次は俺の出番かな」

一つだけ笛吹おじさんネタをしておくと、POSIXの名付け親はストールマンなんだよね(ほんとにどうでもいい) *1

@ __KERNEL_RCSID(), __KERNEL_SCCSID(), __KERNEL_COPYRIGHT() マクロ

こいつはNetBSD-1.4で導入されました。えーっと今1.1〜1.3までの変更を取り上げてるんですが 現在の実装は、前回までに説明した__RCSID(), __SCCSID(), __COPYRIGHT() マクロと実装は identicalなので先に説明しときます。

128 #define	__KERNEL_RCSID(_n, _s)		__RCSID(_s)
129 #define	__KERNEL_SCCSID(_n, _s)
130 #define	__KERNEL_COPYRIGHT(_n, _s)	__COPYRIGHT(_s)

__KERNEL_({R,SC}CSID|COPYRIGHT)()はそのまんま__({R,SC}CSID|COPYRIGHT)()を呼んでるだけです。

まぁ名前からして __KERNEL_* は kernel つまり src/sys の下で使うんだろうなというとこは判りますが、マクロの第一引数 _n が使われることなく捨てられてます、ナンデ!?ニンジャナンデ!?

これはですね、前々回 __RCSID()の説明でしたrcsidが多重定義エラーになる問題と関連しています。

これの解決法として、FreeBSDでは __FBSID()マクロの中で、事前定義マクロ __LINE__ *2を使うことで

static const char __rcsid_行番号[] = "$FreeBSD: メタデータ $";

とマクロを展開させて、シンボルに行番号を含めることで重複を避けてたのも前々回説明しました。

まーFreeBSDも力技やなとは思うんですが、NetBSDはただのアホでした。

さっきの謎の引数、_n に自分で番号入れてシンボルの重複を避けてたわけだ。

#ifndef HOGE_H_
#define HOGE_H_

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hoge.h,v $");

#include <stdio.h>

static __inline void
hello()
{
	puts("hello");
}
#endif /*HOGE_H*/

インクルードされる側の _n = 0

#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: fuga.c,v $");
# include "hoge.h"

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

する側の _n = 1 にしておけば

static const char rcsid1 = "$NetBSD: hoge.h,v $";

static const char rcsid0 = "$NetBSD: fuga.c,v $";

みたいにマクロ展開されて重複避けられるわけですが、この番号をワザワザ手動ふってくとかもうねアボガド…

…わかった この話はやめよう ハイ!! やめやめ、__pure2/__dead2 マクロに続いて __KERNEL_* も FreeBSD と比べて クソザコナメクジな実装だったね(ゲッソリ)。

ということでもはや__KERNEL_({R,SC}CSID|COPYRIGHT)() には存在理由ないので消せばいいと思うよ。そもそも src/commonを導入して、kernelとuserlandのコードを共有してるんだし

#include <sys/cdefs.h>
#if !defined(_KERNEL) && !defined(_STANDALONE)
__RCSID("$NetBSD: ... $");
#else
__KERNEL_RCSID(0, "$NetBSD: ... $");
#endif

とかいちいちifdef増やして「汚いコードだなアッー」ってのは止めましょうよ。

@ __RENAME() マクロ

これも過去の チラシの裏で詳しく説明していますので割愛しますね。

ちなみに最近のNetBSDでは nonaka@さんのおかげで ELF symbol versioningをサポートするようになりましたので、将来的には __RENAME() マクロも deprecated になるはずです。

…ですがもう2年経っても libc の build system がそれに対応する兆候すらないわけで。まぁ スローロリスの歩みのようなNetBSD時間なので、JR横浜駅工事とどっちが早く完成するかは知らん。

どーでもいいこと補足しておくなら__RENAME()の実装は

#ifdef __lint__
#define __RENAME(a)    __symbolrename(a)
#else

lint(1)の場合、__RENAME(a) → __symbolrename(a)にしてますが、これは別にそういう名前の関数があるのではなくscan.lの中でCの キーワード扱いになってます。

/*
 * Keywords.
 * During initialisation they are written to the symbol table.
 */
static	struct	kwtab {
	const	char *kw_name;	/* keyword */
	int	kw_token;	/* token returned by yylex() */
	scl_t	kw_scl;		/* storage class if kw_token T_SCLASS */
	tspec_t	kw_tspec;	/* type spec. if kw_token T_TYPE or T_SOU */
	tqual_t	kw_tqual;	/* type qual. fi kw_token T_QUAL */
	u_int	kw_stdc : 1;	/* STDC keyword */
	u_int	kw_gcc : 1;	/* GCC keyword */
} kwtab[] = {
...
	{ "__symbolrename", T_SYMBOLRENAME, 0,	0,	0,	  0, 0 },
...
};

__ではじまる実装依存のキーワードとはいえ、これまでコンパイラやツールの違いを吸収する役目だったsys/cdefs.hのはずなのに、 逆に今度はsys/cdefs.hが定義するマクロの為にツール側を改造するってもう支離滅裂だよね、ugly hack以上の何物でもない。

立浪「(NetBSDの)そういうとこがあかんねん(ボコー」

@ そもそもsys/cdefs.hとは何ですか?がコロコロ変わるんですが…

すでに4.3BSD Net/2 から NetBSD1.3 になるまでに

  • K&R vs ANSI/ISO-C の文法の差を吸収するラッパー
  • → pcc vs gcc のコンパイラの差を吸収するラッパー
  • → sccs vs rcs/cvs のソースコード管理の差を吸収するラッパー
  • → ABI(アプリケーションバイナリインタフェース)において後方互換性を維持するトリックを提供する

のように節操なく役割が増やされています。 おかしい、こんなことは許されない

最初にも書いたようにsys/cdefs.hには移植性はありません、ですのでここに機能を増やしてくのはデザインとして下の下ゲゲゲゲッワイ(加齢臭)下策なんだけどねぇ…

なもんで

  • すでに時代的に意味が失われ不要になってるマクロはさっさと消して身軽になる
  • せめてバージョン管理の話だけでも別のヘッダにした方が…
  • ABI周りもなぁ…

という感じ。

@ 次回予告

NetBSD 1.3までの話が終わったので、次回はどこまでにしましょうかねぇ。zzz

*1:最初は「IEEEIX」というニンジャな名前だったらしい、ショッギョ・ムッジョ
*2:LINEとかカカオと無縁のオッサンどもオッスオッス

2013/11/29(Fri)

[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その14)

@ 前回まで

人生 Abend Calendar 2013 14日目です。

@ 【補足】BSDL第2項と__COPYRIGHT()について【触手】

ワイのFUD(ゲス顔)が総スルーされる…だと…?と涙目になってたところなので、ツッコみありがとうございます、ありがとうございます( ベビクイワシのキックちゃんに蹴られた時のような声で)。

いま商用でBSDLの成果物を使ってる企業は全部これですやね、つまり Redistributionsから復元できる(binary formから復元できるとは言っていない)という解釈です。

OSG-JPの訳もその解釈よりさらに意図的に超訳されてます。

2. バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作権表示、本条件一覧、および下記免責条項を含めること。

原文の「must reproduce(=再現可能であること)」がそもそも添付文書にそぐわない文言だし、what(1) でバイナリから著作権者表示が出力可能になってる実装を考えると、オリジナルの意図はどう考えてもバイナリ埋込だと思うんですよね。 そもそもこの訳って「copyright notice(=著作権者表示)」が「conditions(=利用条件)、disclaimer(=免責事項)」と等価列挙になってますが、原文はいったん前者と後者で文章切れてるし。

JNUGの日本語訳も「再現」をとても苦しい使い方しております。

2. バイナリー形式の再配布においては、上記の著作権表示、この条件の列挙、
     下記の注意書きを、配布物に附属した文書および/または他のものに再現
     させること。

ワイが訳するならこう?

2. バイナリ形式での再配布すんならこの著作権表示を(バイナリから)再生可能にしとかんとあかんのやで、それと
利用条件と免責事項の一覧もドキュメントか何かの形式で配布物に添付しとくんやで。

ただ現実問題さっきのOSG-JPの超訳通りの運用でいいんじゃねーのと。当時は

  • 著作権者 = カリフォルニア大学 → バイナリに含める
  • 貢献者 = CSRGの開発者、外部からのパッチ提供者 → バイナリに含めない

という 1:N の単純な図式で、今みたいに著作権者だけで 延々とスクロールしなきゃならないような人数ではなかったわけで、元々あのライセンスは将来的に世界中で真似されることなんかこれっぽっちも考えてないですし *1

実際に OpenBSD では copyright in binary form 消してますやね。はえーすっごい。

ちなみに NetBSD ライセンスの テンプレも基本的には 著作権者:貢献者 を 1:N のに保つよう

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by 
 *

著作権者は TNF で、コード作者は TNF にコードを寄贈することになってます。

といってもこれは別に強制ではないし、いまや 3rd Partyだらけでもう「Berkeley Software Distribution」ではなく「Borrowed Software Distribution」なので 従う理由もありませんが *2

つーことで第2項はもっと現代的に書き直されるべきなんですが、ライセンスの厳密性考える人は GPLv334 とか CDDDDDDDDDDDL とかで遊んでるのだろう。

結論: __COPYRIGHT(), __KERNEL_COPYRIGHT() も消してしまおう(提案) *3

@ NetBSD 1.3 → 1.4

さて前回でやっと1.3までの説明終わったんで1.4へなんだけど、実は新しい機能はこのリリースではありません。

_C_LABEL()マクロの説明でも軽く触れましたが、binutilsがa.out形式のサポートを終了するのでELF形式への切替作業が開始されます。当初はsys/cdefs.hの中で

#ifdef __ELF__
# define _C_LABEL(x)	x
#else
# define _C_LABEL(x)	__CONCAT(_,x)
#endif

ifdefされてたのですが、可読性の及び将来的にa.outサポートを消す時の手間の為にヘッダは分割され

  1. machine/cdefs.h … 機種依存のマクロ
  2. sys/cdefs_elf.h … ELF形式を扱う〃
    #define	_C_LABEL(x)	x
    
  3. sys/cdefs_aout.h … a.out形式を扱う〃
    #define	_C_LABEL(x)	__CONCAT(_,x)
    
  4. sys/cdefs.h … 機種非依存〃

という構成になりました *4。とはいっても使用する側は sys/cdefs.h だけをincludeすればOKです(1〜3はマクロが自動で判定して必要なものをincludeしてくれます)、 ここ

#include <machine/cdefs.h>
#ifdef __ELF__
#include <sys/cdefs_elf.h>
#else
#include <sys/cdefs_aout.h>
#endif

NetBSD では MD/MI といって機種依存部(Machine Dependent)/機種非依存部(Machine Independent)のコードを分離しますが、こういうobjformatの違いも同様に分離することでコードの見通しを良くするわけです。

@ 次回予告

いよいよNetBSD-1.5でELF化されるわけですが、a.out vs ELFの話ができたらなーと。

*1:ライセンスの本来の意図としては「著作権表示を削って(あたかも自分のもののように振る舞って)はならない」なので、BSDLは悪文なんだよな、再生できるようにしたきゃ自分で puts(copyright) でもしとけという話
*2:ワイも Copyright は Citrus にcontribしたコード以外は、自分の名前でNにつっこんどります。
*3:init_main()で出す TNFの著作権表示だけで十分ですよ。
*4:execve(2) が読む込むための a.out形式 あるいは ELF形式のフォーマット(構造体)情報は別途 sys/exec_{elf,aout}.h にあります、そっちの話は関係ないので省略