kozos

スーパーへ水を汲みに行ってた時の事。順番待ちしてたら、何の前触れもなく突然、停電に なってしまった。薄明かりの中ですぐに復旧するだろうと思って待っていたんだが、一向に 復旧しない。停電になってしまったので店内放送も無いし。

ふと、道の向こう側にある店に目をやると、その店も真っ暗。地域一体が停電したのかな? 店の人が慌しく、携帯でどこかに連絡を取っていた。へぇ、携帯基地局ってバッテリーで バックアップされてるんかいななんて、余計な事を思ってしまった。

哀れ私の前で水を汲んでいたおばさん。容器が給水機の中にしまわれたままになっていて、 取り出し口の扉が開かないので、容器が人質になっちゃったよ。この給水機、フェイルセーフ 設計が出来てないね。回復する様子も無いので、おいらは諦めて帰る事にしたよ。

外に出たら、スーパーの自家発電を一生懸命に起動しようとしてる店員さんが多数居た。 非常訓練やっとかなきゃ。

道路の信号はあちこちで消えていた。こういう時は注意進行でしたっけ?営業車はちゃんと 守っているけど、普通車は、クラクションを鳴らして交差点を高速進行してく。まるで 鎖から解き放たれた獣みたい。怖いこった。

家に帰ったら、女房が何くわぬ顔をして電子レンジを使ってた。あれ? 停電してるんじゃ ないの? え、何それ? 再び外に出て探索してみると、町区を境にきっちりと、停電地区 と通電地区が分かれていたよ。電気もネットワークになってるんだなあと感じてしまい ましたよ。それにしても、原因は何だんだろう? 東電のすんませんページにも事故報告 あがってなかったよ。

kozos準備

以前行ったKernel/VMの会で紹介されてた 12ステップで作る 組込みOS自作入門 が発売になった。

生憎、当地は田舎ゆえ、こういう本は書店に流れてこない。これはもう、上京した時に 買ってくる鹿。(相変わらず、ネット通販嫌いなおいら)

H8って懐かしいなあ。昔使った事あるよ。あの時はGPIB-BUSラインの信号を拾って、ごにょ ごにょしたっけ。開発環境は、Plamo/Linux上とFreeBSDに作ったんだよな。 るねさすのマニュアルを読むと、ふらっしゅへの書き換え保障回数は100回までと書いてあって 、何回書き換えたか、正の字をincrimentしながら、管理してたなあ。幸い、30回ぐらいの 書き換えで満足する動きになってくれたけど。正直、何回ぐらい書き換え出来るのかな?

あの時は、シングルスレッドで事足りたけど、正直マルチスレッドってものすごく興味が あるんだ。本が手に入るまで、FreeBSDで感触を掴んでおこう。

独自OSを作ってみよう! の第1部から順番に試して行こう。 FreeBSD-8.0-p3がVMWareの上で動いているので、環境としては十分だ。

H8上のkozosでも勉強のためにアセンブラを一部使っているそうだ(と、会の時に質問して 、教えていただいた)。 きっと、FreeBSD版と言うかi386版にもアセンブラが登場するに違いない。ちょっと先回り して、 gas とか gas vs. intelを見ておくと いいのかな。それとこれも重要。 C言語系/呼び出し規約/x86/cdecl即物的に コンパイラが吐きそうな命令

それにしても、i386かあ。”知恵遅れ”まで ”下位互換”な石ですね。あっ、これオイラが 言ってるんじゃなくて、『エキスパートCプログラミング』の一節ですんで、誤解無きよう。

試運転

第1回目を試してみる。

(gdb) b kz_run
Breakpoint 1 at 0x80487b6: file syscall.c, line 21.
(gdb) run
Starting program: /usr/home/sakae/KOZOS/01/koz
main start

Breakpoint 1, kz_run (func=0x8048880 <func1>, name=0x806cc1c "func1", pri=2,
    argc=1, argv=0xbfbfeb58) at syscall.c:21
21        param.un.run.func = func;
(gdb) c
Continuing.

Program received signal SIGSYS, Bad system call.
0x0805f913 in kill ()
(gdb) handle SIGSYS nostop noprint
Signal        Stop      Print   Pass to program Description
SIGSYS        No        No      Yes             Bad system call
(gdb) c
Continuing.
thread 1 started

Breakpoint 1, kz_run (func=0x8048910 <func2>, name=0x806cc34 "func2", pri=2,
    argc=1, argv=0xbfbfeb58) at syscall.c:21
21        param.un.run.func = func;
(gdb) c
Continuing.
thread 2 started
mainfunc loop 0
mainfunc loop 1
mainfunc end
func1 start 1 /usr/home/sakae/KOZOS/01/koz
func1 loop 0
func2 start 1 /usr/home/sakae/KOZOS/01/koz
func2 loop 0
func1 loop 1
func2 loop 1
func1 end
func2 end

Program exited normally.

ちゃんと走っているよ。signalを割り込みに使っちゃうなんてアイデアだなあ。割り込みが 入る度にgdbに戻ってきてメッセージが出力されるのには閉口しるので、handleコマンドで 抑制してみた。gdbって柔軟だなあ。

setjmp/longjmp

上記の割り込みを影で支えているのが、setjmpとlongjmpになるかな。使い方がいまいち 理解出来ていないので、ちょっと実験してみる。

#include <stdio.h>
#include <setjmp.h>

jmp_buf env;
int     a;

int main() {
        a = 1;
        setjmp(env);
        printf("After setjmp\n");

        if ( a ){
             printf("%d\n", a);
             a = 0;
        } else {
             printf("%d\n", a);
             return 0;
        }

        longjmp(env, 1);
}
[sakae@cdr ~/KOZOS]$ ./a.out
After setjmp
1
After setjmp
0

これって、Schemeで言う継続なのかな。 なんでも継続 へ継続したりして。

(gdb) b main
Breakpoint 1 at 0x80484a0: file hoge.c, line 7.
(gdb) run
Starting program: /usr/home/sakae/KOZOS/a.out

Breakpoint 1, main () at hoge.c:7
7       int main() {
(gdb) b setjmp
Breakpoint 2 at 0x280c6974
(gdb) c
Continuing.

Breakpoint 2, 0x280c6974 in setjmp () from /lib/libc.so.7
(gdb) disas
Dump of assembler code for function setjmp:
0x280c6974 <setjmp+0>:  mov    0x4(%esp),%ecx
0x280c6978 <setjmp+4>:  push   %ebx
0x280c6979 <setjmp+5>:  call   0x280c697e <setjmp+10>
0x280c697e <setjmp+10>: pop    %ebx
0x280c697f <setjmp+11>: add    $0xc815a,%ebx
0x280c6985 <setjmp+17>: lea    0x1c(%ecx),%eax
0x280c6988 <setjmp+20>: push   %eax
0x280c6989 <setjmp+21>: push   $0x0
0x280c698b <setjmp+23>: push   $0x1
0x280c698d <setjmp+25>: call   0x280aa86c <__stack_chk_fail_local+313644>
0x280c6992 <setjmp+30>: add    $0xc,%esp
0x280c6995 <setjmp+33>: pop    %ebx
0x280c6996 <setjmp+34>: mov    0x4(%esp),%ecx
0x280c699a <setjmp+38>: mov    0x0(%esp),%edx
0x280c699e <setjmp+42>: mov    %edx,0x0(%ecx)
0x280c69a1 <setjmp+45>: mov    %ebx,0x4(%ecx)
0x280c69a4 <setjmp+48>: mov    %esp,0x8(%ecx)
0x280c69a7 <setjmp+51>: mov    %ebp,0xc(%ecx)
0x280c69aa <setjmp+54>: mov    %esi,0x10(%ecx)
0x280c69ad <setjmp+57>: mov    %edi,0x14(%ecx)
0x280c69b0 <setjmp+60>: fnstcw 0x18(%ecx)
0x280c69b3 <setjmp+63>: xor    %eax,%eax
0x280c69b5 <setjmp+65>: ret
0x280c69b6 <setjmp+66>: mov    %esi,%esi
End of assembler dump.

ここに出てくる インテル語ぐらいが理解出来ればいいのかな。

アプリが実装された

こうして、とぼとぼながら1回目から読み進めている。前の回からのdiffがついているので 何が変わったかは、冒頭を見るだけで瞬時に判断出来るので嬉しい。こういう風に少しずつ 成長させていくってのは、最初から完成品がどーんとあって、それを説明されるより、 ずっと難易度が低く、理解度がUPする。

自分で何がしかのプログラムを書く時も、一発で完成品が出来る事なんてなくて、骨格に 肉をつけてだんだんと膨らませていくんだから、こういう説明方法は普段の実践と同じで すんなりと受け入れられる。また、極力エラーチェックを省略しているのも、コードを 読みやすくしてる理由だろう。

後、#if ~ #endif が無い、Pure(FreeBSD用)に書かれているのがとっても嬉しい。(これ、 オイラがFreeBSD Loveって事じゃなくて、単にソースを読む時、余計な#if等を気に しなくてもいいと言う事)まじで、これらの#ifディレクティブを理解して、自分の環境用 にコードを出してくれるもの、ないかしらん。 gcc -E した後、indent するぐらいじゃ、どうにもならないんだよなーー。

ちょっと横道にそれちゃった。軌道修正して、第7回へと進んで行く。早速実験。

[sakae@cdr ~/KOZOS/07]$ ./koz
Floating point exception: 8 (core dumped)

なんとまあ、coreだよ。臨場の続きをやれってか?

[sakae@cdr ~/KOZOS/07]$ gdb -q koz koz.core
Core was generated by `koz'.
Program terminated with signal 8, Arithmetic exception.
#0  0x08058ea2 in timesub ()
(gdb) bt
#0  0x08058ea2 in timesub ()
#1  0x080593d2 in localsub ()
#2  0x0805b59c in localtime ()
#3  0x0805b641 in ctime ()
#4  0x080494bf in clock_main (argc=0, argv=0x0) at clock.c:21
#5  0x0804829b in thread_init (thp=0x80e058c, argc=0, argv=0x0) at thread.c:48
#6  0x080482a0 in thread_init () at thread.c:49
#7  0x0804945a in main (argc=Cannot access memory at address 0x0) at main.c:19
(gdb) i all-r
eax            0xc40    3136
ecx            0x0      0
edx            0xc22e4507       -1037155065
ebx            0xa      10
esp            0x28121ee8       0x28121ee8
ebp            0x28121f2c       0x28121f2c
esi            0x7da    2010
edi            0x94     148
eip            0x8058ea2        0x8058ea2
eflags         0x10206  66054
cs             0x33     51
ss             0x3b     59
ds             0x3b     59
es             0x3b     59
fs             0x3b     59
gs             0x1b     27
st0            0        (raw 0x00000000000000000000)
st1            0        (raw 0x00000000000000000000)
st2            0        (raw 0x00000000000000000000)
st3            0        (raw 0x00000000000000000000)
st4            0        (raw 0x00000000000000000000)
st5            0        (raw 0x00000000000000000000)
st6            0        (raw 0x00000000000000000000)
st7            0        (raw 0x00000000000000000000)
fctrl          0x40     64
fstat          0x0      0
ftag           0x0      0
fiseg          0x0      0
fioff          0x0      0
foseg          0x0      0
fooff          0x0      0
fop            0x0      0
xmm0           {v4_float =     {0x0,
    0x0,
    0x0,
    0x0}, v2_double =     {0x0,
    0x0}, v16_int8 =     {0x0 <repeats 16 times>}, v8_int16 =     {0x0,
    0x0,
    0x0,
    0x0,
    0x0,
    0x0,
    0x0,
    0x0}, v4_int32 =     {0x0,
    0x0,
    0x0,
    0x0}, v2_int64 =     {0x0,
    0x0}, uint128 = 0x00000000000000000000000000000000}
xmm1           {v4_float =     {0x0,
 :
xmm7
 :
(gdb) i f
Stack level 0, frame at 0x28121f34:
 eip = 0x8058ea2 in timesub; saved eip 0x80593d2
 called by frame at 0x28121f78
 Arglist at 0x28121f2c, args:
 Locals at 0x28121f2c, Previous frame's sp is 0x28121f34
 Saved registers:
  ebx at 0x28121f20, ebp at 0x28121f2c, esi at 0x28121f24, edi at 0x28121f28,
  eip at 0x28121f30
(gdb) x/10w 0x28121f20
0x28121f20:     0x080d6130      0xbfbfe750      0x080da8e0      0x28121f70
0x28121f30:     0x080593d2      0x080da8e0      0x080d7510      0x00000000
0x28121f40:     0x00000000      0x080da8e0
(gdb) frame 4
#4  0x080494bf in clock_main (argc=0, argv=0x0) at clock.c:21
21          strcpy(p, ctime(&t));
(gdb) i local
t = 1275115305
p = 0x28152080 ""
(gdb) shell date -r 1275115305
Sat May 29 15:41:45 JST 2010
(gdb) p/x &t
$1 = 0x28121fc8
(gdb) q

gdbセッション中で、不敵にも全てのレジスターを表示してみたけど、これとOS起動時のdmsg で捕らえた特徴とはどう対応するん? 電卓上がりのCPUに拡張拡張で、複雑怪奇に拡張 された温泉宿みたいで落ち着かないな。出来たら、敬遠したい所であります。

CPU: Intel(R) Celeron(R) CPU          900  @ 2.20GHz (2194.37-MHz 686-class CPU)

  Origin = "GenuineIntel"  Id = 0x1067a  Stepping = 10
  Features=0xfebfbff<FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,CLFLUSH,DTS,ACPI,MMX,FXSR,SSE,SSE2,SS>
  Features2=0x80000201<SSE3,SSSE3,<b31>>
  AMD Features=0x100000<NX>
  TSC: P-state invariant
real memory  = 402653184 (384 MB)
avail memory = 379142144 (361 MB)

どうやら、libcの中で落ちてるな。こういう時は慌てず騒がず、あの人みたいに胡瓜を かじるかトマトをほおばりながら binary hacks本でも眺めるか。

すると、hack#50 POSICのスレッドセーフな関数って所に、やばいやつとしてctime()が 列挙されてた。この場合はあてはまらないっぽいけど、ものは試しとばかり ctime_r()を 使うように変更してみた。で、走らせてみると、やっぱり Floating point exception が 発生する。なんで?

そうそう、今回のエラーには関係ないけど、なんで? は、もう一つあって、staticリンク してるのは何故なんだろう? OSだから、威風堂々 してなさい。よそ(のプロセス)と 共有なんてもっての外? あるいは、debugが簡単に出来るから? それとも、作者さんの手元に、インテルの石が載ったボードが あって、そこへbinaryを転送して密かに遊んでる とか?

で、上の臨場結果だけれど、ctimeに渡っている引数は正常。そのアドレスとかpとかの アドレスがちょっと異常と言うか見慣れないけど、まあいいか。 (ああ、そうか。スタックエリアは自前で確保してたんですね。すっかり忘れてましたよ。) だけど、参照透明性は あるはずだから、途中でこけるのは論外だな。(だから、例外だってば)

ctime()とかのソースは、/usr/src/lib/libc/stdtime/localtime.c にあった。こういう時 、FreeBSDっていいよな。ソース一式がセットで用意されてるから。Linuxだと、あたりを つけて、glibcほにゃららを取ってくる事になるんだもん。この一点があるから、*BSD止め られないんだよな。まだまだ、ソースを読む技量は未熟だけれども。。。 (あとFreeBSDで嬉しいのは、/usr/portsが有るからかな。たまに、どーんと改定と言うか、 お祭りがあるけど。つい最近は、gettext祭りがあったな。その前は、kde祭りとかgnome祭りとか)

それでは頑張って解剖開始。途中でめまいを起して退室しちゃうかも?

日付の計算に、小数点演算が出てくるなんて、EXCELの世界だけかと思っていたら、なんと 冒頭付近に

#include "float.h" /* for FLT_MAX and DBL_MAX */

しっかり小数点演算を使ってるっぽい。、ctime()を探してみると、ctime_r()と共に列挙 されてました。リエントラントにするなら徹底しなきゃだめってのが実践されてて妙に納得 しました。

char *
ctime(timep)
const time_t * const    timep;
{
/*
** Section 4.12.3.2 of X3.159-1989 requires that
**      The ctime function converts the calendar time pointed to by timer
**      to local time in the form of a string. It is equivalent to
**              asctime(localtime(timer))
*/
        return asctime(localtime(timep));
}

char *
ctime_r(timep, buf)
const time_t * const    timep;
char *                  buf;
{
        struct tm       mytm;

        return asctime_r(localtime_r(timep, &mytm), buf);
}

localtimeからlocalsub経由でtimesubへと降りていくんだけど、どうも一番面倒な時間の変換は 最下層のtimesubへ丸投げしてるっぽい。哀れ下請けのtimesub、うるう年だけでなく、うるう秒まで 計算させられているっぽい。

        /*
        ** A positive leap second requires a special
        ** representation. This uses "... ??:59:60" et seq.
        */

なんとまあ、人間界の不合理な事よと思っているに違いない。なんで、一日が24時間なんだ。 計算簡単にするため、100newHour/day とかにならんものかのう。それにうるう秒なんて、 めんどくせー。tzinfo改定しなきゃならんし。どうせうるう秒なんて、数年に一度有るか ないかなんで、100年に一度どーんと修正でいいじゃん。 と、天文学者に怒られそうな事を 言ってみる。

文句を言っていても始まらないので、検分を続ける。coreは語るかな?

0x08058e8b <timesub+299>:       push   %eax
0x08058e8c <timesub+300>:       movzwl 0xffffffe2(%ebp),%eax
0x08058e90 <timesub+304>:       fildl  (%esp)
0x08058e93 <timesub+307>:       add    $0x4,%esp
0x08058e96 <timesub+310>:       fadds  0x80c43c4
0x08058e9c <timesub+316>:       mov    $0xc,%ah
0x08058e9e <timesub+318>:       mov    %ax,0xffffffe0(%ebp)
0x08058ea2 <timesub+322>:       fldcw  0xffffffe0(%ebp)
0x08058ea5 <timesub+325>:       fistpl 0xffffffdc(%ebp)
0x08058ea8 <timesub+328>:       fldcw  0xffffffe2(%ebp)

eipってPCの事だよなと脳内変換しつつ、eipが示した 0x08058ea2 付近をdisasしてみた。 ここから先は、複雑怪奇迷路のようなので、 IA32のマニュアル が必要そう。取ってきたよ。取り合えず900ページを超える概要を見てる。ほんと複雑!!! 複雑なのは、Wintelだからですな。MS-DOSはなんちゃら、なんて説明が出てくるし、8087とか の話も当たり前に出てくるし。これが、世界のプログラマーを苦しめる元凶ですな。 アップルみたいに、すぱっと過去を切り捨てたらどうでっしゃろ。

で、魔窟に足を踏み入れてみたら、小数点演算例外の元になった要因は、fstatに載るとか。 coreの臨場結果には例外が上がってないじゃん。これはもう生体解剖するしか。

(gdb) b timesub
Breakpoint 2 at 0x8058d66
(gdb) c
Continuing.

Breakpoint 2, 0x08058d66 in timesub ()

(gdb) display/i $eip
1: x/i $eip  0x8058d66 <timesub+6>:     sub    $0x38,%esp
(gdb) nexti
  :
(gdb)
0x08058e9e in timesub ()
1: x/i $eip  0x8058e9e <timesub+318>:   mov    %ax,0xffffffe0(%ebp)
(gdb)
0x08058ea2 in timesub ()
1: x/i $eip  0x8058ea2 <timesub+322>:   fldcw  0xffffffe0(%ebp)
(gdb)

Program received signal SIGFPE, Arithmetic exception.
0x08058ea2 in timesub ()
1: x/i $eip  0x8058ea2 <timesub+322>:   fldcw  0xffffffe0(%ebp)
(gdb) i float
=>R7: Valid   0x4016c5c1000000000000 +12960000
  R6: Empty   0x00000000000000000000
  R5: Empty   0x00000000000000000000
  R4: Empty   0x00000000000000000000
  R3: Empty   0x00000000000000000000
  R2: Empty   0x00000000000000000000
  R1: Empty   0x00000000000000000000
  R0: Empty   0x00000000000000000000

Status Word:         0x3800
                       TOP: 7
Control Word:        0x0040
                       PC: Single Precision (24-bits)
                       RC: Round to nearest
Tag Word:            0x3fff
Instruction Pointer: 0x33:0x08058e96
Operand Pointer:     0x3b:0x080c43c4
Opcode:              0xd805

ちょっとやり(進み)過ぎたみたいなので、もう一度やりなおし。

(gdb)
0x08058e9e in timesub ()
1: x/i $eip  0x8058e9e <timesub+318>:   mov    %ax,0xffffffe0(%ebp)
(gdb) i float
=>R7: Valid   0x4016c5c1000000000000 +12960000
  R6: Empty   0x00000000000000000000
  R5: Empty   0x00000000000000000000
  R4: Empty   0x00000000000000000000
  R3: Empty   0x00000000000000000000
  R2: Empty   0x00000000000000000000
  R1: Empty   0x00000000000000000000
  R0: Empty   0x00000000000000000000

Status Word:         0xb8a0                  PE   ES
                       TOP: 7
Control Word:        0x0040
                       PC: Single Precision (24-bits)
                       RC: Round to nearest
Tag Word:            0x3fff
Instruction Pointer: 0x33:0x08058e96
Operand Pointer:     0x3b:0x080c43c4
Opcode:              0xd805
(gdb) she date -r 12960000
1970年 5月31日 日曜日 09時00分00秒 JST

意味深なデータが出てるな。PEが精度不良で、ESはエラーサマリー(各エラーのorかな)って 事か。なんだかよく分からんなあ。わざわざintで表される時間情報を浮動小数点演算してる のは、計算途中のオーバーフローとかの防止だろうか?

F87の章を見てたら、浮動小数点機構と言いつつ、拡張精度モードにすれば、64Bit integer も扱えるのね。そんな機構を持っているなんて初めて知ったよ。 結局、intelの魔窟の中であえなく撃沈かなあ。もう少し戦略を練ってみようっと。