この記事は BitVisor Advent Calender 2017,20 日目の記事(大遅刻)です。
UEFI 向けのローダーでも読んで記事書くかーって思ってたら先にやられてしまってウンウン唸ってましたが, 彼の記事の続きを勝手に書けば良いことに気がつきました。
では元気よく,UEFI の 1st stage loader から
いきなり
リンカスクリプト 2 行目,
で,その後,32-bit なら
でももう書き始めちゃったのでこのまま書いちゃいます。
まず後々
→えいらくさんから解説頂きました。
そのあとは,
ここからは簡単で,セグメントレジスタを全部スタックに積んで,
さて,その後設定した PML4 テーブルのアドレスを
まずは 1st stage loader で
頻りに
をみると一瞬ページテーブルを UEFI に戻して,第二引数に渡した UEFI で扱う SystemTable とかの情報を第一引数で渡した変数の物理アドレスに書き込んでから元のページテーブルに戻してるっぽい。
これによって,60 行目まで
これを使って BitVisor のサイズを取得してから,86 行目でこのサイズに必要なぶんだけ UEFI の機能でページを確保しています。
そのあと,96 行目(
108 行目では改めて UEFI の機能で確保したページに BitVisor の読み込んだバイナリをコピーしてます。続く 112 行目から続くループで,64 KiB しかロードしてなかった
その後,ロードが完了したら
まずこの関数には引数として改めてロードした BitVisor の確保されているアドレスの先頭位置が
479 行目から 485 行目ではメモリの初期化をしており,486 行目から 492 行目は
とりあえず,BitVisor 本体の処理に飛ぶまでの,VMM Loader(2nd stage)を読んできました。間違って読んでる箇所があったら教えてください。 BitVisor はなんとなくどういうことに使われてるかは知ってたけどちゃんとした処理は知らなかったので,最初のローダーの部分だけでも,後々の切り替えや UEFI に戻るための仕掛けなんかがとてもなるほどと感心しました。すごい。どうせなのでこの後の部分も色々読もうと思います。
UEFI 向けのローダーでも読んで記事書くかーって思ってたら先にやられてしまってウンウン唸ってましたが, 彼の記事の続きを勝手に書けば良いことに気がつきました。
では元気よく,UEFI の 1st stage loader から
entry_func
に飛んだ後を追っていきましょう。いきなり
find
と grep
を使っても良いのですが,折角手掛りがあるのでまずリンカスクリプト, bitvisor.lds
を読んでみます。1st stage loader を読む記事に因ると,bitvisor.elf
を 0x10000
だけ読んで,paddr
(おそらく物理アドレス,physical address の略でしょう)に読んで,そこからエントリポイントを計算して,entry_func
に飛んでるらしいです。bitvisor.lds
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ENTRY(entry) | |
virt = 0x40100000; | |
phys = 0x00100000; | |
PHDRS { | |
all PT_LOAD; | |
} | |
SECTIONS { | |
. = virt; | |
head = .; | |
. = virt + SIZEOF_HEADERS; | |
.entry : AT (phys + (code - head)) { | |
code = .; | |
*(.entry) | |
*(.entry.text) | |
*(.entry.data) | |
} :all | |
.text : AT (phys + (text - head)) { | |
text = .; | |
*(.text) | |
codeend = .; | |
} | |
.data : AT (phys + (data - head)) { | |
data = .; | |
*(.data) | |
. = ALIGN (8); | |
__initfunc_start = .; | |
*(.initfunc) | |
__initfunc_end = .; | |
__process_builtin = .; | |
*(.processes) | |
__process_builtin_end = .; | |
dataend = .; | |
} | |
.bss : AT (phys + (bss - head)) { | |
bss = .; | |
*(.bss) | |
*(COMMON) | |
. = ALIGN (4096); | |
} | |
end = .; | |
/DISCARD/ : { | |
*(.gomi) | |
} | |
} |
phys = 0x00100000;
となっており,11 行目, .entry : AT (phys + (code - head)) {
をみるとこのアドレスは bitvisor.elf
の .entry
というセクションになってるらしいので,このセクションを探せば良さそう。ついでに,このスクリプトから bitvisor.elf
がメモリに置かれるときの仮想アドレスの開始位置もわかります。core/entry.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######## ENTRY SECTION START | |
# This section is placed at the start of the .text section. | |
# See the linker script. | |
.section .entry | |
# GRUB Multiboot Header | |
.align 4 | |
.long MULTIBOOT_HEADER_MAGIC | |
.long MULTIBOOT_HEADER_FLAGS | |
.long CHECKSUM | |
# 32bit code start | |
.code32 | |
.globl entry | |
entry: | |
# boot/loader jumps to here in real-address mode | |
test $0xF9F9F9F9,%eax # (test;stc;stc in 16bit mode) | |
jc 1f # must be short jump for 16bit & 32bit | |
shl %al | |
inc %eax # (rex prefix in 64bit mode) | |
rcr %al # undo %al | |
jnc uefi64_entry | |
jmp multiboot_entry |
.entry
セクションをみると,まず multiboot のヘッダが書いてあって,次に 32-bit のコードが始まってます。test
命令の結果,もしリアルモードであるならば,jc
によってリアルモード用の初期化コードに飛んでますが,今回は割愛。で,その後,32-bit なら
jnc
命令で分岐せず multiboot のためのコードにジャンプ,ロングモードなら uefi64_entry
に分岐しているわけですが,この分岐のやりかたが結構トリッキーというか,あんまりちゃんとなんでこれでいいのかわかってないなあと思ったら,
BitVisor本体のブート仕様 2 年前のえいらくさんの記事にしっかり書いてあった……。やだ,もしかしてこの記事を書く必要なかった……?でももう書き始めちゃったのでこのまま書いちゃいます。
core/entry.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
uefi64_entry: | |
.if longmode | |
# The entry_pd that will not be used in 64bit mode is used for | |
# a page table during this routine | |
push %rbx | |
push %rsi | |
push %rdi | |
mov %rcx,%rdi | |
mov %rdx,%rsi | |
mov %r8,%rdx | |
push %rbp | |
push %r12 | |
push %r13 | |
push %r14 | |
push %r15 | |
push %rcx | |
xor %ecx,%ecx | |
lea entry_pd(%rip),%ebx | |
lea head-0x100000+0x3(%rip),%eax | |
1: | |
mov %rax,(%rbx,%rcx,8) | |
add $0x1000,%eax | |
add $1,%ecx | |
cmp $512,%ecx | |
jb 1b | |
lea 7(%ebx),%eax | |
mov %rax,(%rbx) | |
lea head-0x100000(%rip),%rax | |
add %rax,entry_pml4(%rip) | |
add %rax,entry_pdp+0(%rip) | |
add %rax,entry_pdp+8(%rip) | |
addq $entry_pd-entry_pd0,entry_pdp+8(%rip) | |
sub $head-0x100000,%rax | |
mov %rax,uefi_entry_physoff(%rip) | |
pop %rcx | |
mov %es,%eax | |
push %rax | |
mov %ss,%eax | |
push %rax | |
mov %ds,%eax | |
push %rax | |
mov %fs,%eax | |
push %rax | |
mov %gs,%eax | |
push %rax | |
mov %cr3,%rax | |
mov %rax,uefi_entry_cr3(%rip) | |
mov %rsp,uefi_entry_rsp(%rip) | |
lea uefi_entry_ret(%rip),%rax | |
mov %rax,uefi_entry_ret_addr(%rip) | |
lea entry_pml4(%rip),%rax | |
cli | |
mov %rax,%cr3 | |
mov $start_stack,%rsp | |
mov $1f,%rax | |
jmp *%rax | |
1: | |
call uefi_init | |
mov %rax,1b(%rip) | |
mov uefi_entry_physoff(%rip),%rax | |
call uefi_entry_rip_plus_rax | |
mov uefi_entry_rsp(%rip),%rsp | |
mov uefi_entry_cr3(%rip),%rax | |
mov %rax,%cr3 | |
mov 1b(%rip),%rax | |
uefi_entry_ret: | |
pop %rbx | |
mov %ebx,%gs | |
pop %rbx | |
mov %ebx,%fs | |
pop %rbx | |
mov %ebx,%ds | |
pop %rbx | |
mov %ebx,%ss | |
pop %rbx | |
mov %ebx,%es | |
pop %r15 | |
pop %r14 | |
pop %r13 | |
pop %r12 | |
pop %rbp | |
pop %rdi | |
pop %rsi | |
pop %rbx | |
ret |
uefi_init
関数の引数として 1st stage loader から渡された引数を渡すために,整数として渡されてる rcx, rdx
を ポインタとして扱うため rdi, rsi
へ,r8
は rdx
にコピーしておいて,準備してますね。そのあとは uefi_init
を呼び出すまでページテーブル準備してるだけっぽいですね。core/entry.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Provisional page tables | |
.align PAGESIZE | |
entry_pml4: # 7654321|76543210 | |
.long entry_pdp-DIFFPHYS+0x7 # 0x0000000000000000 | |
.long 0 # | |
.space 4096-8*1 # 0x0000008000000000- | |
.globl entry_pdp | |
entry_pdp: | |
.long entry_pd0-DIFFPHYS+0x7 # 0x0000000000000000 | |
.long 0 # | |
.long entry_pd0-DIFFPHYS+0x7 # 0x0000000040000000 | |
.long 0 # | |
.long entry_pd0-DIFFPHYS+0x7 # 0x0000000080000000 | |
.long 0 # | |
.quad 0 # 0x00000000C0000000 | |
.space 4096-8*4 # 0x0000000100000000- | |
.globl entry_pd0 | |
entry_pd0: | |
.rept 512 # +0x00000000- | |
.quad 0x83 + (. - entry_pd0) / 8 * 0x200000 | |
.endr | |
.globl entry_pd | |
entry_pd: # Page directory for PAE OFF # 7654321|76543210 | |
.rept 3 # +0x00000000- | |
1: | |
.rept 256 | |
.long 0x83 + (. - 1b) / 4 * 0x400000 | |
.endr | |
.endr | |
.space 1024 # +0xC0000000- | |
# Stack for initialization | |
.align PAGESIZE | |
.space PAGESIZE | |
start_stack: | |
######## ENTRY SECTION END |
entry_pd
とかのページディレクトリとかの定義はこの通り。まあこれも見たとおり。uefi64_entry
17 行目(この Gist での行数。以下,注釈なく行数が書かれた場合基本的に Gist に貼ったコードでの行数とする。)で ecx
レジスタはゼロにされており,また,18 行目でページディレクトリのアドレスを ebx
レジスタに(entry_pd
ラベルは相対アドレスなので,インストラクションポインタに足して絶対アドレスを取ってる,で合ってますよね……?mov a, %rax
と mov a(%rip), %rax
は同じだけど前者が絶対アドレス,後者が %rip
相対アドレスになるそうです。),19 行目でこの行に続く 20 行目のラベルの位置の仮想アドレス計算して,mov a,%rax と mov a(%rip),%rax は同じアドレスのアクセスなんですが、前者は絶対アドレス、後者は %rip 相対アドレスに変換されるみたいです。%rip 相対アドレスのほうが再配置に適し、命令長を抑えられるのと、64bit の絶対アドレスが使える命令は限られるので、よく相対アドレスが使われます。
— Hideki EIRAKU (@hdk_2) 2017年12月22日
eax
レジスタにロードしているようです。ここで出てくる head
は,リンカスクリプトの 9 行目で定義されてるアドレスですね。21 行目〜25 行目のループで,さきほど計算した仮想アドレスを格納している rax
レジスタとページディレクトリの物理アドレスを格納している rbx
を利用して,ecx
をループカウンタにしてエントリを 512 コほど設定しています。そのあとは,
entry_pd
の先頭アドレスから 7 バイト目を entry_pd
の最初に設定したり,add
を利用して PML4 テーブルと PDP テーブルを設定してますね。また,この時,rax
レジスタの中身が,head-0x100000
にページテーブルのぶんのアドレスを足したアドレスになってるので,ここから head-0x100000
を引いたアドレスを uefi_entry_physoff
に入れて,このエントリポイントの物理アドレスのオフセットを格納しているようです。ここからは簡単で,セグメントレジスタを全部スタックに積んで,
cr3
のアドレスとスタックポインタのアドレスをそれぞれ uefi_entry_cr3
と uefi_entry_rsp
に記録し,また,uefi_entry_ret
という関数の終了処理をする部分のアドレスを uefi_entry_ret_addr
に記録しています。これは,おそらく,えいらくさんのスライドを参考にするに VMM から UEFI に戻るときにこれらの記録したアドレスをひっぱり出してまた UEFI に帰ってくるためではないでしょうか?さて,その後設定した PML4 テーブルのアドレスを
cr3
レジスタに設定し,スタックのアドレスを設定し,57 行目のラベルにジャンプします。ここで,uefi_init
関数をコールしています。core/uefi.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int SECTION_ENTRY_TEXT | |
uefi_init (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab, void **boot_options) | |
{ | |
EFI_BOOT_SERVICES *uefi_boot_services; | |
EFI_FILE_HANDLE file; | |
u64 loadaddr, loadsize; | |
u64 uefi_read; | |
u64 alloc_addr64, readsize; | |
ulong alloc_addr; | |
u32 vmmsize, align, ret, loadedsize, blocksize, npages; | |
int freesize; | |
extern u8 dataend[]; | |
EFI_SIMPLE_TEXT_IN_PROTOCOL *conin; | |
EFI_SIMPLE_TEXT_OUT_PROTOCOL *conout; | |
struct bitvisor_boot bitvisor_opt; | |
u64 boot_opt_addr; | |
uefi_boot_param_ext_addr = (ulong)boot_options; | |
uefi_image_handle = (ulong)image; | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_conin), | |
&systab->ConIn, sizeof uefi_conin); | |
conin = uefi_conin; | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_conin_read_key_stroke), | |
&conin->ReadKeyStroke, | |
sizeof uefi_conin_read_key_stroke); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_conout), | |
&systab->ConOut, sizeof uefi_conout); | |
conout = uefi_conout; | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_conout_output_string), | |
&conout->OutputString, | |
sizeof uefi_conout_output_string); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_boot_services), | |
&systab->BootServices, sizeof uefi_boot_services); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_allocate_pages), | |
&uefi_boot_services->AllocatePages, | |
sizeof uefi_allocate_pages); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_free_pages), | |
&uefi_boot_services->FreePages, | |
sizeof uefi_free_pages); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_wait_for_event), | |
&uefi_boot_services->WaitForEvent, | |
sizeof uefi_wait_for_event); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_get_memory_map), | |
&uefi_boot_services->GetMemoryMap, | |
sizeof uefi_get_memory_map); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_locate_handle_buffer), | |
&uefi_boot_services->LocateHandleBuffer, | |
sizeof uefi_locate_handle_buffer); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_free_pool), | |
&uefi_boot_services->FreePool, sizeof uefi_free_pool); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_open_protocol), | |
&uefi_boot_services->OpenProtocol, | |
sizeof uefi_open_protocol); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_close_protocol), | |
&uefi_boot_services->CloseProtocol, | |
sizeof uefi_close_protocol); | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_disconnect_controller), | |
&uefi_boot_services->DisconnectController, | |
sizeof uefi_disconnect_controller); | |
read_configuration_table (systab); | |
if (!boot_options) { | |
_PRINT ("Fatal: Boot options not found\n"); | |
return 0; | |
} | |
boot_opt_addr = boot_param_get_phys (&boot_opt_uuid); | |
if (boot_opt_addr == 0x0) { | |
_PRINT ("Fatal: Cannot find boot handler\n"); | |
return 0; | |
} | |
uefi_entry_pcpy (uefi_entry_virttophys (&bitvisor_opt), | |
(void *)(ulong)boot_opt_addr, | |
sizeof (bitvisor_opt)); | |
loadaddr = bitvisor_opt.loadaddr; | |
loadsize = bitvisor_opt.loadsize; | |
file = bitvisor_opt.file; | |
uefi_entry_pcpy (uefi_entry_virttophys (&uefi_read), | |
&file->Read, sizeof uefi_read); | |
uefi_init_get_vmmsize (&vmmsize, &align); | |
vmmsize = (vmmsize + PAGESIZE - 1) & ~PAGESIZE_MASK; | |
alloc_addr64 = 0xFFFFFFFF; | |
npages = (vmmsize + align - 1) >> PAGESIZE_SHIFT; | |
ret = uefi_entry_call (uefi_allocate_pages, 0, | |
1 /* AllocateMaxAddress */, | |
8 /* EfiUnusableMemory */, | |
npages, uefi_entry_virttophys (&alloc_addr64)); | |
if (ret) { | |
_PRINT ("AllocatePages failed "); | |
_printhex (ret, 8); | |
_PRINT ("\n"); | |
return 0; | |
} | |
_PRINT ("Load"); | |
alloc_addr = alloc_addr64; | |
if (alloc_addr % align) | |
alloc_addr += align - (alloc_addr % align); | |
freesize = (alloc_addr - alloc_addr64) >> PAGESIZE_SHIFT; | |
if (freesize > 0) | |
uefi_entry_call (uefi_free_pages, 0, alloc_addr64, freesize); | |
freesize = npages - freesize - (vmmsize >> PAGESIZE_SHIFT); | |
if (freesize > 0) | |
uefi_entry_call (uefi_free_pages, 0, alloc_addr + vmmsize, | |
freesize); | |
_PRINT ("ing "); | |
uefi_entry_pcpy ((u8 *)alloc_addr + 0x100000, (u8 *)(ulong)loadaddr, | |
loadsize); | |
loadedsize = loadsize; | |
blocksize = (((dataend - head) / 64 + 511) / 512) * 512; | |
do { | |
_putchar ('.'); | |
readsize = dataend - head - loadedsize; | |
if (readsize > blocksize) | |
readsize = blocksize; | |
ret = uefi_entry_call (uefi_read, 0, file, | |
uefi_entry_virttophys (&readsize), | |
(void *)(alloc_addr + 0x100000 + | |
loadedsize)); | |
if (ret) { | |
_PRINT ("\nRead error.\n"); | |
return 0; | |
} | |
loadedsize += readsize; | |
} while (dataend - head > loadedsize && readsize > 0); | |
_PRINT ("\n"); | |
if (dataend - head > loadedsize) { | |
_PRINT ("Load failed\n"); | |
return 0; | |
} | |
uefi_entry_start (alloc_addr); | |
} |
uefi_init
関数はこの通り。まずは 1st stage loader で
entry_func
の引数として渡されている UEFI ローダーの引数とブートオプションをこの関数の引数として受けとってます。頻りに
uefi_entry_pcpy
を呼び出してますが,これは,core/entry.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
uefi_entry_pcpy: | |
mov uefi_entry_physoff(%rip),%rax | |
call uefi_entry_rip_plus_rax | |
mov uefi_entry_cr3(%rip),%rax | |
mov %rax,%cr3 | |
lea entry_pml4(%rip),%rax | |
cld | |
1: | |
sub $8,%rdx | |
jb 1f | |
movsq | |
jne 1b | |
mov %rax,%cr3 | |
ret | |
1: | |
add $8,%edx | |
.byte 0xA8 # test $imm,%al | |
1: | |
movsb | |
sub $1,%edx | |
jnb 1b | |
mov %rax,%cr3 | |
ret | |
uefi_entry_rip_plus_rax: | |
add %rax,(%rsp) | |
ret |
uefi_entry_pcpy
で add
とか sub
とかを rdx
レジスタにしてるのは書き込みたい変数のサイズを確認してるからで,movsq
や movsb
でオペランドが指定されてないのは,この関数の第一引数と第二引数として既に rdi
と rsi
が設定されてるから第二引数から第一引数へデータがコピーされてる,んですよね?これによって,60 行目まで
SystemTable
と BootServices
が持つ重要な関数のアドレスや情報をひっぱってきてます。
その後,起動時のオプションから,bitvisor.elf
のファイルハンドルと,読み込んだアドレスと,そのサイズを取得しています。その後,
core/mm.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void __attribute__ ((section (".entry.text"))) | |
uefi_init_get_vmmsize (u32 *vmmsize, u32 *align) | |
{ | |
*vmmsize = VMMSIZE_ALL; | |
*align = 0x400000; | |
} |
_PRINT ("Load");
)から 107 行目(_PRINT ("ing");
)の間でアライメントして余計なページを開放してるみたいですね(ちょっと本当にそうなのか自信がないです)。しかしこの“Loading”の表示のしかたちょっと面白くて好きです。108 行目では改めて UEFI の機能で確保したページに BitVisor の読み込んだバイナリをコピーしてます。続く 112 行目から続くループで,64 KiB しかロードしてなかった
bitvisor.elf
の残りのデータも全部ロードしています。その後,ロードが完了したら
uefi_entry_start
を呼び出し,core/entry.s
に戻ってきています。
core/entry.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
uefi_entry_start: | |
.if longmode | |
mov uefi_entry_physoff(%rip),%rax | |
call uefi_entry_rip_plus_rax | |
mov uefi_entry_cr3(%rip),%rax | |
mov %rax,%cr3 | |
mov uefi_entry_physoff(%rip),%rax | |
add $head-0x100000,%rax | |
neg %rax | |
add %rdi,%rax | |
add %rax,entry_pml4-DIFFPHYS(%rdi) | |
add %rax,entry_pdp+0-DIFFPHYS(%rdi) | |
add %rax,entry_pdp+8-DIFFPHYS(%rdi) | |
mov %rdi,%rax | |
mov $0x83,%al | |
xor %ebx,%ebx | |
1: | |
mov %rax,entry_pd-DIFFPHYS(%rdi,%rbx,8) | |
add $0x200000,%rax | |
add $1,%ebx | |
cmp $512,%ebx | |
jb 1b | |
lea entry_pml4-DIFFPHYS(%rdi),%rax | |
mov %rax,%cr3 | |
mov %edi,vmm_start_phys | |
sgdtq calluefi_uefi_gdtr | |
sidtq calluefi_uefi_idtr | |
sldt calluefi_uefi_ldtr | |
mov %es,calluefi_uefi_sregs+0 | |
mov %cs,calluefi_uefi_sregs+2 | |
mov %ss,calluefi_uefi_sregs+4 | |
mov %ds,calluefi_uefi_sregs+6 | |
mov %fs,calluefi_uefi_sregs+8 | |
mov %gs,calluefi_uefi_sregs+10 | |
mov uefi_entry_cr3(%rip),%rax | |
mov %rax,calluefi_uefi_cr3 | |
mov $bss,%edi # Clear BSS | |
mov $end+3,%ecx # | |
sub %edi,%ecx # | |
shr $2,%ecx # | |
xor %eax,%eax # | |
cld # | |
rep stosl # | |
mov %cr4,%rcx | |
or $(CR4_PAE_BIT|CR4_PGE_BIT),%rcx | |
and $~CR4_MCE_BIT,%rcx | |
mov %cr3,%rax | |
mov %rcx,entry_cr4 # Save CR4 | |
mov %rax,vmm_base_cr3 # Save CR3 | |
mov %rcx,%cr4 | |
lgdtq entry_gdtr # Load GDTR | |
ljmpl *1f | |
1: | |
.long callmain64 | |
.long ENTRY_SEL_CODE64 | |
.else | |
ret | |
.endif |
rdi
レジスタに保存されています。
また,関数のプロローグでまたページテーブルやスタックを UEFI のものに戻したあとに,DIFFPHYS
に設定されている値 0x40000000
,つまりいままで 2nd stage loader が使ってた仮想アドレスのローダーが読まれてた位置と BitVisor がロードされているアドレスを足した値を entry_pml4
などのラベルから引いたアドレスに,rdi
の値を設定しています。これで,読み込み直して BitVisor の物理アドレスがズレた分だけ動かした値をページテーブルに改めて設定しているようですね。466 行であらためて cr3
レジスタに新しいページテーブルを登録した後に,BitVisor のロードされている先頭アドレスを vmm_start_phys
に保存して,BitVisor がロードされている物理アドレスを保存しておきます。また,GDT
や IDT
,セグメントレジスタ,UEFI のページテーブルへのアドレスといったものは,全部 calluefi_uefi_*
へ保存しています。これの実体は core/calluefi_asm.s
にあります。479 行目から 485 行目ではメモリの初期化をしており,486 行目から 492 行目は
cr4
レジスタの設定と cr3, cr4
レジスタの保存をして,GDTR
のアドレスをロードしてから,496 行目で callmain64
を呼び出しています。core/entry.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
callmain64: | |
mov $ENTRY_SEL_DATA64,%eax | |
xor %ebx,%ebx | |
mov %eax,%ds | |
mov %eax,%es | |
mov %ebx,%fs | |
mov %ebx,%gs | |
mov %eax,%ss | |
mov $start_stack,%esp | |
mov $bspinit_done,%eax | |
cmpb $0,(%rax) # BSP? | |
jne 1f # No- | |
movb $1,(%rax) | |
# BSP | |
mov $entry_ebx,%eax | |
mov (%rax),%edi | |
call vmm_main | |
cli | |
hlt | |
1: | |
# AP | |
call apinitproc0 | |
cli | |
hlt |
ds,es,ss
を 0x20,fs,gs
をゼロに設定し,スタックポインタを設定,最初は bspinit_done
に何も触れてないので jne
で分岐せずにそのまま処理を続け,vmm_main
に飛ぶことで,ようやく BitVisor 本体の処理が始まります。なお,
マルチコアで,BSP にならなかったコアだと,bspinit_done が既に設定されている状態で jne で分岐し,apinitproc0 の呼び出しのほうに行くと思われます
とりあえず,BitVisor 本体の処理に飛ぶまでの,VMM Loader(2nd stage)を読んできました。間違って読んでる箇所があったら教えてください。 BitVisor はなんとなくどういうことに使われてるかは知ってたけどちゃんとした処理は知らなかったので,最初のローダーの部分だけでも,後々の切り替えや UEFI に戻るための仕掛けなんかがとてもなるほどと感心しました。すごい。どうせなのでこの後の部分も色々読もうと思います。