The Man Who Fell From The Wrong Side Of The Sky:2010年9月分

2010/9/2(Thu)

[C99] 小ネタ

唐突ですが、C99で導入された「構造体中の 0 長配列メンバ」ってーのは
長さが不定の場合、配列ではなくポインタで宣言し動的に確保せなあかんので

struct foo {
	int x;
	char *y;
	^^^^^^^^
};
struct foo *
foo_new(size_t size)
{
	struct foo *f;
	f = malloc(sizeof(*f));
	^^^^^^^^^^^^^^^^^^^^^^^
	if (f == NULL)
		return NULL;
	f->y = malloc(size);
	^^^^^^^^^^^^^^^^^^^^
	if (f->y == NULL) {
		free(f);
		return NULL;
	}
	return f;
}
void
foo_delete(struct foo *f)
{
	free(f->y);
	free(f);
}

ちう具合に2回も malloc/free しなきゃならないのが面倒/高コストだからと

struct foo {
	int x;
	char y[1];
	^^^^^^^^^^
};
struct foo *
foo_new(size_t size)
{
	struct foo *f;
	f = malloc(sizeof(*f) + size - 1);
	^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	if (f == NULL)
		return NULL;
}
void
foo_delete(struct foo *f)
{
	free(f);
}

ちゅーカリカリチューニングがナウなヤングにフィーバーしたのが由来なのですよな。

しかしまぁ人間の欲望とは尽きないもので「size - 1 すら書きたくないでござる」という一派が
C では元々 length zero の object は禁止という大原則をぐいっと曲げて

struct foo {
	int x;
	char y[];
	^^^^^^^^^
};
struct foo *
foo_new(size_t size)
{
	struct foo *f;
	f = malloc(sizeof(*f) + size);
	^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	if (f == NULL)
		return NULL;
}
void
foo_delete(struct foo *f)
{
	free(f);
}

と、構造体の最後のメンバ変数だけはサイズを省略することで
サイズを0として宣言できるちゅー文法を導入したのですよな。

でもさーこれ、トラブル生むケースが無いわけではないのよね。

struct foo {
	int x;
	char y[];
};
struct foo *
foo_new(size_t size)
{
	struct foo *f;
	f = malloc(sizeof(*f) + size);
	if (f == NULL)
		return NULL;
}
void
foo_delete(struct foo *f)
{
	free(f);
}

void
main(void)
{
	struct foo *f;
	f = foo_new(SIZE_MAX);
	assert(f == NULL);
}

このコードぱっと見だと、foo_new() の引数に SIZE_MAX *1ちう
確実に malloc(3) が失敗するであろうサイズを指定してるので、foo_new()の戻りは
NULL だと想定していますが、それは甘いスイーツ(笑)

malloc(sizeof(*f) + SIZE_MAX);

どう見ても integer overflow です、本当にありが(ry
つまり malloc(sizeof(*f)-1) になりますので foo_new() は予想に反して成功してしまいま。
おおこわいこわい。

ちうことで

struct foo *
foo_new(size_t size)
{
	struct foo *f;
	size_t alloc;

	if (SIZE_MAX - size < sizeof(*f)) {
		errno = ENOMEM;
		return NULL;
	}
	f = malloc(sizeof(*f) + size);
	if (f == NULL)
		return NULL;
}

という integer overflow 対策じうやうです。
ぱっとパラノイア〜ぱっとパラノイア〜

まぁそもそも今時のマシンでこんなチューニングすんなって話だよなーと。

*1:要は malloc_usable_size() 以上のサイズ、Nの場合この関数欲しいねーゆうとフルボッコ(ry

2010/9/7(Tue)

[NetBSD] funopen(3) / open_wmemstream(3)

以前紹介した funopen(3) ですが seekfn って要するに fseek(3) の中の人なんだけども
これ第2引数が fpos_t なのはちょっとイケてないよな。

FILE *
funopen(void *cookie, int (*readfn)(void *, char *, int),
    int (*writefn)(void *, const char *, int),
    fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *));

fpos_t ってそもそも実装依存の opaque object なんだよね。
NetBSD だと off_t と identical だけど、実装によっては構造体だったりする。
例えば glibc2 なんかは

typedef struct
{
  __off_t __pos;
  __mbstate_t __state;
} _G_fpos_t;

となってますな。まぁ fpos_t が mbstate_t 持つ実装ってどうなんよとは思うけど、これは後述。

よって funopen(3) を使う実装ってのは、seekfn 内で fpos_t を扱うところは
OS dependendent(そもそも *BSD にしかないけど) かつ machine dependent になるのよね(fpos_t は MD)。
一例をあげるに、 さっきのページのサンプルコードだと libz による gzip ファイル操作を
通常の FILE 経由の read/write で隠ぺいしてるんだけど

fpos_t
seekfn(void *cookie, fpos_t offset, int whence)
{
	gzFile *p = (gzFile *)cookie;
	return (fpos_t)gzseek(p, (z_off_t)offset, whence);
}

本来 opaque であるべき fpos_t を無理矢理 z_off_t にキャストしてるけど
厳密に言えばこれはアウト、でも他にやりようが無いので困っちゃうよなーと。

一方同様に移植性はないけど、glibc2 の fopencookie(3) はその点マシで、

#define _G_off64_t        __off_t
...
#define _IO_off64_t _G_off64_t
...
typedef int __io_seek_fn (void *__cookie, _IO_off64_t *__pos, int __w);
...
typedef __io_seek_fn cookie_seek_function_t;

と off64_t という public type なので、fpos_t のように問題にはなりません。

おまけ。
さっき glibc2 は fpos_t の実装は off_t と mbstate_t なフィールドを持つ構造体だと書いたけど
なぜそんな実装になっとるの?という話。

これは glibc2 では open_wmemstream(3) なんかの実装用にワイド文字専用インタフェースを
持ってるからなんですよな。

/* Extra data for wide character streams.  */
struct _IO_wide_data
{
  wchar_t *_IO_read_ptr;        /* Current read pointer */
  wchar_t *_IO_read_end;        /* End of get area. */
...

fwide(3) 的は stream すなわち FILE はバイト指向とワイド文字指向どちらかのモードを持つので
そのへんパラノイアックに前者は open_memstream(3) 後者は open_wmemstream(3) として
実装したよんということだと思うんだけど、これ N でもやろうとすると影響箇所が多杉で死ねる。

そもそも通常のファイルってのはバイトなわけでして、ワイド文字なファイルというものは存在せんので

が正しい実装だったと思うんだけどねぇ、まーでもすでに POSIX:2008 に open_wmemstream(3) も
入ってしまってるので南無阿弥陀仏。

まぁ open_wmemstream(3) の仕様では FILEにワイド文字用インタフェース設けずとも
fflush(3) あるいは fclose(3) のタイミングでmbsrtowcs(3) で変換するでもいいのだけど
N の場合 fflush(3) が呼ばれたことを知るためのフックが用意されていないので
writefn が呼ばれる度に mbsnrtowcs(3) で変換せざるをえずとても効率が悪いのね。
頭が痛いお。

(追記) これワイド文字専用インタフェースの問題じゃないです、 訂正記事読んでちょ。

[NetBSD] ATF(Automated Testing Framework) 義務化

だそうです

そもそも チュートリアルが間違ってるという NetBSD クオリティ。
テストケースのテンプレに以下の間違いがありまする、これ釣りなのかな。

とか。

まぁATF独自のマクロだらけでテストの生産性が上がるどころか下がってるので
htdocsのdocbook化のせいでドキュメント直す人が減ったのと同じ結末になる予感。
正直早いとこYA(Yet Another)ATF作ろうぜという感じ。
少なくとも libc のテストのような OS 非依存のテストケースは plain test の方が好ましい。
とりあえず今回は以下のようなムリヤリな ifdef 書いて他のOSでも動くようにしたけど。

#if defined(__NetBSD__)
#include <atf-c.h>
#else
#include <assert.h>
#include <stdio.h>
#define ATF_TC(arg0)            static void arg0##_head(void)
#define ATF_TC_HEAD(arg0, arg1) static void arg0##_head()
#define atf_tc_set_md_var(arg0, arg1, ...) do { \
        printf(__VA_ARGS__);                    \
        puts("");                               \
} while (/*CONSTCOND*/0)
#define ATF_TC_BODY(arg0, arg1) static void arg0##_body()
#define ATF_CHECK(arg0)         assert(arg0)
#define ATF_TP_ADD_TCS(arg0)    int main(void)
#define ATF_TP_ADD_TC(arg0, arg1) arg1##_head(); arg1##_body()
#define atf_no_error()          0
#endif

YAATFを考えるならテストケースは JUnit みたいに

void
testFoo()
{
    ...
}
void
testBar()
{
    ...
}
int
main(void)
{
    testFoo();
    testBar();
}

と prefix 決めうちの plain testにして、runner は gcc -fPIE と dlopen(3)で
みたいな感じじゃだめなのかなーと思ったり。

2010/9/8(Wed)

[pcc] ほんやく!

@ その2

これの続きですよ。
前回に増していい加減な訳ですがクレームは/dev/nullへ。

NODE構造体
  コンパイラの基本はツリー構造を使えちゅうことです、これはノード(NODE)という構造で表現されます。
  このノードは二項(binary)、単項(unary)あるいは末端(leaf)であって、それぞれ異なる型を表現します。
  全てのノードは演算子(op)あるいは型(type)で構成されとります。

  ノードは一致するものがあれば命令(instruction)に変換されます、この処理は
  テーブルを検索することで行います。ノードからの出力は常にレジスタとみなされますが
  入力ノードは蟻ノード(?)とみなされるかもしれません。

  以下に一覧として中間ノードタイプをまとめました。フロントエンドはこれ以外のノードタイプをも
  追加する方法を選ぶかもしれません。表にしたのは(ツリー)構造の各ノード種別に使用される要素です。
 こいつらがフロントエンド(つまりpass1)かバックエンド(pass2)いずれかだけでしか有効でないなら
 名前の後ろに(1)か(2)と書き加えてあります。

注意事項:
	n_op(演算子), n_type(型) そして n_qual(修飾子)はすべてのノードタイプに有効です。
	n_name(ノード名称)は決してぬるぽにはなりません、使用しない場合は "" になるとです。
	n_lval(左辺値)はその移植対象におけるもっとも大きな数値を保持できます。
	n_dcon(浮動小数点数定数)は最低でもその移植対象のlong double型を表現できます。
	全てのノードの変換結果は最後にはレジスタになると考えられます(?)。

末端ノード
        NAME
                メモリ上の位置の参照です、例えば変数にアクセスする場合など。
                NAMEから取得したアドレスはICONになります。
                n_lval - NAMEへのオフセットです、例えば a[3] とか
                n_sp (1) - NAMEのシンボルテーブル中のエントリになります
                n_name (2) - 名前の文字列を指すポインタです

        ICON
                数値型の定数値です、素の数値にもアドレス値にもなります。
                n_lval - 定数の値です
                n_sp (1) - アドレスの場合、シンボルテーブル中のエントリになります
                n_name (2) - シンボルの名前です

        FCON
                浮動少数点数の定数値です。
                n_dcon - 定数です

        REG
                レジスタのノードです、通常生成するのはレジスタアロケータの結果だけ
                ですが、しかしながらそのほかの場所でも生成されることがありえます。
		それは例をあげれば、ターゲットがレジスタ引数を持つ場合なんかです。
		n_rval - 数値でレジスタ変数になります

        OREG
                メモリ参照で、レジスタに対するオフセットになります。
                通常はpass2のoreg()内でUMUL-PLUS-REG-ICONのチェインにより作られます。
                ただし対象CPUがサポートしてる場合だけですが。
                n_lval - 定数でレジスタからのオフセットになります
                n_rval - 数値でレジスタのインデックス順になります

	TEMP
		一時ノードで、生成されるのはフロントエンドが値を
		レジスタでもメモリでもどっちにでも置けるようにするためです。
		レジスタアロケータは後でそれらを可能であればレジスタに置こうと試みます。
		n_rval - 数値で一時変数になります

力尽きた。

@ ゆっくりによる pcc の内部解説

つーことでコードリーディングのお時間、ゆっくりの声でよんでね!

↑のドキュメントで触れられている

  • 二項(binary)
  • 単項(unary)
  • 末端(leaf)

のノード分類ですが、実際にプログラム中では
以下の定数として宣言されています。

[ mip/manifest.h ]
46 /*
47  * Node types
48  */
49 #define LTYPE   02              /* leaf */
50 #define UTYPE   04              /* unary */
51 #define BITYPE  010             /* binary */

そして{末端,単項,二項}ノード種別についても以下同文。
ノード種類の説明は次回翻訳とあわせてです。

[ mip/node.h ]
147 /*
148  * Value nodes.
149  */
150 #define NAME    2
151 #define ICON    4
152 #define FCON    5
...
234 #define MAXOP   58

そして NODE は n_op フィールドにこのノード種別を保持します。

[ mip/node.h ]
62 typedef struct node {
63         struct  node *next;
64         int     n_op;
65         union {

ノード種別からノード分類へ変換するには、キバヤシよろしくノイズ除去に以下のマクロを使って
「ガラッ話は聞いた世界は滅亡するAA略」してください。

	optype(p->n_op) == BITYPE

この optype はマクロで以下の場所で宣言されています。

[ mip/pass2.h ]
400 #define optype(o)       (dope[o]&TYFLG)

このマクロ中で使われている dope は int 型の配列で以下のよに定義されとります。
他にも opst ちうのもありますが、これについては後述。

[ mip/pass2.h ]
360 extern  int dope[];     /* a vector containing operator information */
361 extern  char *opst[];   /* a vector containing names for ops */

配列の添字にはさっきのノード種別を使い、上限値は MAXOP となります。

このdope及びopstはフロントエンドが mkdope() を呼んだタイミングで初期化されます。

[ mip/common.c ]
319 int dope[DSIZE];
320 char *opst[DSIZE];
...
385 void
386 mkdope()
387 {
388         struct dopest *q;
389
390         for( q = indope; q->dopeop >= 0; ++q ){
391                 dope[q->dopeop] = q->dopeval;
392                 opst[q->dopeop] = q->opst;
393         }
394 }

初期化の為の元ソースは mip/common.c にテーブルが宣言されてる indope です。

[ mip/common.c ]
322 struct dopest {
323         int dopeop;
324         char opst[8];
325         int dopeval;
326 } indope[] = {
327         { NAME, "NAME", LTYPE, },
328         { REG, "REG", LTYPE, },
...
382         { -1,   "",     0 },
383 };

ですので今回のINCR/DECRのpass2化のように種別を増やすなんてときに
弄るのはここになります。

opst は ccom -Xb などのデバックオプションでノードツリーをダンプする際に
ノードタイプを画面表示可能な文字列に変換する用に以下同文、例えば mip/reader.c では

664         if (rv == FFAIL && !q)
665                 comperr("Cannot generate code, node %p op %s", p,opst[p->n_op]);

のように使われています、まぁ今どきのプログラムならNODE構造体にtoString()とかto_strみたいな
関数を用意するんでしょうが、なにせ30年前のコードですので以下略

これらのノード種別は pass2 で有効なものであって、言語(=フロントエンド)非依存です。
言語に依存するノード種別については C であれば cc/ccom/pass1.h に定義されています。

539 /*
540  * C compiler first pass extra defines.
541  */
542 #define QUALIFIER       (MAXOP+1)
...
572 #define INCR            (MAXOP+26)
573 #define DECR            (MAXOP+27)

今回問題になっている INCR/DECR は今のところ言語依存の扱いですやね。
なので pass1.h に定義され、pass2に渡される前に別の形に書き換えられてしまう運命なのです。

話し戻してさっきの dope[] の添字にこの pass1 オンリーのノード種別を渡したりすると、たうぜん
array index out of bounds の悲劇が発生しますので、cc/ccom/trees.c には cdope() という
wrapper 関数が定義されていて、pass1ではこっちを使います。

2647 int
2648 cdope(int op)
2649 {
2650         if (op <= MAXOP)
2651                 return dope[op];
2652         switch (op) {
...
2696         case INCR:
2697         case DECR:
2698                 return BITYPE|ASGFLG;
2699         }
...

同様に opst にも wrapper が用意されています。

2601 char *
2602 copst(int op)
2603 {
2604         if (op <= MAXOP)
2605                 return opst[op];
...
2632         SNAM(INCR,++)
2633         SNAM(DECR,--)
...

今日はここまで。

2010/9/9(Thu)

[pcc] ゆっくりによる pcc 内部解説(その2)

それでは前回の翻訳の中で出てきたノードの分類である

について理解を深めましょう。

以下のソースがどういうツリー構造に変換されるか見てみましょう。

$ cat >test.c
int X, Y;
main(void)
{
	X = -Y;
}
^D

pcc(1) はコンパイラの実体の実体である ccom(1)を呼びますが、こいつのマニュアルをみれば
デバッグスイッチで -Xe を指定すると pass1 でどういうツリーが作られるかが出力されるとあります。

cpp test.c | /usr/local/libexec/ccom -Xe

そうすると以下のツリー情報が得られます。

0x7f7ffd7019c0) =, int, 0x0, attributes; basetyp,
    0x7f7ffd702190) NAME, 0, 0, int, 0x0, attributes; basetyp,
    0x7f7ffd701920) U-, int, 0x0, attributes; basetyp,
        0x7f7ffd701450) NAME, 0, 0, int, 0x0, attributes; basetyp,
...

つうかもうちょいマシなデバグ出力にして干し酢、読み方すら書いてねぇ…

ということで読み方を知るために use the source

[ cc/ccom/trees.c ]
1847 #ifdef PCC_DEBUG
1848 void
1849 eprint(NODE *p, int down, int *a, int *b)
1850 {
1851         int ty;
1852
1853         *a = *b = down+1;
1854         while( down > 1 ){
1855                 printf( "\t" );
1856                 down -= 2;
1857                 }
1858         if( down ) printf( "    " );
1859
1860         ty = coptype( p->n_op );
1861
1862         printf("%p) %s, ", p, copst(p->n_op));
1863         if (p->n_op == XARG || p->n_op == XASM)
1864                 printf("id '%s', ", p->n_name);
1865         if (ty == LTYPE) {
1866                 printf(CONFMT, p->n_lval);
1867                 printf(", %d, ", (p->n_op != NAME && p->n_op != ICON) ?
1868                     p->n_rval : 0);
1869         }
1870         tprint(stdout, p->n_type, p->n_qual);
1871         printf( ", %p, ", p->n_df);
1872         dump_attr(p->n_ap);
1873 }
1874 # endif

1862行目をみれば最初の hex な謎数値はノードのアドレスであることが判ります、要らんわw
んで次は昨日説明したcopst(n_op)なのでこれはノード種別の名称ですやね。
つか「=」に置き換えず定数名のまま「ASSIGN」と出力した方が判りやすいのに。

んで1860行目でやはりこれも説明した coptype(n_op) でノード分類を取得してます。
この分類を元に1865行目でこのノードがリーフ(末端)である場合には左辺値(n_lval)
そしてリーフであってもNAME/ICONで無い場合は、右辺値(n_rval)も出力しています。
それぞれのケースでこれらの値の持つ意味は変わりますので、昨日の説明を再度ご確認をば。

で1870行目で tprint() で型名(n_type)と修飾子(n_qual)を出力します。
型と修飾子についてはまた次回説明します。

その次1871行目では n_dfのアドレスを表示しています、n_df については pass1.h で定義される
union dimfun のコメント参照、ノードがARYの場合、配列の次元大介情報をなど保持します(詳細はいずれ)。
最後 1872行目の dump_attr() で gcc 互換の __attribute__情報を出力しますが割愛。

さてもう一度さっきのツリー出力を見てみましょう、わかりやすく図にすると

というツリーが作られているわけです、こーゆー絵はドラゴンブックなんかの
コンパイラ解説書どおりでございますやね。

まとめると

ということでございます。

[pcc] _Bool + INCR/DECR(後置インクリメント/デクリメント命令) 問題 その2

んではノード分類を理解したところで、今問題になってる後置インクリメント/デクリメント(INCR/DECR)が
いまどーゆー実装になっとるのかをみてみましょ、さっくりダンプ。

0x7f7ffd7020c0) ++, int, 0x0, attributes; basetyp,
    0x7f7ffd701920) NAME, 0, 0x7f7ffd701ff0, int, 0x0, attributes; basetyp,
    0x7f7ffd7019c0) ICON, 1, 0x0, int, 0x0, attributes; basetyp,

とまぁ二項(binary)ノードで実装されてるわけですね。

しかしですね、実はこれ pass1 の最後でさっくり以下のコードで書き換えが発生しとります。

2248 static NODE *
2249 delasgop(NODE *p)
2250 {
2251 	NODE *q, *r;
2252 	int tval;
2253
2254 	if (p->n_op == INCR || p->n_op == DECR) {
2255 	 	/*
2256 	 	 * Rewrite x++ to (x += 1) -1; and deal with it further down.
2257 	 	 * Pass2 will remove -1 if unnecessary.
2258 	 	 */
2259 	 	q = ccopy(p);
2260 	 	tfree(p->n_left);
2261 	 	q->n_op = (p->n_op==INCR)?PLUSEQ:MINUSEQ;
2262 	 	p->n_op = (p->n_op==INCR)?MINUS:PLUS;
2263 	 	p->n_left = delasgop(q);
2264

これ-Xeによるデバッグ出力が終わった後こっそりやられるので、コード弄らんと
ツリーのダンプがキャントゲットというイデオットな作りですが、とりあえず以下のように
コードを修正してどう書き換えられたか見てみまひょ。

Index: cc/ccom/trees.c
===================================================================
RCS file: /cvsroot/pcc/cc/ccom/trees.c,v
retrieving revision 1.257
diff -u -r1.257 trees.c
--- cc/ccom/trees.c     5 Sep 2010 10:20:10 -0000       1.257
+++ cc/ccom/trees.c     9 Sep 2010 09:31:59 -0000
@@ -2324,6 +2324,10 @@
 		tfree(p);
 	else
 		ecode(p);
+#ifdef PCC_DEBUG
+	if (edebug)
+		fwalk(p, eprint, 0);
+#endif
 }


結果はこちら

0x7f7ffd7020c0) -, int, 0x42c5a6, attributes; basetyp,
    0x7f7ffd701450) =, int, 0x42c5a6, attributes; basetyp,
        0x7f7ffd701970) NAME, 0, 0x7f7ffd701ff0, int, 0x7f7ffd701fc0, attributes; basetyp,
        0x7f7ffd702190) +, int, 0x42c5a6, attributes; basetyp,
            0x7f7ffd701920) NAME, 0, 0x7f7ffd701ff0, int, 0x7f7ffd701fc0, attributes; basetyp,
            0x7f7ffd702070) ICON, 1, 0x0, int, 0x42c5a6, attributes; basetyp,
    0x7f7ffd7019c0) ICON, 1, 0x0, int, 0x42c5a6, attributes; basetyp,

相変わらずわかりにくひ出力ですが

MINUS(binary)
	L: EQ(binary)
		L: NAME(leaf) - x
		R: PLUS(binary)
			L: NAME(leaf) - x
			R: ICON(leaf) - 1
	R: ICON(leaf) - 1

となっとります、あれなんで(x = x + 1) - 1 でコメントのように (x += 1) - 1 になってないん
と思われた方は上級者、これは PLUSEQ もpass1で書き換え対象になっているからっす。
x += 1 は x = x + 1 に書き換えられるわけですが、これどこでやってるかはコード読み慣れんと多分判らんかも。
これはまた次回にですかね、ヒント: 定数マクロの PLUSEQ と PLUS を見比べてその後 UNASG に注目すると以下略。

また次回へ続きます。

2010/9/10(Fri)

ゆっくりによる pcc 内部解説(その2 補講)

ternary opというと条件演算子 <cond> ? <true> : <false> ですやね。
条件演算子はpccでは言語依存の扱いで、pass1に定義されてます。

[ cc/ccom/pass1.h ]
553 #define QUEST           (MAXOP+9)
554 #define COLON           (MAXOP+10)

[ cc/ccom/trees.h ]
2620 char *
2621 copst(int op)
2622 {
2623         if (op <= MAXOP)
2624                 return opst[op];
2625 #define SNAM(x,y) case x: return #y;
...
2635         SNAM(QUEST,?)
2636         SNAM(COLON,:)
...
2667 int
2668 cdope(int op)
2669 {
2670         if (op <= MAXOP)
2671                 return dope[op];
2672         switch (op) {
...
2684         case QUEST:
2685         case COLON:
...
2688                 return BITYPE;

QUEST(?) COLON(:) どっちもbinary node扱いですやね。

int x, y;
main (void)
{
        x = (y) ? 1 : 2;
}

なコードは以下のようなツリーになります。

0x7f7ffd701970) =, int, 0x0, attributes; basetyp,
    0x7f7ffd702290) NAME, 0, 0x7f7ffd702000, int, 0x0, attributes; basetyp,
    0x7f7ffd702120) ?, int, 0x0, attributes; basetyp,
        0x7f7ffd702170) NAME, 0, 0x7f7ffd7020a0, int, 0x0, attributes; basetyp,
        0x7f7ffd7019c0) :, int, 0x0, attributes; basetyp,
            0x7f7ffd701450) ICON, 1, 0x0, int, 0x0, attributes; basetyp,
            0x7f7ffd701920) ICON, 2, 0x0, int, 0x0, attributes; basetyp,

判りづらいので絵を描くと

という感じ、なお pass1 の終わりには全て GOTOノードつまりjmp instructionに置き換えられます。
インチキくさいですが動けばいいんです!

2010/9/12(Sun)

[NetBSD] fwide(3) and orientation

先日 fpos_t が mbstate_t を持つ実装ってどうなのよ的な話を 書いたのですが
ご指摘受けて気づいたのですがこれ ISO/IEC 9899:1990 AMD1で must になってるんですね、thanks to 塩崎さん

ワイド文字単位のストリームは, それぞれ結び付けられた mbstate_tをもつ。そのオブ
ジェクトは,その時点でのストリームの解析状態を保持している。fgetpos関数の呼び出しが成功すると,
このmbstate_tオブジェクトの値の表現をfpos_tオブジェクトの値の一部として格納する。

うへぇ、まぁ確かにFILEはmbstate_t持っててfgetwcとか呼ぶとそれを更新するので
fgetpos/fsetposで場所を覚えとくには必要ですわ、特にstateful encoding。

ということで謝罪はするが(ry

そいや前世紀くらいにそういう話 出てました、完全に忘れ去られてたガビーン。

うーんこれそもそもISO Cが悪筋で wfpos_t/fsetwpos/fgetwpos を導入して
fwide(3)がpositiveなfileに対して通常のfpos_t/fsetpos/fgetpos
したらEBADFという方が既存の実装に影響少なかったよなぁと(後の祭りです)。

そもそもワイド文字単位のストリームって fseek/ftellどうすんの問題あるし。このメールだと

fseek/ftell は、ある種のエンコーディング環境では利用できないだろう

ということみたいだけど。

これ当然バイナリ互換性無くなるのでlibcのmajor bumpのタイミングで対処すべきですが
さーてどうしましょ。

2010/9/19(Sun)

Security / Backward Compatibility

至言 ktkr

exploit がなければいいという話じゃない、vulnerability を生み出しただけでアウトなんだよ

という話ですな(そーか?) 特にこういう 強力なワームを相手にしなきゃならん場合は。

前作で出来ていたことが出来ないなんて劣化もいいとこだろうが!

NetP^H BSD 界隈で「後方互換とかどうでもいい」「メンテの邪魔だし 骨董品はこれから毎日サポート切ろうぜ」とか
発言しようものならいつ刺すか刺されるかの展開になることやら。

ニコ動三大コンテンツというとニコマス・東方・初音ミクですが

くらいしか知らないです、すいません。

2010/9/25(Sat)

[NetBSD] setenv(3)

@

PR/43899だが、 そんな実装で大丈夫か?

そもそも 前にも書いたけどあそこは再利用すらしちゃダメなんですよな。
なぜなら environ は直接ユーザが触れるので、書込禁止なメモリに置換されてる可能性があるから。
なので unsetenv(3) 側で 勝手に free(3) なんぞしていいわきゃないんだが。

まぁsetenv(3)を使ってる限り問題にはならんので、memory leakしない方がマシ
environを直接変更した奴の自業自得と考えられなくもないだが、やっぱつっこんだほうがいいよなぁ。

つか FreeBSD の実装を持ってきてくるのが一番いいんだけどね。
local exploitを引き起こした unsetenv(3) の実装がアレな部分とか直さんとならんけど。

おまけ、以前ヒウイッヒーとかいうとこでも 粒焼いたのですが
NetBSD の setenv(3) は POSIX 的に EINVAL を返すケースがあることに注意。

setenv("FOO=bar", "buzz", 1)

のような場合、"=" 以降は無視されて

setenv("FOO", "buzz", 1);

と等価の結果になることに注意。
理由はリンク先に書いたとおり、まぁ若干パラノイアックな理由ですが。
(本当はENOMEMのケースがあるので絶対にsetenvの戻り値はチェックしないとダメ)。

@

バックアウトしろーメール投げた直後にtron氏まで参戦してきて修正したけど
どうにもこれ十分な対策には見えないんだよな、つぅかテストケースないの。
それにPOSIXの仕様どおりのputenv(3)を実装しないといけん件を考慮すると
さっさとFreeBSDの実装パチってきて後方互換的にアレなとこだけ直して使ったほうが
いいと思う。

2010/9/30(Thu)

[NetBSD] stdio

@ より良いインタフェースへ変更する必要性

POSIX:2008 対応の一環で fmemopen(3) を最近 commit したり作業してるんですが
open_memstream(3) のテストを書いてる最中に、今の NetBSD の stdio の実装では
仕様を満たすこたー無理だと判明してちょっと困ったことに *1

今の stdio 実装では strct FILEに fread/fwrite/fseeko(ftello)/fcloseに相当する操作があります。

typedef struct __sFILE {
...
	/* operations */
	void	*_cookie;	/* cookie passed to io functions */
	int	(*_close)(void *);
	int	(*_read) (void *, char *, int);
	fpos_t	(*_seek) (void *, fpos_t, int);
	int	(*_write)(void *, const char *, int);
...
} FILE

んでこいつは userland からは funopen(3)を 使って乗っ取り可能だったりします。

んで勘のいい人はアレ?と思うでしょうが read/write の操作、これ書込サイズintなんですよね。
fread/fwrite は size_t(=unsigned long)を引数にとるので int じゃ「そんな実装で大丈夫か?」なのです。

何故にこんな実装?という種明かしをすると、この操作より上のレイヤでバッファリングが行われていて
fread/fwrite に int を超えるサイズを指定した場合でも これらの操作はより小さいバッファサイズ単位で
分割して呼び出されるので「大丈夫だ問題ない」なのですよな。

ところがですね、fmemopen/open_w?memstream の仕様には、fflush(3)やfclose(3)のタイミングで
末尾に'\0'を書き込んで NUL terminate するという仕様があったりします(主に安全面への考慮)。
ところが今実装されてる操作では fclose(3) はフックできるのですが fflush(3) がどうにもならんのです。

かといって fflush(3) のフックを入れるゆうても、バッファは見えない上位の 層にに存在するのに
バッファをフラッシュする操作はあるってーのは、デザイン的に?がついてしまいます。

蛇口があれど捻っても水は出ません、でも無いと困りますなんてのは 超芸術トマソンでしかないわけでして。

そもそもの話、なぜstdioがバッファリングしてるかちゅーと、read/write といった
system callが頻繁に呼ばれることによる性能劣化への対策なので、system callなぞ呼ばない
ただのメモリ操作しかしないfmemopen(3)やopen_memstream(3)であればバッファリングなぞせず
直接書込/読込して問題ないわけでして。

@ 実装上の問題

ということで、バッファリングまで全部自前で面倒を見るような操作を新たに用意する必要があるのかなと。

typedef struct __sFILE {
...

	/* newly introduced unbuffer operation */
        size_t  (*__fread_unlocked)(void * __restrict,
	    size_t, size_t, FILE * __restrict);
        size_t  (*__fwrite_unlocked)(const void * __restrict,
	    size_t, size_t, FILE * __restrict);
        int     (*__fseeko_unlocked)(FILE *, __off_t, int);
        __off_t (*__ftello_unlocked)(FILE *);
        int     (*__fflush_unlocked)(FILE *);
        int     (*__fclose_unlocked)(FILE *);
} FILE;

static __inline size_t
__fread_unlocked(void * __restrict buf,
    size_t size, size_t count, FILE * __restrict fp)
{
	return (*fp->__fread_unlocked)(buf, size, count, fp);
}

size_t
fread(void * __restrict buf,
    size_t size, size_t count, FILE * __restrict fp)
{
	size_t ret;

	FLOCKFILE(fp);
	ret = __fread_unlocked(buf, size, count, fp);
	FUNLOCKFILE(fp);

	return ret;
}

まぁすぐに思いつくのはこんな実装ですかね。

ところがですね NetBSD の stdio 実装における標準{乳力,出力,エラー出力}は

__BEGIN_DECLS
extern FILE __sF[];
__END_DECLS
...
#define stdin   (&__sF[0])
#define stdout  (&__sF[1])
#define stderr  (&__sF[2])

ちゅー宣言になってるので、後方互換保ちつつ変更するのは厳しいどすな。

例えばですね

fputc('A', stdout);

なんてコードはx86なら

        movl    $__sF+152, %esi		/* +152 はちょうど sizeof(FILE) */
        movl    $65, %edi		/* 65 は 'A' */
        call    fputc

と展開されますので、さっきのコード例のように struct FILEに フィールドを追加したりして
sizeof(FILE) が変わってしまう場合、再コンパイルしないと offset がズレズレになるのですよな。

まぁこういう場合、NetBSD では __RENAME マクロトリックを使って

extern FILE __sF[]	__RENAME(__sF50);

と名前を変更スルノデス!となります。

ちゅーか次に struct FILE のサイズが変更になったときに
またまた __RENAME が必要になっちゃうのも嫌なので
ここは将来に 禍根を残さぬようクサい実装は元から絶つということで

__BEGIN_DECLS
extern FILE __sF[];	/* for backward compatibility */

extern FILE *__sF_stdin;
extern FILE *__sF_stdout;
extern FILE *__sF_stderr;
__END_DECLS
...
#define stdin   (__sF_stdin)
#define stdout  (__sF_stdout)
#define stderr  (__sF_stderr)

と、しとくべきですな。

と こ ろ が ですね、もひとつ問題があって今回のケースは __RENAME すら使えないのですな。
なぜならば

  • 古い libc に対して compile した shared library は依然として __sF の方に読み書きをする
  • 新しい libc に対して compile した binary は改めて __sF_std{in,out,err} の方を読み書きする
  • __sF と __sF_std{in,out,err} は別々にバッファ管理をしているので、同期を取ることが不可能

うぎゃー。

@ これまではどうしていたの?

今までも MT-safe 化の為に mutex の為のフィールドを追加したり、f{get,put}wcなんかの
ワイド文字関数が追加されたときに、mbstate_tなフィールドを追加したりしてたのですが
これは stdio.h#rev1.42で、ungetc(3) 用のバッファを転用するという hack でなんとかこれまでやってきました。
実際の中身は こちら

まぁ今回もこれを踏襲して、さっきのunbuffered interfaceを struct __sFILE にではなく
このstruct _sfileext 中に含めるように変更すればいいのですが。

/*
 * file extension
 */
struct __sfileext {
...
        size_t  (*__fread_unlocked)(void * __restrict,
                    size_t, size_t, FILE * __restrict);
        size_t  (*__fwrite_unlocked)(const void * __restrict,
                    size_t, size_t, FILE * __restrict);
        int     (*__fseeko_unlocked)(FILE *, __off_t, int);
        __off_t (*__ftello_unlocked)(FILE *);
        int     (*__fflush_unlocked)(FILE *);
        int     (*__fclose_unlocked)(FILE *);
};

でもせっかく libc の major も bump することだしこの負の連鎖を抜け出したい気がするんですよね。
なんか良いアイデアはないものか。

単純にインタフェース変更即 libc major bump なら全然問題ないんですが
time_t 64bit 化の時にそういうアプローチで作業はしないという前例があるのですよなぁ。
それに src/lib/libc/compat 以下のトリックで、今後はlibc.so.12とlibc.so.13を同時に管理していくので
あまり大鉈を振るうことができないという。

*1:まぁ効率の悪い実装になることは織込み済で、とりあえず実装を入れてから
改良して最適化しようとは思ってたのですが、6の枝切り間に合うんかな。