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の魔窟の中であえなく撃沈かなあ。もう少し戦略を練ってみようっと。