「30日でできる!OS自作入門」をRustで。21日目

Posted on July 27, 2019 , Tags: OS自作入門, OS, Rust

「30日でできる!OS自作入門 」のC言語の部分をできるだけRustですすめてみる。今回は21日目の内容。

Rustでアプリケーションを書く

前回はアセンブリ言語で書かれたコードをOS上のアプリケーションとして実行した。本では次にCのコードを実行している。折角なので、ここではRustのコードをアプリケーションとして実行する。

OS用の設定を使いまわすので、以下の通り設定する。

Cargo.tomlもOSにならって修正したのち、以下の通りMakefileを追加する。

hello3.binのmain関数にジャンプするため、呼出側で最初の6バイトを書き換えるようにする。

これで準備はできた。アプリ側のlib.rsは以下の通り

実行結果

コンソール画面でhello3を実行すると無事にhelloと表示された。

hello3

OSの保護

アプリケーション側のコードを例えば以下のようにするとOSに不具合をおこすことができる。

実行すると、lsで何も見えなくなり、hello2などのアプリケーションの実行も失敗する。

cracked

これを防ぐために、GDTで適切にメモリアクセスの設定をする。

// console.rs
    pub fn cmd_app<'a>(&mut self, filename: &'a [u8], fat: &[u32; MAX_FAT]) {
        let memman = unsafe { &mut *(MEMMAN_ADDR as *mut MemMan) };
        let mut finfo = search_file(filename);
        if finfo.is_none() && filename.len() > 1 && filename[filename.len() - 2] != b'.' {
            let mut filename_ext = [b' '; MAX_CMD + 4];
            let filename_ext = &mut filename_ext[0..(filename.len() + 4)];
            filename_ext[..filename.len()].copy_from_slice(filename);
            filename_ext[filename.len()] = b'.';
            filename_ext[filename.len() + 1] = b'b';
            filename_ext[filename.len() + 2] = b'i';
            filename_ext[filename.len() + 3] = b'n';
            finfo = search_file(filename_ext);
        }
        if finfo.is_none() {
            self.display_error("Bad Command");
            return;
        }
        let finfo = finfo.unwrap();
        let content_addr = memman.alloc_4k(finfo.size).unwrap() as usize;
        let app_mem_addr = memman.alloc_4k(APP_MEM_SIZE as u32).unwrap() as usize;
        {
            let ptr = unsafe { &mut *(CS_BASE_ADDR as *mut usize) };
            *ptr = content_addr;
        }
        finfo.load_file(content_addr, fat, ADR_DISKIMG + 0x003e00);
        let content_gdt = 1003; // 1,2,3はdesciptor_table.rsで、1002まではmt.rsで使用済
        let gdt = unsafe { &mut *((ADR_GDT + content_gdt * 8) as *mut SegmentDescriptor) };
        *gdt = SegmentDescriptor::new(finfo.size - 1, content_addr as i32, AR_CODE32_ER + 0x60);
        // この辺から追加
        let app_gdt = 1004;
        let gdt = unsafe { &mut *((ADR_GDT + app_gdt * 8) as *mut SegmentDescriptor) };
        *gdt = SegmentDescriptor::new(
            APP_MEM_SIZE as u32 - 1,
            app_mem_addr as i32,
            AR_DATA32_RW + 0x60,
        );
        // kernel.ldを使ってリンクされたファイルなら最初の6バイトを置き換える
        if finfo.size >= 8 {
            // 4から7バイト目で判定
            let bytes = unsafe { *((content_addr + 4) as *const [u8; 4]) };
            if bytes == *b"Hari" {
                let pre = unsafe { &mut *(content_addr as *mut [u8; 6]) };
                *pre = [0xe8, 0x16, 0x00, 0x00, 0x00, 0xcb];
            }
        }
        let esp0_addr: usize;
        {
            let task_manager = unsafe { &mut *(TASK_MANAGER_ADDR as *mut TaskManager) };
            let task_index = task_manager.now_index();

            let task = &task_manager.tasks_data[task_index];
            esp0_addr = unsafe { &(task.tss.esp0) } as *const i32 as usize;
        }

        // アプリをfarcallではなくPUSHしてRETFで呼ぶように変更
        unsafe {
            _start_app(
                0,
                content_gdt * 8,
                APP_MEM_SIZE as i32,
                app_gdt * 8,
                esp0_addr as i32,
            );
        }
        memman.free_4k(content_addr as u32, finfo.size).unwrap();
        memman
            .free_4k(app_mem_addr as u32, APP_MEM_SIZE as u32)
            .unwrap();
        self.newline();
    }

GDTの設定変更とそれを使ったアプリ呼出の変更を行っている。これまでfarcallでアプリ側の制御にうつっていたが、_start_appというアセンブリ言語の関数を使って、アプリ側の制御にうつるようにする。

この_start_appだが、最初にPUSHADしないといけない関係上、どうしてもインラインアセンブリだと実現方法が思いうかばなかったので、ここにきて新しくasmfunc.asmというソースを追加し、リンクするように変更した。コード自体は本の内容のコピペなのでここでは割愛する。

アプリ側からOSに戻るときは、システムコールで戻る。

例えば

のようにEDX4を設定してINT 0x40すればOSに戻ることができる。
Rustでアプリケーションコードを書く場合は

のようにすればよい。

例外処理

アプリケーションから許可していないセグメントにアクセスがあったときなどにエラーを表示できるように例外処理も追加する。

今後も例外処理が増えそうなので、handlerと同様のexception_handlerというマクロも定義した。

実行結果

本にある通りQEMUのバグなのか、General Protected Faultの表示はでないが、元々問題だったコードを実行しても壊れることがなくなった。
また、本にある以下のアプリケーションも実行してみる。

すると、こちらは問題なくGeneral Protected Faultが表示された。

crack2

21日目は以上となる。ここまでの内容のコードはyoshitsugu/hariboteos_in_rustのday21としてタグを打ってある。