セキュリティキャンプ受かりました
受かった
今年が年齢的に最後だということで出してたら通りました。やったぜ!
集中開発トラック(だっけ?)のOS開発ゼミです、同じゼミの人はよろしくお願いします。
恒例の応募シート晒し
GitHubに上げときます。全文は seccamp_apply_sheet.txt
を見てください。
応募の経緯と意気込み
「30日でできる!OS自作入門」をやって、今度はx64のOSを作ってみたくなったので、セキュリティキャンプに応募してみました。 募集が4月から始まって時間があったので、わりと練って書いたのですが、今見ると結構短いですね……10000字無かった……
友達や先輩がキャンパーで、キャンプの存在を身近に感じていたのも応募した要因の一つです。キャンパーの知り合いはみんないい経験だったと言っているので当日が楽しみです。
現在開発している自作OSは以下のリポジトリなのですが、
当日までに
- メモリ管理(ページング)
- 割り込み
- タスク管理
くらいは実装しておきたいですね。がんばり〼。
来年応募する人へ
応募シートですが、自分はただ闇雲に長く書くのではなく、凝縮して書くことを努めました。 審査員の方たちは何百通と応募シートを見るので、わかりやすい言葉遣いで、意図がしっかりと伝わる文章にするのが良いと思います。読みにくい文章は読むのが嫌になってしまいますからね。 学校の先生とかに見てもらうのもいいかもしれません。
プログラムの課題が出たときは、最低限の要件は満たしたほうがいいですが、できなくても、ここをこう試行錯誤したとか、ここはできなかったけど代わりにこの機能を追加したとか、とにかく食らいついてみるのがいいと思います。逆にできる人はもっと機能追加するとか、性能測定するとか(自分はこれですね)、いろいろやってみるといいかもしれません。
最後に
SecHackのページなので微妙に違いますが、多分似たところはあるので引用させていただきます。選考に関するコメントの部分です。
SecHack365で私を指名してくれたけど落ちてしまった人たちは、実力がなくて落ちたのではなく、相対的な競争や、この人はできれば今年ではなく来年以降に!ということで選にもれただけなのです。自信を持ってください。そして通過した人も自分よりも実力がありそうな人が落ちたということを忘れずに、是非努力をしてください(その人が「こんな成果が出たのなら自分が翌年に回されたのもしょうがないな」と思えるように)。
まさにこのとおりだと思います。これから夏にかけて頑張っていきます。
「30日でできる!OS自作入門」を今更ながらやってみる - 5日目
今日もOSがんばるぞい
4日目は こちら
5日目 - 構造体と文字表示とGDT/IDT初期化
1 - 起動情報の受け取り
ポインタを用いて asmhead.nas
の情報を受け取っていますね。それだけです。
2 - 構造体を使ってみる
構造体の登場です。構造体は変数をかたまりで扱える便利なものです。まあここも難しくないでしょう。
もし構造体のポインタの扱い方に混乱したら、また 3日目 を見直すか、本のコラムをじっくり読んでください。わからなくなったら基本に立ち返ることが重要です。
3 - 矢印表記を使ってみる
(*binfo).scrnx
と binfo->scrnx
は等価です。この本では ->
を積極的に使っていくようです。
おそらくこの ->
にはもっと深遠な機能があると思うのですが、自分はC言語ナニモワカラナイのでここで詳しくは解説しません。
4 - とにかく文字を出したい
ドットをちょんちょんちょんと打って「A」を描いています。気が遠くなりますね…
5 - フォントを増やしたい
OSASKに用いられているフォントデータとコンパイラを用いてフォントを導入しています。省略〜
6 - 文字列を書きたい
文字列を描いています。はい。
文字列の補足ですが、文字列は文字コードを順番にメモリに並べて最後に 0x00
をつけたもの…のようです。
7 - 変数の値の表示
GO(ジーオー)というコンパイラは sprintf
という関数を提供してくれます。
sprintf
関数は指定した文字列をメモリの中に生成する機能を持っています。これを使ってどこかの番地に文字列を保存しておいて、それを putfonts8_asc()
で読み出せば無事文字が表示されます。やったネ!
8 - マウスカーソルも描いてみよう
はい描くだけ〜カット!!!
9 - GDTとIDTを初期化しよう
ここまでで1000文字ですが、ここから多分3000文字くらい書くことになりそうです〜んご〜頑張るお
まず何がしたいかをはっきりさせておきましょう。私達は マウスカーソルを動かしたい のです。それには マウスが動いたときの信号をキャッチして、マウスカーソルの絵を動かせばいい のです。信号をキャッチするには IDT
を設置する必要があります。 IDT
を設定するには今度は GDT
なるものを設定する必要があります。こういう流れで GDT
と IDT
を設定する必要があるのです。それでは書いていきます〜
まずはセグメンテーションです。手っ取り早く言うと メモリを切り分けてそれぞれのブロックの最初の番地を0として扱える という機能です。今まで ORG 0x7c00
とか書いていましたが、セグメンテーションを設定すれば自動的に ORG 0
が設定されるというわけです。3日目 にセグメントレジスタというのがありましたが、今回もそれを使います、使いますが、あの頃はまだ16ビットモードでした。あの時 MOV AL,[SI]
は MOV AL,[DS:SI]
で、 DS
の16倍が SI
に足されていました。しかし今は32ビット、事情が違います。 MOV AL,[DS:EAX]
という時 AL
に代入されるのは、 DSの開始番地 + EAXの値
です。ご注意を。
セグメントレジスタを省略したら自動的に DS
が足されるというのは32ビットモードでも16ビットモードでも共通です。
そのセグメンテーションですが、セグメントを表すには次の情報が必要です。
- セグメントの大きさ
- セグメントの開始番地
- セグメントの管理用属性(書き込み禁止、実行禁止、システム専用etc.)
CPUではこれらのデータを64ビット(8バイト)で表していますが、セグメントを指定するためのレジスタは16ビットしかありません。じゃあできないのかと思いますがそんなことはありません。以下の図がわかりやすいですので、この図を参照しながら説明します。
まずセグメントレジスタにはセグメントの番号(一般にセグメントセレクタと呼ぶ)を格納します。セグメントレジスタとは別に、メモリ上に GDT(Global Descriptor Table)
というセグメントの必要情報(箇条書きで挙げた3つですね)とセグメントセレクタを記載したテーブルを用意します。そしてメモリの空き領域を都合よく区切ります。そしたらセグメントレジスタに格納したセレクタと GDT
のセレクタを紐づけて、更に GDT
のそれぞれの情報と合致するメモリ領域を紐づけます。
こうすると、セグメントセレクタを指定したら対応する GDT
の要素が指定され、 GDT
の要素と対応するメモリが指定される、というわけです。2段クッションがかまされているんですね。
セグメントセレクタの数ですが 0~8191
が扱えます。セグメントレジスタは16ビットですが、CPUの都合上、下位3ビットが使えないためです。ということは8192個の GDT
の要素が作れるわけで、つまり GDT
は 8192 * 8 = 65,536
バイト(64KB)扱えるわけです。
GDT
の作り方ですが、メモリの何処かにセグメントの情報をずらっと並べて、その先頭の番地と有効設定個数をCPUの GDTR
というレジスタに設定すればよいです。
IDT(Interrupt Descriptor Table)
は、割り込み番号とそれに対応した呼び出し関数の対応表です。まずは割り込みについて説明しましょう。割り込みとは、 CPU外部のデバイスが変化した時や内部エラーが起きたときに臨時の処理を発生させる機能 です。CPU外部のデバイスの変化とは、例えばキーボードが押されたとか、マウスがクリックされたとか、そういった類のことです。
なぜそういったものがあるかというと、CPUの処理スピードに比べて外部デバイスの変化が圧倒的に遅いからです。CPUは1秒間に100万回以上(今のCPUは1億回以上)動作します。それに比べればキーボードだって1秒に3回押されるくらいですし、マウスだって16連打が限界です。100万分の1秒とは次元が違うわけです。なので外部デバイスからの信号は、一種の例外として処理したほうが都合がいいのです。説明終わ〜り。
プログラムの解説は本を読んでください、わかると思います。ひとつだけ注意する点としては、ポインタの足し算です。 ポインタに足し算すると、もとの型のバイト分だけn倍されたものを足します。 何を言っているかわからないと思うので例を出しましょう。以下のポインタ型変数を宣言したとします。
int *p = (int *) 0x1000;
これに足し算をしていくと、結果は以下のようになります。
printf("+0 : %d\n", p); // +0 : 4096 printf("+1 : %d\n", p + 1); // +1 : 4100 printf("+3 : %d\n", p + 2); // +3 : 4108
int
型は4バイトなので、1足すと4バイト、2足すと8バイト増えていますね。 char
だと1バイトずつ、 short
だと2バイトずつ増えます。8バイトの構造体だったら8バイトずつ増えます。覚えておきましょう。
追記: どうやら型のサイズはCPUによって変わるようです。 ここ とか ここ などを参考にするとよい。
今日はここまで
ふー疲れましたね、それではまた明日〜
「30日でできる!OS自作入門」を今更ながらやってみる - 4日目
さてさて今日もOSやるぞ
3日目は こちら
4日目 - C言語と画面表示の練習
1 - C言語からメモリに書き込みたい
ここはまだアセンブリですね。プログラムをみると以下のようになっています。
_write_mem8: ; void write_mem8(int addr, int data); MOV ECX,[ESP+4] ; [ESP+4]にaddrが入っている MOV AL,[ESP+8] ; [ESP+8]にdataが入っている MOV [ECX],AL RET
コメントをみるとわかると思いますが、 void write_mem8(int addr, int data);
のようなC言語の関数と対応しています。ここでやりたいことは、 addr
番地に data
を書き込むことです。
ここでC言語の引数とアセンブリの対応ですが、
第一引数 : [ESP+4] 第二引数 : [ESP+8] 第三引数 : [ESP+12] ...
となっております。
現在32ビットモードでプログラムを組んでいるので積極的に32ビットレジスタを使っています。16ビットレジスタを使うこともできるのですが、機械語のバイト数が増え、実行速度も遅くなるらしいです。
C言語と連携する際には、自由に使ってもいいレジスタが制限されていて、 EAX
, ECX
, EDX
の3つだけです。他のレジスタは読み込みはOKですが書き込みはだめなので注意です。
C言語側のプログラムは簡単ですな。 0xa0000 ~ 0xaffff
番地に 15
を書き込んでいます。
さてどうでしょう、みなさんは画面が真っ白になったでしょうか?
2 - しましま模様
AND演算が登場です。AND演算は2進数の世界でよく出てきます。こんな感じの演算です。
11111111 AND 10010001 = 10010001
筆算するとこんな感じです。
11111111 AND) 10010001 ------------- 10010001
...法則性がわかったでしょうか?
AND演算は 同じ桁どうしを比べて、両方1だったら1、それ以外は0を出力する演算 です。コンピュータの世界ではよく出てくるので要チェックです。
本ではこれを使って縞模様を出力していますね。しましまになりましたか?
3 - ポインタに挑戦
さて幾多のプログラミング入門者を苦しませてきたポインタです。ポインタはアセンブリと密接な関係があるので、アセンブリと比較しながら見ていきましょう。
まずC言語にはメモリの番地に直接データを書き込むための表現があります。つまり以下のことをする表現です。
MOV [0x7ffc],0x0f ; メモリの0x7ffc番地にデータ0x0fを書き込む
これをC言語で表現しようとすると以下のようになります。
char *p; p = (char *) 0x7ffc; *p = 0x0f;
ゆっくり見ていきましょう。まず char *p;
があります。どうやら変数宣言のようですが、変数 p
の前に *
がついています。この記号で 変数 p
はメモリの番地 を表すことになります。つまり、この p
をいじることによって、メモリのとある番地のデータを読み込んだり、これまたメモリのとある番地にデータを書き込んだりすることができるのです。
しかしなぜ宣言に char
を用いているのでしょうか。というのもアセンブリとC言語に対応があって、以下のようになっています。
char *p; // BYTE用番地の場合 short *p; // WORD用番地の場合 int *p; // DWORD用番地の場合
今回は1バイト分のメモリ番地を指定したかったので char
を用いています。
次に p = (char *) 0x7ffc;
を見ましょう。これは p
にメモリの番地を指定する作業 です。変数 p
を宣言してメモリ内のデータを操りたいのですが、メモリのどの番地を操作するのかまだ指定してませんでした。ということでここで(例として) 0x7ffc
を p
に指定しています。注意する点として、メモリの番地を指定するときは *
はつけません。ここテストで出ます。 (char *)
でキャストらしきことをしているのは、Cコンパイラではどうやら普通の数値とメモリの番地用の数値を区別するようで、警告が出るのでそれを回避するためです。
最後に *p = 0x0f;
です。これは p
に指定されたメモリ番地にデータを書き込むこと を表現しています。今回の場合は 0x7ffc
番地に 0x0f
を書き込んでいますね。注意ですが、データを書きこむときは *
をつけます。ここもテストで出ますので注意。
4 - ポインタの応用(1)
for (i = 0; i <= 0xffff; i++) { *(p + i) = i & 0x0f; }
これは p + i
番地にデータ i & 0x0f
を書き込んでいますね。for文で i
が1つずつ増えていくので実はさっきとやっていることは変わりません。
5 - ポインタの応用(2)
for (i = 0; i <= 0xffff; i++) { p[i] = i & 0x0f; }
p[i]
という表現は *(p + i)
と完全に同じです。なので p[i] == i[p]
なのです…と言われても納得行かないかもしれません。でも自分の力ではこれ以上説明できそうにありません(泣) この本 とか読むといいんじゃないでしょうか。申し訳ない(汗)
6 - 色番号設定
色番号の章ですが、色番号の解説は割愛します。これはただの決まりですもんね。
さてこの章では、値をまとめて宣言する方法が紹介されます。 C言語の特性ですが、
char a[3];
と書くと、アセンブラでは
a: RESB 3
に相当します。でも微妙にアセンブラとC言語では違いがあって、 RESB
では0で埋めてくれましたが、C言語の書き方だと0で埋めてくれなくて、中にゴミデータが入っているかもしれませんので注意です。そのため、データの初期値を設定することもできます。
char a[3] = { 1, 2, 3 };
これは以下の表記とほぼ同等です。
char a[3]; a[0] = 1; a[1] = 2; a[2] = 3;
ここで注目してほしいのが、 a
が最初からポインタとして認識されていることです。上のアセンブラを見てもらえるとわかるのですが、 a
はラベルで、つまりメモリのどこかの番地を指しているわけです。
今回のプログラムだと、上の方法では48個の値を MOV
しなくてはなりません。これは(機械語的に)大変なので DB
命令で一気に指定したいところです。C言語では DB
命令に相当する表記があって、それが static
です。今回のプログラムでは static
を宣言の前につけていますね。
次に bootpack.c
の set_palette()
を解説します。
CPUにはメモリ以外にも沢山のデバイスが接続されていて、そこに命令を送るのが OUT
命令と IN
命令です。この命令は装置番号(portといいます)を指定してデータを送ったり受け取ったりします。この命令はC言語では書けないのでアセンブラで書く必要があります。
さてどんなことをすればいいのでしょうか。本によると、以下の操作をすれば良さそうです。
- 割り込みを禁止する
0x03c8
に設定したいパレット番号を書き込み、続いてR,G,Bの順に0x03c9
に書き込む- パレット状態の読み込みは、
0x03c7
にパレット番号を書き込み、続いて0x03c9
を3回読み出す - 割り込み状態をもとに戻す
割り込み禁止処理と割り込み許可処理ですが、 CLI
と STI
が登場します。 CLI
というのはclear interrupt flagの略で、割り込みフラグを0にして割り込みを禁止する処理です。 STI
というのはset interrupt flagの略で、割り込みフラグを1にして割り込みを動作させる処理です。
割り込みが何かって?その説明はちょっと先になります。もう少し我慢。
次に EFLAGS
という特別なレジスタの説明です。
簡単に言うといろんなフラグが詰まったレジスタで、キャリーフラグや割り込みフラグもここに入っています。キャリーフラグは JC
とか JNC
などで簡単に中身が調べられるのですが、割り込みフラグはそういう命令がないので、 EFLAGS
を読み込んでから第9ビットが0か1かチェックするしかないのです。
EFLAGS
もC言語からは操作できないのでアセンブラでがんばります。 naskfunc.nas
へ移りましょう。
ここで私達がやりたいのはつまり MOV EAX,EFLAGS
なわけですが、これはできません。そういう命令がないのでしょうがないです。なのでスタックを使う必要があります。スタックって何って?それはまた後日。
PUSHFD
と POPFD
命令が初登場です。それぞれ、push flags double-wordとpop flags double-wordの略で、フラグをスタックに押し込む/取り出すという意味です。つまり、
_io_load_eflags: PUSHFD POP EAX RET
というのは、スタックに PUSHFD
で ELFAGS
を押し込んで、 POP
で飛び出してきたところを EAX
に代入しているのです。
また値を返す関数ですが、C言語の規約では RET
したときに EAX
に入っていた値が戻り値としてみなされます。要チェックです。
7 - 四角形を描く
いままでのことを総動員して絵を描いていますね。特に難しそうなところはなさそうですね。
8 - 今日の仕上げ
根気よく絵を描いています。これ大変そうだなー
今日はここまで
お疲れ様でした〜それではまた明日〜
5日目は こちら
「30日でできる!OS自作入門」を今更ながらやってみる - 3日目
今日も今日とてOSやるぞ
2日目は こちら
3日目 - 32ビットモード突入とC言語導入
今日は重いですね…頑張ってまとめます。
1 - さあ本当のIPLを作ろう
さて書き足された部分のまとめをしていきます。
フロッピーディスクの構造を学ぼう
INT 0x13
は前回やったとおり割り込み命令ですね。
INT : ソフトウェア割り込み命令のこと これだけだと何がなんだかわかりませんが、今のところはBIOSの関数呼び出しの一種だと思っておけばいいらしいです。 BIOSというのは「basic input output system」の略です。いかにも画面に文字表示したりキーボード入力できそうな雰囲気ですね。これを用いてハロワを表示させていたんですね〜
「30日でできる!OS自作入門」を今更ながらやってみる - 2日目 - 無限遠まで突撃中
さて何をさせるのでしょうか。本によると
ディスクの読み込み、書き込み、セクタのベリファイおよびシーク
らしいです。パラメータの解説をします。
AH
: モードの選択AL
: 処理するセクタ数CH
:シリンダ番号 & 0xff
CL
:セクタ番号(bit0-5) | (シリンダ番号 & 0x300) >> 2
DH
: ヘッド番号DL
: ドライブ番号ES:BX
: バッファアドレス(ベリファイ時とシーク時は参照しない)- 戻り値 :
FLAGS.CF == 0
: エラーなし、AH == 0
FLAGS.CF == 1
: エラーあり、AH
にエラーコード(リセットファンクションと同じ)
今回は AH = 0x02
でディスクの読み込みです。
まずは戻り値です。
CF
というのは「キャリーフラグ」というもので、1ビット記録するレジスタです。本来はキャリー状態というものを記録するのですが、扱いやすいのでいろいろ使われるらしいです。
ちなみにキャリーというのは carry 、つまり けた上げ のことです。計算していてレジスタが桁あふれしたときにキャリーフラグがぴょこっと1になることでうまいこと計算ができるんですね〜大学の論理回路の授業でやりました。詳しい解説は この方の記事 を読むといいです。(キャリーフラグはどうやら符号なし演算のときに使うらしいですね)
さて、残りを解説するにはフロッピーディスクの構造を説明しなくてはいけません。以下の図を見てください。
この図はフロッピーディスク内部の磁気ディスクの構造です。円筒状にシリンダ(図ではトラック)が配置されており、セクタという区分で分割統治されていて、磁気ヘッドから読み込みます。シリンダは全部で80あり、セクタは各シリンダ内に18あります。磁気ヘッドは表と裏で2つあります。今の解説で上のパラメータは大体わかりましたね。
ドライブ番号というのは、フロッピーディスクドライブが沢山つながっている場合に識別するための番号です。ドライブが1つの場合は0番を指定します。
残りはバッファアドレスですね。これは メモリのどこに読み込むか を表すものです。なぜ2つのレジスタで表すのでしょう?というのも 前回 を思い出してほしいのですが、 BX
というレジスタは16ビットでしたね。ということは、 BX
一つだけだと 0x0000 ~ 0xffff
までしか、つまり64KBまでしか表すことができないのです。
これを回避するために2つレジスタを用いてメモリの番地を表します。具体的にはバッファアドレスは ES * 16 + BX
で表されます。 ES
でおおざっぱに指定してから BX
で細かく指定、という感じですね。これで 0xffff * 16 + 0xffff = 1,114,095
バイト、つまり約1MB使えます。やったー。
セグメントレジスタ
ところで先程登場した ES
レジスタですが、これはセグメントレジスタと呼ばれるレジスタです。末尾に S
がつくレジスタですね。セグメントとは何ぞやと思ったので調べてみました。
セグメント方式 (memory segmentation)は、メモリ管理の方式の一つ。プログラムやデータをセグメントまたはセクションという「可変な」大きさのまとまりで管理する。セグメントは、メモリ空間上で、情報の属性などによって分類されたグループである。セグメント方式でメモリ位置を参照するには、セグメントを識別する値とセグメント内のオフセットを指定する。 (Wikipediaより)
なんだか難しくてよくわかりませんが、つまりセグメントレジスタでメモリをおおざっぱに区切っているということなのかな?(違うよ!ということであればコメントください)
大事なのは最後の文章で、「メモリ位置を参照するには、セグメントを識別する値とセグメント内のオフセットを指定」とあります。さっきの例だと、セグメントを識別する値とは ES
のことで、セグメント内のオフセットとは BX
のことなのでしょう。
メモリの番地指定の裏設定
ここで新事実が発覚。なんと今までメモリの番地指定では、実は暗黙的に DS
がセグメントレジスタとして指定されていたんだよ!!!(ΩΩΩ < ナ、ナンダッテー!?)
つまり我々が
MOV CX,[1234]
だと思っていたのは、実は
MOV CX,[DS:1234]
だったのです。この理由から、 MOV DS,0
の初期化が必要だったのです。(0以外にすると番地の計算がややこしいことになりますから…)
2 - エラーになったらやり直そう
AH = 0x00
、 DL = 0x00
で INT 0x13
してやると、ドライブのシステムリセットが走るようです。
3 - 18セクタまで読んでみる
4 - 10シリンダ分を読み込んでみる
特に難しいところはないので省略!!!
5 - OS本体を書き始めてみる
ここでやることは、 OSのプログラムが保存されているメモリ番地にブートセクタからジャンプする ことです。そのためにはまずOSのプログラムがどの番地に保存されているか調べる必要があります。とりあえず普通にコピーしてバイナリエディタで覗いてみましょう。
…
本によって進めると、空ディスクにファイルを保存すると
- ファイル名は
0x002600
以降に入る - ファイルの中身は
0x004200
以降に入る
ということがわかりました。ブートセクタから 0x004200
に飛べばいいですね。
6 - ブートセクタからOS本体を実行させてみる
ブートセクタからOS本体に飛ばす処理をしていますね。
7 - OS本体の動作を確認してみる
真っ黒画面表示です。画面関係なので INT 0x10
で割り込みさせればいいですね。
パラメータは以下のとおりです。
AH = 0x00
AL = モード
- 戻り値 : なし
8 - 32ビットモードへの準備
C言語で開発したいので32ビットモードへの移行を目標にします…が32ビットモードではBIOSの機能が使えなくなります。(BIOSは16ビットモードを前提に書かれているので…)なのでI/O関係のことは今のうちにやっておきましょう。具体的にはキーボードの状態をBIOSから教えてもらいます。
AH = 0x02
: シフトフラグステータス取得
詳細は ここ で調べました。
あとVRAMはメモリマップの色々なところに散らばっているらしいです。びっくりしないようにしましょう。
9 - ついにC言語導入へ
asmhead.nas
に100行くらい書き足されたようです…が今はその内容は明かされません。今後の解説に期待ですね。
C言語で書かれたOS本体は簡単ですね。割愛します。
さてこのcファイルをコンパイルして ipl10.nas
や asmhead.nas
なんかとリンクするわけなんですが、そこら辺は筆者であるKさんが用意した(のを先人が整備した)ツールが大活躍です。あまり気にせず進められるでしょう…と言いたいのですが、一つ悩んだのがあって、hikalium氏のツールの使い方で1時間位つまりました。ちょっと分かりづらいのでここで共有しておきます。
z_tools
内の haritol concat
の使い方ですが、
copy hoge+fuga piyo
は、
haritol concat piyo hoge fuga
と対応しているようです。参考にしてください。
10 - とにかくHLTしたい
C言語ではHLTできないのでアセンブリでHLTする関数を書きます。少し気になったところなのですが、 naskfunc.nas
の
[SECTION .text]
ってリンカスクリプトの .text
セクションと同じなんですかね?知っている人、教えてくださいm(-_-)m
あと内部のリンクの仕組みがまだわからないですね…分かり次第追記します。
今日はここまで
お疲れ様でした。また明日〜
4日目は こちら
「30日でできる!OS自作入門」を今更ながらやってみる - 2日目
今日もやるぞOSやるぞ
1日目は こちら
2日目 - アセンブラ学習とMakefile入門
1 - まずはテキストエディタの紹介
2 - さて開発再開
また新たな命令が出てきました。
ORG
: 機械語が実行時にメモリのどの番地にロードされるか
例:ORG 0x7c00
で0x7c00
番地からスタート
GAS(GNU Assembly)なんかだとORG
命令がないのでリンカスクリプトで指定してあげます。
こんな感じ↓
SECTIONS { . = 0x7c00; //「.」は現在の番地を表します。 }
JMP
:JMP 0x3cf5
とかJMP loop
みたいな感じで使う
メモリの番地かラベルまでジャンプするMOV
:MOV SS,AX
でSS = AX
と同等の作用(SSにAXを代入)
ゆかいなレジスタたち
16ビットレジスタくんたちです。
AX
: アキュムレータ(累積計算機という意味)
各種演算に使うと効率がいいCX
: カウンタ
回数を数えるときに使うと効率がいいDX
: データBX
: ベース
メモリの番地計算の起点に使うと効率がいいSP
: スタックポインタBP
: ベースポインタSI
: ソースインデックス(読み込みインデックス)DI
: ディスティネーションインデックス(書き込みインデックス)
レジスタには上のような役割があるらしい。何も書いていないものは今はわからないものです。分かり次第追記します。
次は8ビットレジスタくんたちです。
AL
: アキュムレータロウCL
: カウンタロウDL
: データロウBL
: ベースロウAH
: アキュムレータハイCH
: カウンタハイDH
: データハイBH
: ベースハイ
16ビットレジスタに名前が似ているのは、たとえば AX = 0x3c21
だった場合、 AL = 0x21
, AH = 0x3c
のように、 AX
の下位8ビットが AL
に、 AX
の上位8ビットが AH
に入るらしいです。
ところで8ビットの各種演算の時は AL
と AH
のどちらを使うとより効率がいいのでしょうかね?わかったら追記します。
32ビットレジスタとセグメントレジスタの解説についてはまた今度。
メモリは[]で囲もう
NASKではメモリの番地を指定したい場合は次のように書きます。
MOV SI,0x21 MOV AL,[SI]
これは レジスタ AL
に、メモリ SI
番地に保存されている値を書き込みなさい という意味です。今回の場合は 0x21
番地に保存されているデータが AL
に代入されます。
メモリは[]で囲むと良さそうです。
リトルエンディアンで行こう
本によるとどうやらメモリには8ビットずつの塊でデータが保存されるらしいです。ここで問題なのがその保存方式です。こんな感じで保存されます↓
(Wikipediaより)
これはつまり、 MOV [678],0x007f8400
みたいにすると、 678
番地に 0x8400
が、 679
番地に 0x007f
がそれぞれ保存される 、ということです。
この保存方式を巷では「リトルエンディアン方式」なんて言うみたいです。詳しく解説はしないので気になった人はググってください。
INT命令とBIOS
新たな命令です。
INT
: ソフトウェア割り込み命令のこと
これだけだと何がなんだかわかりませんが、今のところはBIOSの関数呼び出しの一種だと思っておけばいいらしいです。
BIOSというのは「basic input output system」の略です。いかにも画面に文字表示したりキーボード入力できそうな雰囲気ですね。これを用いてハロワを表示させていたんですね〜
一文字を表示させるためには以下のように設定すればよいそうです。
AH = 0x0a
AL = キャラクタコード
BH = 0
BL = カラーコード
- 戻り値: なし
- 注: ビープ、バックスペース、CR、LFは制御コードとして認識される
以上を設定して INT 0x10
とすると文字が出ます。
出たよ0x7c00
ここのプログラムでは ORG 0x7c00
と設定していますが、これは
0x7c00 ~ 0x7dff
が、ブートセクタが読み込まれるアドレス
だからです。
なんで 0x7c00
なのかは この記事 に詳しく書いてあります。
3 - ブートセクタだけを作るように整理
コードを分割するだけなので略!!!
4 - 今後のためにMakefile導入
とくに難しいことはないので省略!!!(一般生成規則などややこしいものが出てきたら解説入れます)
今日はここまで
お疲れ様でした。また明日!
3日目は こちら
「30日でできる!OS自作入門」を今更ながらやってみる - 1日目
やるぞやるぞOSやるぞ
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
メモ書きなので読みにくいかも…
まずは環境構築
ArchLinuxで行いました。Linuxユーザーならば読み替え可能です。
hikalium氏のツールを用います。以下リンク
https://hikalium.com/pages/note/20161202.html
次のようにディレクトリを配置します。
haribote/ | |--z_tools/ | |--mihon_harib27f/ | |--01_day/ | |--02_day/ | |--...
詳細は上のリポジトリを見てほしいのですが、
z_tools
はz_tools_linux
をリネームしたものmihon_harib27f
は見本用の完成ディレクトリ
です。
1日目
1 - とにかくやるのだぁ
バイナリエディタでがんばります…が自分はカット。以前Windows環境で挑戦したときにがんばったのでね。ゆるして
2 - 結局何をやったのだろうか?
CPUはONとOFF、つまり0と1しか認識できません。なのでCPUに読み込ませるデータも0か1でなくてはいけません。バイナリエディタは2進数編集機と訳されるわけで、前の章では直接0と1を書いてたわけなんですね。
ちなみに
- 頭に
0x
は16進数 - 頭に
0b
は2進数
であることが多いです。
3 - アセンブラ初体験
バイナリでOSを書くのはいくらなんでも大変なので、アセンブリ言語でOSを書きましょう。Wikipediaによると、アセンブリ言語とは、
アセンブリ言語(アセンブリげんご、英: assembly language)とは、コンピュータ、マイクロコントローラ、その他のプログラム可能な機器を動作させるための機械語を人間にわかりやすい形で記述する、代表的な低水準言語である。なお、英語の assembly とは「組立」という意味である。
と解説がされています。
わかりやすく言うと機械語(バイナリ)と一対一対応する言語です。
さてNASKの命令が出てきたので解説です。といっても本に書いてあることをまとめただけですが…
DB
: ファイルの内容を1バイトだけ直接書く命令RESB
:RESB 10
で10バイト分0x00
で埋める命令
helloos.nas
のアセンブルの仕方ですが、 z_tools/
内に nask
というアセンブラがあるので、 01_day/
ディレクトリから
$ ../z_tools/nask helloos.nas helloos.img
でアセンブルできます。
4 - もうちょっと書き直してみる
新たな命令と文法です。
DW
: ファイルの内容を2バイトだけ直接書く命令DD
: ファイルの内容を4バイトだけ直接書く命令$
: 先頭から何バイトか示す変数
あとは本を読んでくれれば大丈夫だと思います。
今日はここまで
お疲れ様でした、明日もがんばりましょう。
2日目は こちら
「30日でできる!OS自作入門」を今更ながらやってみる - 目次
やるぞ
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る