multiboot仕様でブートローダを自作せずGRUBから自作カーネル(もどき)を起動できたので、起動情報も色々受け取りたい。multiboot準拠していれば、起動直前にブートローダがレジスタEAXにマジック値を、レジスタEBXに起動情報へのアドレスを設定してくれているそう。
やりたいこと
- レジスタEAXに設定されたマジック値を変数に代入する
- レジスタEBXに設定された起動情報アドレスを変数に代入する
_start関数をnaked関数化
普通に_start関数が動くとコンパイラ君が色々設定してくれる(スタックとか?)のでCPUレジスタが破壊されてしまう。chatGPT君に聞いたらnaked関数にすればいいよ!と教えてくれた。
naked関数はコンパイラ君が色々設定してくれるコードを生成しない関数のことなのだけど、実際はどう違うんだろうか。
通常関数のコード
#![no_std]
#![no_main]
#[unsafe( no_mangle )]
pub extern "C" fn _start() -> !
{
let num = 0xDEAD_BEEF;
test( num );
}
#[unsafe( no_mangle )]
fn test( _num: u32 ) -> !
{
loop {}
}
_startをloopだけにしたらjmp命令しか生成してくれなかったので、一個関数かますことに・・・。
naked関数のコード
#![no_std]
#![no_main]
use core::arch::naked_asm;
#[unsafe( naked )]
#[unsafe( no_mangle )]
pub unsafe extern "C" fn _start() -> !
{
naked_asm!( "
cli
hlt
" )
}
ディスアセンブリしてみる
以下のようなコマンドで。
objdump -S -M intel kernel | less
通常関数
00100030 <_start>:
100030: 53 push ebx
100031: 83 ec 08 sub esp,0x8
100034: e8 00 00 00 00 call 100039 <_start+0x9>
100039: 5b pop ebx
10003a: 81 c3 2f 00 00 00 add ebx,0x2f
100040: c7 44 24 04 ef be ad mov DWORD PTR [esp+0x4],0xdeadbeef
100047: de
100048: c7 04 24 ef be ad de mov DWORD PTR [esp],0xdeadbeef
10004f: e8 0c 00 00 00 call 100060 <test>
100054: cc int3
100055: cc int3
100056: cc int3
100057: cc int3
100058: cc int3
100059: cc int3
10005a: cc int3
10005b: cc int3
10005c: cc int3
10005d: cc int3
10005e: cc int3
10005f: cc int3
00100060 <test>:
100060: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4]
100064: eb fe jmp 100064 <test+0x4>
100030はebxがcallee-savedなのでEBXの保存。
100031は_start関数のスタック確保。
100034は何がしたいんだろう…スタックに戻り先アドレス(100039)を入れたい?んだろうけど
100039で戻り先アドレス(100039)をebxに入れている。ここもまだこれだけだと謎。
10003aでebx+=0x2F。100068になるわけだけど、それはtest関数のちょうど終わりのアドレスだ。謎。お試しコードが特殊(無限ループ)すぎて本当はなにをしようとしているかわからない。忘れよう。
100040はlet numに0xDEAD_BEEFを代入しているところ。
100048はtest関数の引数用にnumをコピーしているとこかな。
naked関数
00100030 <_start>:
100030: fa cli
100031: f4 hlt
うん、なんにもしないね。
EAXとEBXを引数で渡す
rustのABIは未安定(今後変わるかもしれない)なので、CのABI(もっと明示的にcdeclじゃなくていいんかな・・・)を指定した関数mainを関数_startから呼び出してやる。
EAXとEBXはスタックに右から引数として積んであげれば渡せる。
#[unsafe( naked )]
#[unsafe( no_mangle )]
pub unsafe extern "C" fn _start() -> !
{
naked_asm!( "
push ebx
push eax
call main
cli
hlt" );
}
#[unsafe( no_mangle )]
extern "C" fn main( eax: u32,
ebx: u32 ) -> !
{
let magic = eax;
loop {}
}
アセンブリで見る
00100030 <_start>:
100030: 53 push ebx
100031: 50 push eax
100032: e8 09 00 00 00 call 100040 <main>
100037: fa cli
100038: f4 hlt
100039: cc int3
10003a: cc int3
10003b: cc int3
10003c: cc int3
10003d: cc int3
10003e: cc int3
10003f: cc int3
00100040 <main>:
hlt
100040: 8b 44 24 08 mov eax,DWORD PTR [esp+0x8]
100044: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4]
100048: eb fe jmp 100048 <main+0x8>
たぶんいい感じ。

