30日OS自作本22日目3ウインドウAPI
22日目3記事目です。
ウインドウAPIを作ります。APIから値を戻すのにややトリッキーなことをやっているので、そこを整理して見ました。
私のこの記事はやや分かりにくいですが、ここは各自紙やエクセルにレジスタやスタックの内容を書き起こしながら頭の中で動かすことをお勧めします。
ウインドウAPIの処理を追ってみる
EDX=5
EBX=ウインドウのバッファ
ESI=ウインドウのx方向の大きさ
EDI=ウインドウのy方向の大きさ
EAX=透明度
ECX=ウインドウの名前呼び出し後はこんな値が返されます
EAX=ウインドウを操作するための番号(リフレッシュとかに必要)
これを作ろうと思ったら、新しい問題にぶつかりました。それは、APIが呼ばれた後にレジスタに値を入れてアプリに返す方法を考えていなかった、ということです。
この問題ですが、結構変な解決策を取っており、出来上がったAPIの動きが難解で私の4bit脳味噌で理解できなかったので、スタックの値を中心に脳内ダンプしながら動きを追っていきます。
winhelo.c
以下のスタックの状態でapi_openwin
を呼び出す
//定義 int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title); //呼び出し win = api_openwin(buf, 150, 50, -1, "hello");
スタックの状態 no,stack 4,buf 8,xsiz=150 12,ysiz=50 16,col_inv=-1 20,title="hello"
api_openwin
呼び出し直後にスタックにレジスタの値を退避する
PUSH EDI PUSH ESI PUSH EBX
スタックの状態
no | stack |
---|---|
4 | edi_old |
8 | esi_old |
12 | ebx_old |
16 | buf |
20 | xsiz |
24 | ysiz |
28 | col_inv |
32 | title |
レジスタにwinhelo.cから渡された引数を入れる,edx=5とする
edx=5はhrb_apiでのウインドウのAPIに分岐するための代入
MOV EDX,5 MOV EBX,[ESP+16] ; buf MOV ESI,[ESP+20] ; xsiz MOV EDI,[ESP+24] ; ysiz MOV EAX,[ESP+28] ; col_inv MOV ECX,[ESP+32] ; title
レジスタの状態(popad/pushadで実行される順に表記を並べてある)
regster | value |
---|---|
edi | ysiz |
esi | xsiz |
ebp | |
esp | |
ebx | buf |
edx | 5 |
ecx | title |
eax | col_inv |
スタックの状態※変化なし
no | stack |
---|---|
4 | edi_old |
8 | esi_old |
12 | ebx_old |
16 | buf |
20 | xsiz |
24 | ysiz |
28 | col_inv |
32 | title |
asm_hrb_api
レジスタに値を代入した状態で_asm_hrb_api(int 0x40)を呼ぶ
各レジスタの退避
PUSH DS PUSH ES PUSHAD ; 保存のためのPUSH
レジスタの状態※変化なし
regster | value |
---|---|
edi | ysiz |
esi | xsiz |
ebp | |
esp | |
ebx | buf |
edx | 5 |
ecx | title |
eax | col_inv |
スタックの状態 ※面倒なので番号は振っていません。
stack |
---|
ysiz |
xsiz |
buf |
title |
col_inv |
es |
ds |
edi_old |
esi_old |
ebx_old |
buf |
xsiz |
ysiz |
col_inv |
title |
hrb_apiを呼ぶ準備。セグメントレジスタと引数のpush
PUSHAD ; hrb_apiにわたすためのPUSH MOV AX,SS MOV DS,AX ; OS用のセグメントをDSとESにも入れる MOV ES,AX
レジスタの状態,ESとDSのみ変化(セグメントレジスタなのでここには表記しない)。AXは一時領域としての使用なので追いかけるような質のものではなさそう。
regster | value |
---|---|
edi | ysiz |
esi | xsiz |
ebp | |
esp | |
ebx | buf |
edx | 5 |
ecx | title |
eax | OSのセグメント |
スタックの状態
stack |
---|
ysiz |
xsiz |
buf |
title |
col_inv |
ysiz |
xsiz |
buf |
title |
col_inv |
es |
ds |
edi_old |
esi_old |
ebx_old |
buf |
xsiz |
ysiz |
col_inv |
title |
hrb_api
edx=5=ウインドウのAPIに分岐してウインドウ関連の処理をする。 ちなみにwindow関連の処理はこんな感んじで定義されている。
} else if (edx == 5) { sht = sheet_alloc(shtctl); sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax); make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0); sheet_slide(sht, 100, 50); sheet_updown(sht, 3); /* 3という高さはtask_aの上 */ reg[7] = (int) sht; }
スタックの16番目の位置に戻り値として確保したシートのアドレスを格納する
struct SHEET *sht; // ・・・ int *reg = &eax + 1; //regをスタックの9番目の値へのポインタとして宣言 //eax=8番目の引数のためeax+1=スタックの9番目のポインタとなる ・・・ sht = sheet_alloc(shtctl); //shtに確保したシートのアドレスを代入 ・・・ reg[7] = (int) sht; //スタックの16番目のアドレスに確保したシートのアドレスを代入
stack |
---|
ysiz |
xsiz |
buf |
title |
col_inv |
ysiz |
xsiz |
buf |
title |
sht |
es |
ds |
edi_old |
esi_old |
ebx_old |
buf |
xsiz |
ysiz |
col_inv |
title |
再びasm_hrb_apiに戻り戻り値(EAX)にshtが来るように操作する
CALL _hrb_api ;これが終わった後。。。 CMP EAX,0 ; EAXが0でなければアプリ終了処理 JNE _asm_end_app ADD ESP,32 ;ESPの値を8進める
ADD ESP,32
これで多分スタックの上から8個目まで、データを捨てるような動きになる。
この時のスタックの値
stack |
---|
ysiz |
xsiz |
buf |
title |
sht |
es |
ds |
edi_old |
esi_old |
ebx_old |
buf |
xsiz |
ysiz |
col_inv |
title |
この時にPOPADをすると、
POPAD POP ES POP DS
regster | value |
---|---|
edi | ysiz |
esi | xsiz |
ebp | |
esp | |
ebx | buf |
edx | |
ecx | title |
eax | sht |
となりeaxにaipを実行した結果shtを格納できるというわけです。
非常に疲れました。
22章で作成するAPI
結局本章ではAPIを3種類作っています。
新しいウインドウを表示する EDX=5 EBX=ウインドウのバッファ ESI=ウインドウのx方向の大きさ EDI=ウインドウのy方向の大きさ EAX=透明色 ECX=ウインドウの名前 ウインドウへの文字表示 EDX=6 EBX=ウインドウの番号 ESI=表示位置のx座標 EDI=表示いちのy座標 EAX=色番号 ECX=文字列の長さ EBP=文字列 四角を描く EDX=7 EBX=ウインドウの番号 EAX=x0 ECX=y0 ESI=x1 EDI=y1 EBP=色番号
現時点でウインドウを作成するAPIはシートの高さ、位置を渡せず、APIの中で決め打ちになっているので
- 好きな位置・高さに出す
- 複数ウインドウを出す
ができなくなっています。
新規に3つ渡すにはレジスタが足りないので構造体を作ってそのアドレスを渡す感じに改善されるのかな〜と予想してみます。
30日OS自作本22日目2hrbファイルの構造
.hrbファイルの構造とcmd_app()の改善
アプリケーションから利用できる文字表示APIを作ったところ、C言語から作ったアプリケーションでは データ(文字列)の取得がうまくいかないようです。本章では.hrbファイルの解説と件のデータ取得の改善が解説されています。
hrbファイルの解説
hrbファイルはそもそもharibote OSのオリジナルの実行ファイルです。 本章で多分はじめてhrbファイルの仕様が公開されます(多分)
- 0x0000,OSに用意してもらうアプリ用のデータセグメントの大きさ
- 0x0004,"Hari"(.hrbファイルであることのマーク)
- 0x0008,データセグメント内の予備領域の大きさ
- 0x000C,ESPの初期値&データ部分の転送先番地
- 0x0010,hrbファイル内部のデータ部分の大きさ
- 0x0014,hrbファイル内にあるデータ部分がどこから始まっているのか
- 0x0018,0xe90000000
- 0x001c,アプリの実行開始番地 - 0x20
- 0x0020,malloc領域開始アドレス
hello4.hrbの例
00000000 00 10 00 00 48 61 72 69 00 00 00 00 00 04 00 00 |....Hari........| 00000010 0e 00 00 00 64 00 00 00 00 00 00 e9 39 00 00 00 |....d.......9...| 00000020 10 04 00 00 55 89 e5 68 00 04 00 00 e8 13 00 00 |....U..h........| 00000030 00 58 c9 e9 1a 00 00 00 ba 01 00 00 00 8a 44 24 |.X............D$| 00000040 04 cd 40 c3 53 ba 02 00 00 00 8b 5c 24 08 cd 40 |..@.S......\$..@| 00000050 5b c3 ba 04 00 00 00 cd 40 55 89 e5 5d e9 c2 ff |[.......@U..]...| 00000060 ff ff 00 00 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |....hello, world| 00000070 0a 00 |..|
hello4.hrbのデータと対応づけてみました。
addr | data |
---|---|
0x0000 | 0x00100000 |
0x0004 | 0x48617269 |
0x0008 | 0x00000000 |
0x000C | 0x00040000 |
0x0010 | 0x0E000000 |
0x0014 | 0x64000000 |
0x0018 | 0x000000E9 |
0x001c | 0x39000000 |
0x0020 | 0x10040000 |
0x0024 | 0x5589E568 |
例えば
addr | data |
---|---|
0x0014 | 0x64000000 |
0x0014,hrbファイル内にあるデータ部分がどこから始まっているのか
ですが、hello4.hrb
ファイルの0x64の位置からhello, world
のデータが格納されているのがわかります。
またdの書かれているアドレスが0x64+0xE=0x72
であり、ファイル終端までがデータという扱いにしていることもわかります。
コマンド用の実行ファイルをCのソースから作る際、文字列がうまく参照できないようになっているようです。それをこちらのファイルフォーマットを参考に、cmd_app関数で、0x0014
の位置に格納されている文字データをこのような感じでセグメント内ESP(0x000C
)のスタックに転送しています。
/* ファイルが見つかった場合 */ p = (char *) memman_alloc_4k(memman, finfo->size); ・・・中略・・・ esp = *((int *) (p + 0x000c)); //ESPの初期値&データ部分の転送先番地 dathrb = *((int *) (p + 0x0014)); //hrbファイル内にあるデータ部分がどこから始まっている ・・・中略・・・ q = (char *) memman_alloc_4k(memman, segsiz); for (i = 0; i < datsiz; i++) { q[esp + i] = p[dathrb + i]; }
また.hrbファイルから得られる情報であるセグメントサイズの大きさ、データ部分の大きさもOSの改善に役立てています。
datsiz = *((int *) (p + 0x0010)); //hrbファイル内部のデータ部分の大きさ segsiz = *((int *) (p + 0x0000)); //OSに用意してもらうアプリ用のデータセグメントの大きさ
セグメントは今まではとりあえず64KBとなっていますが、こちらを参照することでセグメントの利用効率が上がります。
//.hrbファイル中のセグメントサイズを使うように改善。 //q = (char *) memman_alloc_4k(memman, 64 * 1024); q = (char *) memman_alloc_4k(memman, segsiz);
感想
アプリケーションのデータを参照するために、なぜこのような手間がかかるのか理解できませんでした。著者の自作ツール(bim2hrb)を利用されていない方のブログなどを見ると特にこの作業は不要そうです。bim2hrb等は普通のリンカに必要とされる何かが抜けているのでしょうか?まぁ私リンカがなんたるかもそんなにわかっていないので特にこの辺を探求するのはまだ早いかなと思いました。
次回はウインドウAPIについてです。
30日OS自作本22日目1セキュリティ編
22日目の内容になります。
長すぎるので3つに分けます。 3日目以降でやや難易度が高くなっている章だと思われます。
今回はセキュリティ編です。
バグ発見のための拡張
- スタック例外の実装
- スタック例外(配列に対して大きすぎるインデックス等)は0x0cでハンドルされる。ここに割り込みハンドラを登録すればOK
- QEMUではうまく動かないようだ
- 強制終了の実現方法
- bashのctrl + c 的なやつ
- 強制終了(asm_end_app)の命令を作成し、この命令をtask.eipに書き込む
- 本書ではctrl + F1を押したタイミングで発動
_asm_end_app, ; EAXはtss.esp0の番地 MOV ESP ,[EAX] MOV DWORD [EAX+4] ,0 POPAD RET ; cmd_appへ帰る
- 元のESPをEAXに代入した状態で呼びます。
- このサブルーチン内でESPや各レジスタの値を退避してあるスタックからPOPADで戻してreturn(呼び出し元cmd_appへ戻る)します。
個人的にはバグ発見のツールとしては以下のものが欲しい
- ホスト側のOSでメモリやレジスタをリアルタイムでダンプする
- ホスト側のOSからゲストOSの機械語を1命令ずつステップ実行する
- かつ、任意のタイミングでレジスタ、メモリが参照できる
- ホスト側のOSにログが残せる
log.println("message")
みたいにかけばホスト側のtxtファイルにログが残せる
- デバッグ時の詳細出力
なお最後のものは自力で作れる範囲で作ってみた。 一般保護例外捕捉時にスタックに自動的に積まれる値を表示しているだけ。
一応この部分のソースを貼っておく
int *inthandler0d(int *esp) { struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); struct TASK *task = task_now(); char s[30]; cons_putstr0(cons, "¥n0D General Protected Exception¥n"); sprintf(s, "CS:EIP = %04X:%08X¥n", esp[12],esp[11]); cons_putstr0(cons, s); sprintf(s, "SS:ESP = %04X:%08X¥n",esp[15],esp[14]); cons_putstr0(cons, s); sprintf(s, "EFLAGS = %08x¥n",esp[13]); cons_putstr0(cons, s); sprintf(s, "ERROR CODE = %08x¥n",esp[10]); cons_putstr0(cons, s); return &(task->tss.esp0); /* 異常終了させる */ }
様々な攻撃を防ぐ
crack3,アプリケーションからタイマーを不正に弄る
GUI(マウス等)の描画崩壊を狙ったタイマーに不正な値を書き込むプログラムです。 アプリケーション領域からのIOポートの利用を制限されているのでこのプログラムはフォールトの例外が発生します。
EFLAGSレジスタのIOPLフィールドが動作レベル(CPL)より高い特権レベルを持っているのでIOポートの利用が制限されているということになります。
アプリケーション用のセグメントの特権レベルは21日目に3(一番弱い)で設定してあります。
EFLAGSレジスタはPOPFD命令で設定できるようです。 都合の良いことにnaskfunc.nasにPOPFD専用のサブルーチンがあったので利用してみます。
_io_store_eflags, ; void io_store_eflags(int eflags); MOV EAX ,[ESP+4] PUSH EAX POPFD ; POP EFLAGS という意味 RET
権限の高いセグメントで動いているPGでELGASを書き換えてcrack3を実行してみました。
//ファイル先頭らへんに定義 #define EFLAGS_IOPL_3 0x00003000 (中略) //これをhariMainで実行 int eflg; eflg = io_load_eflags(); eflg |= EFLAGS_IOPL_3; /* IOPL=3 */ io_store_eflags(eflg);
OSの方で画面出力してみましたが、
- 改修前,0246
- 改修後,3246
でうまく変更できているように思えます。
hex | binary |
---|---|
eflags | 00!!000000000000 |
0246 | 0000001001000110 |
3246 | 0011001001000110 |
eflagsの!
のところがIOPL。上から3,4bit目
4バイト目の下位2ビットが1になっていることを確認できたのですが、
EFLAGSレジスタの値は正しく更新できているのですが、 特に変化はなしです。なんか俺が試したこと全部失敗してるな~
と思いましたが、例外補足時のログで実行時にIOPLが変化してしまっていることが発覚。 おそらくhariMain内で書いた後にどこかで変わっていたのだと思います。探しても見つかりませんでした。
IOPLが変化した理由
並行して読んでいるはじめて読む486がちょうどマルチタスクの章に入ったところで気づきました。
- EFLAGSレジスタはOS全体で共有しているものではなくて、タスクごとに用意されている
そうです。hariMainでEFLAGSを変えても、別タスクであるconsoleのEFLAGSには変化はないのです。 TSSにEFLAGSが含まれているのをみて気付きましたが、これに気付いた時かなりスッキリしました。
...ということでなんとかしてconsoleのタスクからeflagsを変更する必要があります。
IOPLを変更できるコマンドを作成
とりあえずIOPLを変更できるコマンドを作ってみました。
IOPL SET <Privilege level>
最強の0から最弱の3まで全て設定可能です。これで実行時にアプリケーションがIOポートにアクセスすることができます。
cracks4,CLI + HLT命令で割り込みを無効化する
[INSTRSET "i486p"] [BITS 32] CLI fin, HLT JMP fin
こちらも正しく例外でストップします。アプリケーションの権限で動いているときにHLTとCLI命令を実行すると例外扱いになります。 HLT命令をアプリケーションが直接実行するのはまずいので、タスクをスリープさせるAPIを呼ぶべきです。
crack5,OSの領域にあるCLI命令を含むサブルーチンを呼ぶ
[INSTRSET "i486p"] [BITS 32] CALL 2*8,0xac1 MOV EDX ,4 INT 0x40
アプリのセグメントからOSのセグメントのPGを呼び出すfarCALLは失敗します。 コールゲート(ゲートディスクリプタ)、という仕組みを利用してCLIを含むサブルーチンがロードされているセグメントをアプリケーションから呼べるように設定すればうまくいく気がしますが、気が向いたら試してみます。
(おまけ),crack6
osのルートディレクトリを破壊するようなAPIを呼ぶcrack6です。 そんなAPI定義するなよ!!! という感じですが、プロテクトモードのセグメントの設定をきちんとすれば、このくらいしかこのOSを攻撃する方法はないということです。 これは結構すごいことじゃないでしょうか。と思いましたが、多分まだまだ僕の知らない攻撃方法はあるのでしょう。
各攻撃バイナリのエラーを表示してみる
悪意のあるアプリ | 攻撃内容 | 備考 |
---|---|---|
crack1(p428) | C言語でルートディレクトリにヌル文字(0x00)を書き込む | QEMUで例外捕捉せず |
crack2 | アプリからOS用のセグメントにアクセスし、1と同じくルートディレクトリを壊す | QEMUで例外捕捉 |
crack3 | アプリからIOを不正利用 | QEMUで例外捕捉 |
crack4 | アプリからCLI HLT命令を実行 | QEMUで例外補足 |
crack5 | アプリからOSの命令をfar Call | QEMUで例外捕捉 |
ログ内容
バイナリ | CS,EIP | SS,ESP | FLAGS | ERROR CODE |
---|---|---|---|---|
crack2 | 1F5B,00000005 | 1F63,00010000 | 00000206 | 00000008 |
crack3 | 1F5B,00000002 | 1F63,00010000 | 00000206 | 00000000 |
crack4 | 1F5B,00000000 | 1F63,00010000 | 00000206 | 00000000 |
crack5 | 1F5B,00000000 | 1F63,00010000 | 00000206 | 00000010 |
せっかく作ったログを使ってデータを参照したいのですが、どうやら.mapファイルがないと難しいようです。nasmでmapファイルを作る方法などをググって試してみましたが、うまくいきませんでした。(アセンブル時にエラー)
次回は.hrbファイル構造の解説とそれを利用したOSの改善について書きます。
Java読書会BOF「Kotlinイン・アクション」を読む会 第6回に参加
Kotlin in Action 第6回
ジェネリクスの後半(9章)とリフレクションの章(10章)とDSL(11章)を読みました。 10章は技術的にもちょっと面白い読み物になっていて、オープンソースのjsonシリアライザ/デシリアライザであるjKid*1の解説をしながらリフレクションを学ぶ章になっています。
ジェネリクスにつけるout,in
- outをつけると共変
- inをつけると反変
- それ以外は不変(javaと同じ)
p322の表がおかしい
共変 | 反変 | 不変 |
---|---|---|
Class<out T> |
Class<in T> |
MutableList<T> |
クラスのサブタイピングは維持 | サブタイピングは逆 | サブタイピングなし |
Tはoutポジションだけ | Tはinポジションだけ | Tはどこでも |
inポジションだけにすべき箇所の文言がoutポジションになってしまっている。
::
の意味
クラス参照の意味。p344に軽く説明。(なぜここ?)
- String::ClassはJavaのString.classと同じ意味。
- p146メンバ参照で解説されているものと同じ
久しぶりに読むと忘れちゃいますね。
関数インターフェイス?
p332ページのまとめ
・関数インターフェイスは、1つめの方パラメータについて反変、2つ目の方パラメータについて久代編として宣言されています。
とありますが、「関数インターフェイス」と書いてしまうと一般的な関数の意味になってしまうのでは?という違和感を示す方がいました。322ページにあるFunctionインターフェイスのことなので、Functionインターフェース、もしくはFunctionNインターフェースにすれば違和感がない気がする。
Deprecatedで推奨する関数を通知できる。
p335の内容です。
@Deprecated("Use removeAt(index_ instead.",ReplaceWith("removeAt(index)")) fun remove(index int){...}
こんな感じで非推奨になったAPIに対して代替を示すことができます。この機能はJavaにはなかったため(多分)大変便利だと思います。
inteliJ IDEAではAlt+ ENterはかなり使う
p338
IntelliJ IDEAでは、コンパイラの警告状で[ALT] + [Enter]を押して表示されるオプションメニューから[Suppress]を選ぶことで、このアノテーションを挿入できます。
とありますが、コンパイラ抑制系のアノテーションの挿入の推奨をかなりしてくるとのことです。
これですね。
Java EE 8のJSONのライブラリが
- jsonp
- jsonb
などがかなり良いらしい
jackson,GSONの作者が言ってたらしい。
340文章がおかしい。
最後の塊の二行目。
data class Person( @jsonName("alias") val firstName: String, @JsonExclude val age: Int? = null )
プロパティfirestNameを示すJSON内でのキーを変更するために、アノテーションをつけています。これは、プロパティageをシリアライゼーションやでシリアライゼーションから除外するためです。
「これは、プロパティageを~」の直前に@jsonExcludeの説明がないと変。何かコピペのし忘れ?のように思える。
p350 KFunctionNはどこに生成される?
KFunctionNはKFunction1,KFunction2,KFunction3...と無限に存在しているのか?と思ったがこれはコンパイラによって生成されるらしい。これがどこに生成されるのか?という話題になった。(宿題)
p377誤植
2こ目のコードブロック。
閉じのダブルクォート"が抜けている。
感想
この本の9章、10章は非常に難しいです。少し理解できない点があるので復習中です。 9章は4時間ほどかけてコードを動かしながら精読しました。 明日以降10章も復習したいと思います。
次回は11.2から!
おそらく午前中で終了する程度の分量です。午後は何かレクをするようです(卓球?)。
7/21(土)のJava読書会の会場は、海辺の「川崎マリエン」になりました。午後は併設の体育館で卓球大会などを予定してます #javareading
— Tomohiro Takahashi (@ttdummy) June 16, 2018
ちなみに高橋さんは利き手じゃなくても私に勝てるそうです。
- 作者: Dmitry Jemerov,Svetlana Isakova,長澤太郎,藤原聖,山本純平,yy_yank
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/10/31
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
ブルーボトルコーヒーの豆を買った
通勤経路にあるので覗いてみたところお土産コーナーだけ人がいなかったので買いました。行列並んでまで買うと負けな気がするので今まで飲む機会がありませんでした。
1500円/200gくらいしました。普段買ってる豆が1200円くらいなので少し高いです。挽き売りはやってないらしいです。コーヒーミルを自宅に持っていない人は多いと思うので挽き売りも普通にやって欲しい。
パッケージ裏に焙煎日が書いているけど、なんと焙煎日がなんと1日前(6/6購入)。これは結構すごい。
豆はこんな感じ。貝殻豆やちゃんと焼けてなさそうな色の豆が結構入ってたりして値段の割にはあまりいい印象はありません
適当に粗挽きにしてドリップして頂きました。焼きたてなだけあってブクブク膨らみますね。
味はアフリカの豆を沢山混ぜたような味がしました。ブレンド名がスリーアフリカンだから当たり前か。
特にオチはありません。
30日OS自作本21日目
21日目の内容です。セグメントやメモリの復習が多めです。
- hariboteOSのメモリマップ
- セグメントについておさらい。
- セグメントのアクセス属性を設定する
- 設定したアクセス属性により上がる例外をハンドルする
- crack2が破壊しているOSの部分。
- はじめて読む486
hariboteOSのメモリマップ
メモリに直接データを書き込むなどの危険行為を行なっているので、念のためどこらへんを潰しているのか分かり易くするために復習です。p171から引用したデータを元に作成しました。
start | end | size | content |
---|---|---|---|
0x00000000 | 0x000fffff | 1MB | boot |
0x00100000 | 0x00267fff | 1440KB | floppy content |
0x00268000 | 0x0024f7ff | 30KB | empty |
0x0026f800 | 0x0026ffff | 2KB | IDT |
0x00270000 | 0x0027ffff | 64KB | GDT |
0x00280000 | 0x002fffff | 512KB | bootpack.hrb |
0x00300000 | 0x003fffff | 1MB | stack or etc |
0x00400000 | - | - | empty |
合計4048KB+です。
$ echo $((1000 + 1440 + 30 + 2 + 64 + 512 + 1000 )) 4048
本エントリ中に生のアドレスが出てきた際は参考にしてください。
セグメントについておさらい。
- プロテクトモード時はセグメントは固定長ではない
- リアルモード時はセグメントは固定長である。
- セグメントの設定は
set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
関数で行なっている*1
今まで登録したセグメントをまとめます。
セグメントはこんな感じで登録してきました。
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
set_segmdesc(gdt + 1004, 64 * 1024 - 1, (int) q, AR_DATA32_RW);
ざっくり表にまとめてみました。
position | content | base address | limit | ar |
---|---|---|---|---|
1 | OS data | 0x00000000 | 0xffffffff | 0100000010010010 |
2 | OS code | 0x00280000 | 0x0007ffff | 0100000010011010 |
3~1002 | TSS | alloc result | 103B | 0000000010001001 |
1003 | app code | alloc result | file size | 0100000010011010 |
1004 | app data | alloc result | 64KB | 0100000010010010 |
- alloc resultというのはallocation系の関数の結果次第ということです。
範囲的にはbootpackで解放している
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */ memman_free(memman, 0x00400000, memtotal - 0x00400000);
0x00001000 - 0x0009efff
0x00400000 - メモリの最後まで
この範囲のアドレスが使われます。
OS codeセグメントのベースアドレスが本エントリ冒頭に書かれているbootpack.hrbの開始地点0x00280000
と一致していることが確認できます。
ちなみにTSSはこんな感じでセグメントの3~1002の範囲を割り当てています。
/*参考*/ #define MAX_TASKS 1000 /* 最大タスク数 */ #define TASK_GDT0 3 /* TSSをGDTの何番から割り当てるのか */ #define AR_TSS32 0x0089 struct TASK *task_init(struct MEMMAN *memman) { /* 中略 */ for (i = 0; i < MAX_TASKS; i++) { taskctl->tasks0[i].flags = 0; taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8; set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32); }
セグメントのアクセス属性を設定する
arの下位8ビットはセグメント属性と呼ばれるものであり 下位6,7bit目のフラグを立てる(0x60をOR演算する)と、アプリ用のセグメントとなり、OS用のセグメントにデータが書けなくなります。
セグメント属性について改めて説明します。
content | ar |
---|---|
0x60 | 01100000 |
app code | 10011010 |
app data | 10010010 |
label | PDDSTTTA |
一番下に説明のため追加したlabelの行がセグメント属性の各ビットの役割です。上位から順に
- P:セグメントが存在する(1=存在,0=存在しない)
- D:DPL(セグメントの特権レベル)
- S:セグメントフラグ= セグメントである場合に1
- T:TYPE(セグメントの種類)
- A:セグメントがアクセスされた
PとSは説明しなくてもわかると思います。またAはメモリ管理用のフラグです。
ここで説明が必要なのがtypeとdplだと思います。
DPL
DPL(Descriptor Privilege Level)の2ビットで権限を表現します。
0~4の4種類をとり、小さいほど強い権限になります。
- 0:OS
- 1,2:デバイスドライバなど
- 3:アプリケーション
となり、今回はこの部分に0b11=4を登録しますのでアプリケーションのセグメントであることをマークしたことになります。
type:セグメントのread,write,executeなど
この章ではtypeはあまり関係ないけど一応説明。
type | binary | read | write | execute | content |
---|---|---|---|---|---|
0 | 000 | ◯ | × | × | data segment |
1 | 001 | ◯ | ◯ | × | data segment |
2 | 010 | ◯ | × | × | stack segment |
3 | 011 | ◯ | ◯ | × | stack segment |
4 | 100 | × | × | ◯ | code segment |
5 | 101 | ◯ | × | ◯ | code segment |
6 | 110 | × | × | ◯ | conforming code segment |
7 | 111 | ◯ | × | ◯ | conforming code segment |
- コンフォーミングコードセグメントはアプリケーションからOSの権限の命令(0)を直接呼ぶためのセグメントです。
- コードセグメントはcsレジスタ、データセグメントはdsレジスタを使って参照します。
- スタックセグメントはssレジスタを使います。
設定したアクセス属性により上がる例外をハンドルする
x86ではアクセス属性に違反した挙動があればINT0x0dの割り込みが発生する。この割り込みを例外という。 例外をサポーするにはこのINT0x0dに対応する割り込みハンドラを作成してIDTに登録すればOK。
本書によるとqemuのバグ?などがあるらしく例外割り込みなどはうまく再現できないとのこと。古い本なので最近のqemuでは実は治ってたりしてwと思いましたが、私が利用しているバージョンは2.11.0の段階でまだ治っていません。
$ qemu-system-i386 --version QEMU emulator version 2.11.0 Copyright (c) 2003-2017 Fabrice Bellard and the QEMU Project developers
crack2が破壊しているOSの部分。
crack2を実行するとdirコマンドの表示内容がなくなるとのことですが、 これがなぜ発生しているか本書に記載がなかったので調べてみました。
[INSTRSET "i486p"] [BITS 32] MOV EAX,1*8 ; OS用のセグメント番号 MOV DS,AX ; これをDSにいれちゃう MOV BYTE [0x102600],0 MOV EDX,4 INT 0x40
冒頭にまとめた通り、セレクタ値1のセグメントはOSのデータ領域です。ここのベースアドレスはメモリ上の先頭(0x00000000)になります
position | content | base address | limit | ar |
---|---|---|---|---|
1 | OS data | 0x00000000 | 0xffffffff | 0100000010010010 |
このセグメントをDSで指定した状態で,MOV BYTE [0x102600],0
を実行しています。オフセットアドレス(0x102600
)の位置に0を書き込むという意味です。今回はベースアドレスが0x00000000
なのでオフセットもクソもありませんが。。。
そしてこの0x102600
の位置を冒頭のメモリマップで確認しています。
start | end | size | content |
---|---|---|---|
0x00000000 | 0x000fffff | 1MB | boot |
0x00100000 | 0x00267fff | 1440KB | floppy content |
どうやらフロッピーディスクの0x2600番地の位置のようです。 この位置のデータをバイナリエディタで確認すると次のようになっていました。
そしてこの位置は19日目に書いた通り、root directoryとなります。 crack2は
- root directoryの先頭位置に0書き込みディレクトリの内容を消す、
という結果を引き起こすコードだったのです。
dirコマンドはファイル先頭位置のヌル文字(0x00)を読み取った時点で処理を中断していますので、これが理由でdirの出力結果がなくなった、ということになります。
void cmd_dir(struct CONSOLE *cons) { struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600); int i, j; char s[30]; for (i = 0; i < 224; i++) { if (finfo[i].name[0] == 0x00) { break; } /*dirの処理*/ } } cons_newline(cons); return; }
今回はそのような攻撃をセグメント属性と例外割り込みで守ろう、という趣旨の章でした。
はじめて読む486
32ビットコンピュータをやさしく語る はじめて読む486 (アスキー書籍)
- 作者: 蒲地輝尚
- 出版社/メーカー: 角川アスキー総合研究所
- 発売日: 2014/10/21
- メディア: Kindle版
- この商品を含むブログを見る
参考にしています。 30日OS自作本がOSを30日で作るという目標のため省いている細かいCPUの機能についてかなり充実しています。あと図がかなりわかりやすい。
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
*1:arは権限です。acsess right
プログラマのためのSQL 読書会(20)に参加
25.3.4の自己外部結合から25章最後まで読みました。
自己結合、自己外部結合などがテーマです。 SQLでパズル的なことをやろうとすると自己外部結合が役立ちそうです。 あとは障害調査くらいですかね。
正誤表と掲載SQL
今回読書会中に詰まることがあり必要になりました。どちらも翔泳社の公式サイトにあります。
ただしSQLはプレーンテキストが置いてあるだけであり、またSQLを動かすためのinsert文がついていないものも多数あります。また正誤表で上がっているものの訂正もされていないようですので注意が必要です。
外部結合は順番が入れ替えられない
25.3.5の内容。外部結合は結合則が成り立たない
- (A + B) + C
- A + (B + C)
これが同じ結果にならない可能性がある。 どの順番で結合をするかで結果が変わってしまう。
サポートでも外部結合が入っているとお手上げらしい。
25.4のクエリの疑問
---生徒テーブル CREATE TABLE Students (student_nbr INTEGER NOT NULL PRIMARY KEY, student_type CHAR(1) NOT NULL DEFAULT 'D' CHECK (student_type IN ('D', 'F'))); ---国内学生テーブル CREATE TABLE DomesticStudents (student_nbr INTEGER NOT NULL PRIMARY KEY REFERENCES Students(student_nbr)); ---留学生テーブル CREATE TABLE ForeignStudents (student_nbr INTEGER NOT NULL PRIMARY KEY REFERENCES Students(student_nbr)); ---データ投入 -- Students INSERT INTO Students(student_nbr, student_type) VALUES('1', 'D'); INSERT INTO Students(student_nbr, student_type) VALUES('2', 'D'); INSERT INTO Students(student_nbr, student_type) VALUES('3', 'F'); INSERT INTO Students(student_nbr, student_type) VALUES('4', 'D'); INSERT INTO Students(student_nbr, student_type) VALUES('5', 'F'); -- DomesticStudents INSERT INTO DomesticStudents(student_nbr) VALUES('1'); INSERT INTO DomesticStudents(student_nbr) VALUES('2'); INSERT INTO DomesticStudents(student_nbr) VALUES('4'); -- ForeignStudents INSERT INTO ForeignStudents(student_nbr) VALUES('3'); INSERT INTO ForeignStudents(student_nbr) VALUES('5'); SELECT Students.*, DomesticStudents.*, ForeignStudents.* FROM Students LEFT OUTER JOIN DomesticStudents ON CASE Students.student_type WHEN 'D' THEN 1 ELSE NULL END = 1 LEFT OUTER JOIN ForeignStudents ON CASE Students.student_type WHEN 'F' THEN 1 ELSE NULL END = 1 --注意:正誤表を見ると以下が抜けていた。 WHERE ( Students.student_nbr = DomesticStudents.student_nbr OR Students.student_nbr = ForeignStudents.student_nbr);
結果
student_nbr | student_type | student_nbr | student_nbr -------------+--------------+-------------+------------- 1 | D | 1 | 2 | D | 2 | 3 | F | | 3 4 | D | 4 | 5 | F | | 5
なぜCASE式を使っているのか?という意見が出た。
- Students.student_type = D
- Students.student_type = F
の2つの結合で良いのではないかという意見が出た。
SELECT Students.*, DomesticStudents.*, ForeignStudents.* FROM Students LEFT OUTER JOIN DomesticStudents ON Students.student_type = 'D' LEFT OUTER JOIN ForeignStudents ON Students.student_type = 'F' WHERE ( Students.student_nbr = DomesticStudents.student_nbr OR Students.student_nbr = ForeignStudents.student_nbr);
結果
student_nbr | student_type | student_nbr | student_nbr -------------+--------------+-------------+------------- 1 | D | 1 | 2 | D | 2 | 3 | F | | 3 4 | D | 4 | 5 | F | | 5
確かに結果は変わらないですね。
古いSQLの結合構文の有利な点
結合が宣言的にかけるというメリットがある。
-- JOINを使わない古い結合 SELECT x, y, z FROM Foo, Bar, Flub WHERE y BETWEEN x AND w;--結合条件が宣言的にかける -- JOINを使う新しい結合 SELECT x, y, z --Foo,BAR,Flubとインクリメンタルに結合する必要がる。 --SQLで手続き的に書く必要が出てくる。 FROM Foo INNER JOIN Bar ON y >= x INNER JOIN Flub ON y <= w;
- シーター結合は普通はやらない。
- 新しい結合句で問題ない。
新人研修や初学者がとくべき定番の問題
自己結合の章ではパズルのような問題を沢山ときます。そういえばSQLでは
などの他のプログラミング言語だと最初に解いて見る定番の演習問題が無いので 質問してみました。
- SQLドリル
スッキリわかる SQL 入門 ドリル215問付き! (スッキリシリーズ)
- 作者: 中山清喬,飯田理恵子
- 出版社/メーカー: インプレス
- 発売日: 2013/04/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (5件) を見る
改訂第3版 すらすらと手が動くようになる SQL書き方ドリル (WEB+DB PRESS plus)
- 作者: 羽生章洋,和田省二,菅井大輔
- 出版社/メーカー: 技術評論社
- 発売日: 2016/04/12
- メディア: 大型本
- この商品を含むブログを見る
複数冊ありますがどちらもおすすめとのこと。 後者はブラウザでSQLを試せる環境がついています。
SQL 第2版 ゼロからはじめるデータベース操作 (プログラミング学習シリーズ)
- 作者: ミック
- 出版社/メーカー: 翔泳社
- 発売日: 2016/06/17
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
どこかで話がそれてマニアックなデータベース本の話題になりました。
- 作者: スコット W アンブラー,ピラモド・サダラージ,梅澤真史,越智典子,小黒直樹
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2008/03/26
- メディア: 単行本
- 購入: 10人 クリック: 211回
- この商品を含むブログ (53件) を見る
川崎の工業図書館?にSQL関連のレアな書籍が結構ある。
- ここでしょうか。神奈川県立川崎図書館
横浜に県立図書館がすでに開館していることと、工業都市・川崎にあることから、開館当初から自然科学・工業・産業分野を重点とした資料収集・サービス方針を掲げている。
カーリル*2というサービスで全国の図書館を検索できる
- 意外と近所の図書館にデータベースリファクタリングありました。
成熟している分野なので教材などは充実している感じですが、定番の問題みたいなのは特になさそうです。
人名
この本は質問サイト?のようなコミュニティの引用も多く、一般人の人名が多く登場します。 主催の木村さんは同名人物っぽい人をlinked inで見つけてメッセージを送ってみたそうですが、返事がなかったようです。
初版が古い本なので、昔から書かれている人とはもう連絡を取るのが難しいかもしれませんね。 最近の本だとtwitterIDを載せると思いますし、昔より連絡コストは大きく減りつつあると思います。
感想
パズル系のクエリは普段描かないような書方で描かれていることが多かったり、クエリそのものが長くなっている等で、SQLにコメントがついていないと理解が難しいです。本章は読書会で読むというより、家でじっくり試しながら読むほうがよさそうです。
プログラマのためのSQL 第4版 すべてを知り尽くしたいあなたに
- 作者: Joe Celko
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (11件) を見る