さて、SL-A300 の ROM ver1.1 で確認した BIOS or カーネルの ZERO_PAGE バグの件。 Nov. 29 にML になげているので、 Dec. 7 に make されている B, C にはないかもしれないし、あるかもしれない。 SL5000系にないことはわかっているが、A300 の 1.2 や B500, C700 でどうだかは知らない。 各々で確認されたし。
#include <stdio.h>
#define COUNT 10000
struct big {
int dummy[COUNT];
} data;
int main(){
int i;
for(i = 0;i < (COUNT - 1); i++){
int tmp;
if(data.dummy[i] != 0) {
tmp = data.dummy[i]; // dummy page から読み込み。
data.dummy[i+1] = 0; // copy on write 起動。
printf("%06x %p %08x %d\n", i, &data.dummy[i], tmp, data.dummy[i]);
}
}
return 0;
}
実行すると、SL5x00 では何もかえってこない(正しい挙動だ)が、
ノーマルカーネルの A300 で実行すると:
#./zeropage_test 000288 0x11000 a0000000 0 000688 0x12000 a0000000 0 000a88 0x13000 a0000000 0 000e88 0x14000 a0000000 0 001288 0x15000 a0000000 0 001688 0x16000 a0000000 0 001a88 0x17000 a0000000 0 001e88 0x18000 a0000000 0 002288 0x19000 a0000000 0 002688 0x1a000 a0000000 0暗黙のうちにゼロ初期化されているはずの領域がゼロでなく、その非ゼロになってるところの近所に 何か書きこむと非ゼロになっていた領域がなぜかゼロに戻る。そしてその非ゼロは 4kB 毎に綺麗に並ぶ。
なお、後述する ZERO_PAGE が非ゼロに汚染されるのは suspend/resume の間なので、 ブート後、一度も suspend してない A300 では上のテストではやはり何もかえってこない。
ZERO_PAGE は、本来は中身がすべてゼロのページだ。 BSS 内の read は確かに 0 を返すようになる。これによって 「明示的に初期化されていない大域変数はゼロでなければならない」という規定を満たす。
... が、一度以上 suspend した A300 では、この ZERO_PAGE が 0 でなくなっている (具体的には先頭ワードが 0xa0000000 になっている; とっても見覚えのある magic number なんだが、 その考察は省略する) ため、 BSS 内の配列を順繰りに読むと上で見たようにページ長毎 (= 4kB 毎) に非ゼロが現れる。
この状況で write が発生するとどうなるか ──
プログラム側から ZERO_PAGE への書き込みは禁じられている。 重複してページマップしているんだから当然だ。 書き込みと同時に旧ページの内容が新しいページにマップされ、そちらに書き込まれる。 ... というのが教科書的なコードだが、 「ZERO_PAGE は内容がゼロ」のはずなので、 新しいページは旧ページ(つまり ZERO_PAGE) からの memcpy でなく memset で一気にゼロクリアされてしまっている。 このため、ZERO_PAGE を指さなくなった領域は、 もともとが正しい ZERO_PAGE をさしていたがごとくの挙動に戻ることになる。
ページのどこかへの write でページ全体がゼロクリアされてしまうので、
バグが発現するのはアプリが起動してすぐのことのはずだ。
起動後ただちに segmentation fault をおこして落ちるアプリが、マシンのブート直後に
動かした場合だけ落ちないのなら、このバグにかかっている。
それを確認するには gdb なり strace なりで segmentation fault を起こした理由が
0xa0000000 への書き込みにあることをみればいい。
いまのところ w3m, dpkg, mule で起きることが分かっている。
パッチ k1 時点で指摘のあった CFE-01 が動かん (11/13) という件は、k2 でそれらしいところは戻したけど、なにぶん CFE-01 もってないので確認できない。 あしからず mOm
ついでに言えば、やっぱり BIOS のバグだったらしい。 つまり、ver 1.30J の all.nb0 を入れたあとに 1.20J の無対策カーネルを入れても正常動作した。