OS自作入門 onLinux 5日目
ようやく5日目が終わりました!
11月頭で文化祭があって、その後も学校が相変わらず忙しかったですが、
なんとか時間の合間を縫ってこぎつけました。
まずはソースはこちら。
今回は、4日目で画面表示ができるようになったので、
1.起動情報を受け取る
2.ASCII文字を表示する
3.GDTとIDTを初期化する
のが主な内容でした。
ところが…
====
起動情報を受け取るまではいいです。構造体の仕組みがわかります。
ASCII文字を表示するところで、筆者作のmakefontという自作ツールが登場しています。
面倒だったので、とりあえずLinux版のmakefontを使用しましたが、
./bim2hrb bootpack.bim bootpack.hrb 0
bim file format error
なぜエラーが…。
原因究明をするのもいいですが、自作ツールに翻弄されるくらいなら自分で作ります。
#!/usr/bin/python # -*- coding: utf-8 -*- # mkfont *** The Python Script making C source from font source file*** # Author : levelfour # Usage : ./mkfont [font src] (-o [c src file]) import sys def caution(msg) : print msg exit() usage = "usage: ./mkfont [font src] (-o [c src file])" error = "error: ./mkfont --help" data = [0] * (16 * 256) argv = sys.argv argc = len(argv) # show help if argv[-1] == "--help" : caution(usage) # too few argument if argc < 2 : caution(error) # wrong argument if argv[1] == "-o" : caution(error) # フォントソースファイルを開く src = open(argv[1], "r") # 出力するCソースファイルを開く if argc >= 4 : if argv[2] == "-o" : dst = open(argv[3], "w") else : dst = open("font.c", "w") i = 0 c = 0 # フォントソースファイルを一行ずつ読む for line in src : if i > 0 : tmp = 0 # 横一列の16進数データ j = 7 while j >= 0 : # "*"->1, "."->0に変換する if line[j] == "*" : tmp += (1 << (7 - j)) j -= 1 # 配列に順番に格納 data[c * 16 + (16 - i)] = tmp i -= 1 # 文字コードを取得 if line[0:4] == "char" : i = 16 c = int(line[7:9], 16) code = "char hankaku[4096] = {" i = 0 for d in data : if i != 0 : code += "," if i % 16 == 0 : code += "\n\t" code += (hex(d)) # 16進数データを文字列に変換 i += 1 code += "\n};" dst.write(code) src.close() dst.close()
Pythonで書いてみました。Pythonを勉強したかったついでです
スクリプトの詳細は割愛しますが、これでhankaku.txtを下のようなhankaku.cに変換できます。
char hankaku[4096] = { 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x38,0x44,0x82,0xaa,0xaa,0x82,0x82,0xaa,0x92,0x44,0x38,0x0,0x0,0x0, 0x0,0x0,0x38,0x7c,0xfe,0xd6,0xd6,0xfe,0xfe,0xd6,0xee,0x7c,0x38,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x6c,0xfe,0xfe,0xfe,0x7c,0x38,0x10,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x10,0x38,0x7c,0xfe,0x7c,0x38,0x10,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x10,0x38,0x54,0xfe,0x54,0x10,0x38,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x10,0x38,0x7c,0xfe,0xd6,0x10,0x38,0x0,0x0,0x0,0x0,0x0, ...(以下省略)
こいつをboot.cと一緒にコンパイルしてくっつけてあげれば、同じ動作になりますね。
ところが、前回の課題を覚えていますでしょうか。
そうです。静的変数、もっと言うとグローバル変数が使えないのです。
これでは、折角出力したフォントソースもグローバル配列なので、文字列も表示できません。
そこで、ちょっと緊急処置でフォント配列をローカルにおいて、とりあえず文字列を
表示して原因を考えることにしました。
ついでに、直後でsprintfが必要になるようなので、デバッグにもちょうど使えるし
作りました。
int lsprintf(char *str, const char *fmt, ...) { int *arg = (int *)(&str + 2); // 可変個引数の配列 int cnt, i, argc = 0; char buf[20]; const char *p = fmt; for(cnt = 0; *p != '\0'; p++) { if(*p == '%') { strcls(buf); // バッファの初期化 // フォーマット指定子の場合は引数の数値を文字列へ変換 switch(p[1]) { case 'd': int2dec(buf, arg[argc++]); break; case 'x': int2hex(buf, arg[argc++]); break; } // 変換した数値を生成文字列にコピー for(i = 0; buf[i] != '\0'; i++,cnt++) *str++ = buf[i]; p++; } else { // フォーマット指定子以外はそのままコピー *str++ = *p; cnt++; } } return cnt; } // ヌル文字で埋める void strcls(char *str) { while(*str != '\0') *str++ = '\0'; } // 数値を16進数文字列に変換する void int2hex(char *s, int value) { s[0] = '0', s[1] = 'x'; int i, filter = 0x0000000f; s += 2; for(i = 0; i < 8; i++) { if(((value >> (7-i)*4) & filter) >= 10) { *s++ = 'A' + ((value >> (7-i)*4) & filter) - 10; } else { *s++ = '0' + ((value >> (7-i)*4) & filter); } } *s = '\0'; } // 10進数valueのn桁目を返す int figure(int value, int n) { int i; for(i = 0; i < n-1; i++) value /= 10; return value % 10; } // 数値を10進数文字列に変換する void int2dec(char *s, int value) { int i; char zero = 1; for(i = 0; i < 10; i++) { if(zero && figure(value, 10-i) != 0) zero = 0; if(!zero) *s++ = '0' + figure(value, 10-i); } }
os/src/c/lib.cのほぼ全部の抜粋です。
とても読みづらいのは御愛嬌ですが、まあ特別にトリッキーなことをやっているとしたら
2行目のargですね。
Cで普通printfのような可変個引数を処理するには、stdarg.hのマクロを使って
処理しますが、勿論今回は標準ライブラリが使えません。
そこで、x86のスタックフレームの仕組みを逆手にとって可変個引数を取得します。
x86では、関数を呼ぶ前にまず、引数を逆順でスタックにプッシュします。
そして、関数を呼ぶときにcall命令を使います。call命令は、関数が終わったら引き続き
実行されるアドレス、つまりリターンアドレスをプッシュして関数にジャンプします。
ちなみにret命令はそのアドレスをポップして元の位置に戻ります。
そして、関数に入るとまず、%ebpの値をスタックに保存します。
今、第一引数strが図中の引数1です。そのアドレスに2を足すと、引数3のアドレスです。
これが、argの値です。
そうすることで、arg[0]、arg[1]、arg[2]、…で可変個部分の引数が取得できます。
引数のない関数で同様のことがしたい場合は、適当なローカル変数をつくって
同じようにアドレスを足すことで引数が取得できます。
そんなわけで、sprintfも駆使しながらデバッグした結果…
boot.cのエントリアドレスを0x00280000に設定すればよいだけでしたw
なんともあっさりな…
OUTPUT_FORMAT("binary")
ENTRY("Main")
SECTIONS {
. = 0x280000;
}
ついでに、Makefileのldのオプションがごちゃごちゃ多くて目障りなので、こっちに持ってきました。
さて、head.sでは%dsレジスタに1*8を代入していました。
これは、8日目の説明によるとセグメント番号1番を表すそうです。
セグメント番号1番とは…サイズ4GB、開始番地0x000000、システム専用の実行可能セグメントです。
つまり、%ds=1*8の状態では、(%ebx)みたいにアドレッシングモードを使うと
(%ds:%ebx)と解釈されますから、%ebxの値をそのままアドレスとして解釈します。
ところが、ldにエントリアドレスを示していなかったので、リンクしたときに
グローバル変数等のアドレスが再配置によって、Linuxのelfフォーマットに従った
アドレスに配置されます(多分…^^;)。
それが大体0x08080000辺りなので、グローバル変数の中身は空っぽも同然でした。
これは、そろそろELFも勉強しろという神のお告げかなw