Yabu.log

ITなどの雑記

30日OS自作本23日目

23日目の内容です。

アプリケーションのメモリ管理の導入とGUI周りのAPIの強化をやります。

f:id:yuyubu:20180621215939p:plain

アプリケーションもメモリ管理(malloc)をする

気になったことがあります。winhelo2.hrbが7.6KBもあることです。 (略)なんでかなーと思ってwinhelo2.hrbをバイナリエディタでのぞいてみたら00がいっぱいです。 (略)ソースに戻って調べてみたところ、原因はwinhelo2.cのchar buf[150*50]でした。 (略)アセンブラでいうとRESB 7500ってとこです。

とのことですので調べてみました。

49                                          [SECTION .data]
50 00000013 00 00 00 00 00 00 00 00 00 00       ALIGNB  16
     0000001D 00 00 00
51 00000020                                 _buf:
52 00000020 00 00 00 00 00 00 00 00 00 00       RESB    7500
     0000002A 00 00 00 00 00 00 00 00 00 00
...ずっと0なので略...
     00001D62 00 00 00 00 00 00 00 00 00 00

メモリ管理をしないとこんな風にデータセクションに値が確保されて7500KBも増えるということですね。 アプリケーションから利用できるメモリ確保APIを使って実行ファイルの容量を減らしています。

char *buf;
int win;

api_initmalloc();
buf = api_malloc(150 * 50); //新規

約7500KB程度減っています。

$ ls -alt winhelo*.hrb
-rw-r--r--  1   staff   387  6 20 13:26 winhelo3.hrb
-rw-r--r--  1   staff  7888  6 20 13:26 winhelo2.hrb

一般的に配列を宣言すれば、そのサイズ分実行ファイルに含まれるということでしょうか。

画像系のAPI

ウインドウに点を打つ
EDX=11
EBX=ウインドウの番号
ESI=表示位置のx座標
EDI=表示いちのy座標
EAX=色番号

f:id:yuyubu:20180621215005p:plain

画面の任意の座標にドットを書くAPIです。VRAMの番地を直接指定するのではなく、シート(ウインドウ)上の1ドットを変えています。

星座

この乱数は結局は計算で作っているものであって、PCの中にサイコロがあるわけではないので、もう一度アプリを起動しても結果はいつも同じになります。

rand()の有名な特性ですね。大学の頃のアルゴリズムの授業で、ソートプログラム用のデータを作ったら幾つ作っても同じデータになるんだけど???みたいな学生が大量に発生した思い出があります。

さて今回もシードを設定していないので毎度同じ位置にドットがきてしまいます。書籍中の画像と全く同じ配置になっていますね。

f:id:yuyubu:20180621215546p:plain

もう一度アプリを起動しても、結果はいつも同じになります。だから、筆者が「make run」して眺めた夜空と全く同じものを、読者の皆さんも眺めることになります。

srand((unsigned int) time(null));こんな感じで時間を与えて初期化できるのですがtime()の入っているtime.hがinclude出来ない。

../../z_tools/gocc1 -I../../z_tools/haribote/ -Os -Wall -quiet -o stars.gas stars.c
stars.c:2:18: time.h: No such file or directory

多分ztools配下のhariboteに追加すればいいんだろうけど。

$ls ../../z_tools/haribote/
errno.h     golibc.lib  harilibc.lib    math.h      stdarg.h    stdio.h     string.h
float.h     haribote.rul    limits.h    setjmp.h    stddef.h    stdlib.h

とりあえずsrand(20)を実行して著者と違う並びで星座を表示してみました。

f:id:yuyubu:20180621215739p:plain

GUI

  • 線を引くプログラム
  • RPG風の画面キー入力で動く*のウインドウ
  • 強制終了じに画面が消えない事象の修正

などを行いました。 この辺はAPI追加したりドット表示の延長上の話なのであまりOSが関係ないので流し読み。

感想

  • 例のアルゴリズムの授業ですが、最終テストが筆記だったのでsrand(time(null))は暗記して臨んだ思い出があります。
  • Xボタンを押してウインドウを消したい、みたいな流れにならないのがちょっとモヤモヤする。

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の中で決め打ちになっているので

  • 好きな位置・高さに出す
  • 複数ウインドウを出す

ができなくなっています。

www.youtube.com

新規に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ポートにアクセスすることができます。

www.youtube.com

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のライブラリが

などがかなり良いらしい

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から!

おそらく午前中で終了する程度の分量です。午後は何かレクをするようです(卓球?)。

ちなみに高橋さんは利き手じゃなくても私に勝てるそうです。

Kotlinイン・アクション

Kotlinイン・アクション

ブルーボトルコーヒーの豆を買った

通勤経路にあるので覗いてみたところお土産コーナーだけ人がいなかったので買いました。行列並んでまで買うと負けな気がするので今まで飲む機会がありませんでした。

 

1500円/200gくらいしました。普段買ってる豆が1200円くらいなので少し高いです。挽き売りはやってないらしいです。コーヒーミルを自宅に持っていない人は多いと思うので挽き売りも普通にやって欲しい。

 

f:id:yuyubu:20180616202448j:image

 

パッケージ裏に焙煎日が書いているけど、なんと焙煎日がなんと1日前(6/6購入)。これは結構すごい。

 

f:id:yuyubu:20180616202709j:image

 

豆はこんな感じ。貝殻豆やちゃんと焼けてなさそうな色の豆が結構入ってたりして値段の割にはあまりいい印象はありません

 

f:id:yuyubu:20180616203352j:imagef:id:yuyubu:20180616203409j:image

 

適当に粗挽きにしてドリップして頂きました。焼きたてなだけあってブクブク膨らみますね。

 

f:id:yuyubu:20180616203452j:image

 

味はアフリカの豆を沢山混ぜたような味がしました。ブレンド名がスリーアフリカンだから当たり前か。

 

特にオチはありません。

30日OS自作本21日目

21日目の内容です。セグメントやメモリの復習が多めです。

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種類をとり、小さいほど強い権限になります。

となり、今回はこの部分に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番地の位置のようです。 この位置のデータをバイナリエディタで確認すると次のようになっていました。

[:plane]

そしてこの位置は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

参考にしています。 30日OS自作本がOSを30日で作るという目標のため省いている細かいCPUの機能についてかなり充実しています。あと図がかなりわかりやすい。

30日でできる! OS自作入門

30日でできる! OS自作入門

*1:arは権限です。acsess right