備忘録やめた

備忘録として使用していたけどやめた.このブログに載せてあるコードのライセンスは別途記載がない限りWTFPL OR NYSLです.

RamenOSのカーネルの.headセグメントを削除する

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

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

要約

RamenOSのカーネル.headセグメントを削除します..headセグメントに依存している,asmhead.nas内の; bootpackの起動というコメントで始まる.dataセグメント移動のコードを削除し,リンカスクリプトを編集して最初から.dataセグメントを定位置に置きます.そして.textセグメントの先頭に,最初に実行されるべき関数を配置します.この作業を行なうことで,カーネル.headセグメントを削除することが出来ます.

.headセグメントを削除したら,asmhead.nasの最後のJMP命令をJMP 2*8:0に変更する必要があります.

はじめに

はりぼてOSを基にしているRamenOSにページングを実装するため,はりぼてOSのhrbファイルの最初についているデータを削除する必要がありました.カーネル機械語コードがファイルの先頭に位置して欲しかったためです.この記事ではhrbファイルの先頭についているデータすなわち.headセグメントを削除する方法を紹介します.

注意

現時点で筆者は8日目の内容を終了した段階にあります.従ってはりぼてOSのアプリケーションを製作していません.本中のP460,P461によれば,一部のデータはアプリケーションを実行する際に必要とされています.

筆者はbim2hrbを使用せず,GNU ldを使用しています.この記事では,GNU ldを使用した方法のみを紹介します.

やり方

.headセグメント削除前のリンカスクリプトは以下の通りです.

OUTPUT_FORMAT(binary);
OUTPUT_ARCH(i386);

ENTRY(os_main)

SECTIONS
{
    .head 0 : {
        LONG(64 * 1024)
        LONG(0x69726148)
        LONG(0)
        LONG(ADDR(.data))
        LONG(SIZEOF(.data))
        LONG(LOADADDR(.data))
        LONG(0xE9000000)
        LONG(os_main - 0x20)
        LONG(0)
    }

    .text : { *(.text*) }

    .data 0x310000 : AT ( ADDR(.text) + SIZEOF(.text) ) {
        *(.data)
        *(.rodata*)
        *(.bss)
    }

    /DISCARD/ : { *(.eh_frame) }
}

このスクリプト.headセグメントを削除します.

現時点で不要な情報を削除する

LONG(64 * 1024)LONG(0x69726148),2つのLONG(0)は,8日目の内容を完成させた時点では必要ありません.その後必要になるかもしれませんが,現状残しておく必要がないため削除します.

データセグメントを初めから定位置に置く

リンカスクリプトによれば,.dataセグメントのアドレス解決をする際,.dataセグメントが0x310000から始まると仮定していますが,実際は.textセグメントの直後に配置するようにしています.しかし,asmhead.nas内で.dataセグメントは0x310000に移されることになります.

; asmheadでしなければいけないことは全部し終わったので、
;  あとはbootpackに任せる

; bootpackの起動

        MOV      EBX,BOTPAK
        MOV      ECX,[EBX+16]
        ADD      ECX,3           ; ECX += 3;
        SHR      ECX,2           ; ECX /= 4;
        JZ       skip         ; 転送するべきものがない
        MOV      ESI,[EBX+20] ; 転送元
        ADD      ESI,EBX
        MOV      EDI,[EBX+12] ; 転送先
        CALL memcpy
skip:

[EBX+16][EBX+20][EBX+12]はどれも.headセグメントの情報を参照しています.それぞれLONG(SIZEOF(.data))LONG(LOADADDR(.data))LONG(ADDR(.data))です.

このコードは,.dataセグメントが存在するかを確認し,存在する場合,.dataセグメントを,.dataセグメントが実際に存在する場所から,アドレス解決の際の.dataセグメントの起点アドレスへ移します.

この移す作業を取り除き,最初から定位置に移動させます.ただし,今回は.textセグメントの直後に置きます.

このためには,先程のアセンブリコードを削除し,リンカスクリプトを次のように変更します.

    .text 0x00501000 : { *(.text*) }
    .data : {
        *(.data)
        *(.rodata*)
        *(.bss)
    }

0x00501000という値はRamenOSにおいてカーネルが置かれる位置です.はりぼてOSの場合ここを0x00280000に変更してください.詳しくはこちらで.

tokuchan3515.hatenablog.com

これらの変更によって,LONG(ADDR(.data))LONG(SIZEOF(.data))LONG(LOADADDR(.data))の3つを削除することが出来ます.

ところで,はりぼてOSではスタック領域を0x00300000から0x003FFFFFとしています.つまり,.textセグメントと.dataの間の領域がスタック領域となっています.RamenOSでは別の場所をスタック領域としますが,この段階では仮にカーネルの始点をスタックの基点として用意します.従って以下のようにスタックポインタの初期値を変更します.

    MOV      ESP,BOTPAK                ; スタック初期値

*(.text.os_main).textセグメントに追加

カーネルのコードで最初に実行されるべき関数を先頭に配置する必要があります.RamenOSではos_main関数を最初に実行する必要があるので,この関数を.textセグメントの先頭に配置します.

    .text 0x00501000 : {
        *(.text.os_main)
        *(.text*)
    }

*(.text.os_main)の最初のアスタリスクは,ld実行時,全ての入力ファイルから受け付けるという意味です.例えば,ld a.o b.o c.oを実行した時に,3つのオブジェクトファイルのどれに.text.os_mainが存在しても,それを配置します.特定のファイルからの.text.os_mainのみには制限しません.

JMP命令の削除

最後に残った2つのLONGについては以下の記事を確認してください.

tokuchan3515.hatenablog.com

この時点で,.headセグメントを削除する事が出来ます.残った2つのLONGはOSの他のコードが依存していないためです.

以上の作業により,はりぼてOSのカーネルファイルの.headセグメントを削除することが出来ます.尚,.headセグメントを削除したため,asmhead.nasの最後のJMP命令を以下のように変更する必要があります.

    JMP      DWORD 2*8:0