%@ Register tagprefix="pt" tagname="header" src="../header.ascx" %>
<%@ Register tagprefix="pt" tagname="footer" src="../footer.ascx" %>
<%@ Register tagprefix="pt" tagname="wiki" src="../wiki.ascx" %>
ここではVC++を用いてカーネルイメージを得る方法について述べます。必要なものは、
です。
次のコードを kernel.cpp として保存します。このコードは画面上に文字を書き込みます。
extern void kernel() {
char* vram = (char*)0xb8000;
vram[0] = 'O';
vram[1] = 12;
vram[2] = 'K';
vram[3] = 12;
for(;;) __asm {hlt};
}
次のコードを _entrypoint.cpp として保存します。ファイル名の先頭のアンダースコアは、ほかのソースコードファイルを昇順で整列したときに先頭に来るようにするためのもので、もし一つのアンダースコアで足りないときは必要に応じて増やす必要があります。(関数名のアンダースコアは適当でかまいません。) このコードはカーネルのエントリポイントとして機能します。
extern void kernel();
extern void __declspec(naked) _entrypoint() {
kernel();
for(;;) __asm {hlt};
}
次のコマンドを実行します。(見栄えの都合により改行しているが、一行として入力してください。) なお、実行にはMSVC++がインストールされている環境で、適切にパスが通っていなければなりません。簡単には、スタートメニューのVC++のところの中に環境変数が自動的に設定されるコマンドプロンプトのショートカットがあると思うので、それを使うと良いでしょう。
> cl.exe _entrypoint.cpp kernel.cpp /link /OUT:"kernel.dll" /DLL /NODEFAULTLIB /MAP /MAPINFO:EXPORTS
/OPT:REF /OPT:ICF /NOENTRY /RELEASE /BASE:"0x100000" /NOASSEMBLY /MERGE:".text=.entry" /MACHINE:X86 /FIXED
オプションについて説明します。
_entrypoint.cpp kernel.cpp
ソースファイルのリストには、先頭に _entrypoint.cpp を記述しなければなりません。それ以降は任意の順序でかまいません。VC++のリンカはファイルリストの順に関数を配置するので、先頭のファイルの内容はDLLの先頭に配置されることになります。VS.NETの統合環境はファイル名順にこのリストを渡すようなので、若いファイル名をつけておくことによって位置を制御できます。
/link
続くオプションがリンカへのパラメタであることを示します。
/OUT:"kernel.dll" /DLL /MAP /MAPINFO:EXPORTS /OPT:REF /OPT:ICF /NOENTRY /RELEASE /MACHINE:X86 /NOASSEMBLY
ここらへんはVC++のヘルプを調べてください。
/NODEFAULTLIB
これはVC++の標準ライブラリを無視するために必要な設定です。これを指定しておけば、意図せずライブラリ関数とリンクされてしまったときにリンカエラーとして検出できるようになります。
/MAP /MAPINFO:EXPORTS /BASE:"0x100000" /MERGE:".text=.entry" /FIXED
この指定が最も重要です。
マップ関係の指定は、リンクの結果を確認して、DLLの配置アドレスを調べるために使います。
/BASEの指定は、DLLの標準配置アドレスを指定します。DLLのなかの機械語は、この位置に展開されたときに動作するように生成されます。ディフォルトでは0x4000000などになっているので、明示的に指定する意味も込めて、0x100000にしておきます。ブートローダはイメージのこのアドレス(付近)にロードすることになります。
/MARGEによって余分なセクションが結合されます。
/FIXEDによって再配置情報が破棄されます。
コンパイルがうまくいくと、kernel.map というファイルが生成されています。その中身をみてみましょう。
kernel Timestamp is 435e69ab (Wed Oct 26 02:21:47 2005) Preferred load address is 00100000 Start Length Name Class 0001:00000000 00000005H .entry CODE 0001:00000010 00023a07H .text CODE 0002:00000000 00008861H .rdata DATA 0002:00008861 00000000H .edata DATA 0003:00000000 00000240H .data DATA 0003:00000240 0000141cH .bss DATA Address Publics by Value Rva+Base Lib:Object 0000:00000000 ___safe_se_handler_count 00000000 <absolute> 0000:00000000 ___safe_se_handler_table 00000000 <absolute> 0001:00000000 _entrypoint@12 00101000 f _entrypoint.obj
注目するべきなのは、__safe_se_handler_* というエントリのすぐ次に _entrypoint 関数が配置されていることと、そのRVA (Relative Virtual Address)が0x00101000であることです。
この後の作業でDLLをstripコマンドで処理しますが、これによってできるイメージファイルは、ファイルの一バイト目が_entrypoint関数の本体になります。ここで、_entrypoint関数は0x00101000番地に配置されたときに動作するようにリンクされたことが上記のマップファイルから読み取れますので、結果的に、stripで処理したファイルを0x00101000に配置すれば動作しそうだと分かるのです。
kernel.dll を Cygwin の strip で処理します。
$ strip -o kernel.img -O binary kernel.dll
これによって、PE形式から、DLLがメモリ上のロードされたときの生イメージを得ることができます。この得られたファイルはPE形式などという構造化はされていませんので、使うときには単純に読み込んでメモリにコピーするだけで利用できます。
あとは、上記 kernel.img をブートローダで読み込み、ファイルの先頭が0x00101000になるように配置し、0x00101000番地に制御を移してください。このとき、各種セグメントとスタックポインタなどは予め設定されている必要があります。