FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

「30日でできる!OS自作入門」を読み始めた (22日目. C言語でアプリケーションを開発する)

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

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

C言語でアプリケーションを作る会。どっちにしろ、hrb2bim などの独自ツールは使用していないため、C言語で開発するときもリンカスクリプトを利用しているので特に問題なかった。

途中でどうしても正しく動作しなかったのでかなりてこづったが、最終的には動作させることが出来るようになった。

一つのポイントは、gccコンパイルするときに調子になって-O3を挿入していたのだが、これはやってはいけない。

--- a/program/haribote-os/day15/haribote-000/Makefile
+++ b/program/haribote-os/day15/haribote-000/Makefile
@@ -33,7 +33,7 @@ $(TARGET).img: ipl10.bin $(TARGET).sys


 %.o:%.c
-       gcc -m32 -O3 -c -fno-pic -nostdlib -fno-builtin -o $@ $<
+       gcc -m32 -c -fno-pic -nostdlib -fno-builtin -o $@ $<
        objdump -D $@ > $@.dmp

あとは、StackをオーバフローさせるアプリケーションがQEMU上では動作しない。 QEMUのバグだと思われるが、それ以上は追求しなかった。

f:id:msyksphinz:20180429153902p:plain
図. bug1の実行。QEMUのバグ?スタックオーバフローを検出できなかった。

次に、ウィンドウを起動させた。これもAPIの実装を間違えていたので最初動かなくてかなり戸惑ったが、修正して動作するようになった。

f:id:msyksphinz:20180429154129p:plain
図. winhelo2の実行

APIの構造が少し難しくて挫折しそうになっているのだが、まずはPUSHADという命令。これは以下と等価らしい。

PUSH EAX
PUSH ECX
PUSH EDX
PUSH EBX
PUSH ESP
PUSH EBP
PUSH ESI
PUSH EDI

例えば、winhelo2.cで以下のようなコードを記述した場合。APIへの呼び出しはどのようになるのだろうか。

  • winhelo2.c
#include "a_nask.h"
#include "sprintf.h"

char buf[150 * 50];
void HariMain (void)
{
  int win;
  win = api_openwin (buf, 150, 50, -1, "hello2");

  api_boxfilwin (win,  8, 36, 141, 43, 3  /* yellow */);
  api_putstrwin (win, 28, 28, 0 /* black */, 12, "hello, world");
  api_end ();
}

まずは、api_openwin()が呼ばれるのだが、これはアセンブリで実装されておりAPIを実行させるための割り込みを発行させるだけである。 int 0x40で呼び出されるのは、割り込みルーチンとして登録されている‘asm_hrb_apihrb_api`である。

  • a_nask.nas
api_openwin:   ; int api_openwin (char *buf, int xsiz, int ysiz, int col_inv, char *title);
    push    edi
    push    esi
    push    ebx
    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
    int     0x40
    pop     ebx
    pop     esi
    pop     edi
    ret
  • asm_hrb_api (naskfunc.nas)
asm_hrb_api:
    sti
    push    ds
    push    es
    pushad
    pushad
    mov     ax,ss
    mov     ds,ax
    mov     es,ax

    call    hrb_api

    cmp     eax,0
    jne     asm_end_app
    add     esp,32
    popad
    pop     es
    pop     ds
    iretd
  • hrb_api (console.c)
int *hrb_api (int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
...

最初のPUSHADはスタック退避、2番目のPUSHADはhrb_api に引数を渡すため。hrb_api内でレジスタを書き換えるために、以下のようなコードが入っている。

  • console.c
  int *reg = &eax + 1;
  } else if (edx == 5) {
...
    reg[7] = (int)sht;

これにより、退避したスタックの先頭にshtの値が入ってくる仕組みだ。

この辺が難しいので一応まとめた。正しいかどうかは不明だが。 横軸方向にプログラムが流れていく。スタックに複数の値を退避しながら、生成したshtをどうやって戻しているのかを見て欲しい。

f:id:msyksphinz:20180429162915p:plain
図. 関数コールによりAPIが呼び出される手順。