Help us understand the problem. What is going on with this article?

BitVisorのブートシーケンス解説 (UEFI編)

More than 1 year has passed since last update.

最近BitVisorのブートシーケンス(UEFI)を真面目に追うことがあったので,それについて簡単にまとめます.

BitVisorの起動については既にいろいろと文章があります(参考文献参照)が,ここでは主に全体の起動の流れに着目して説明しています.

UEFIブートの概要

UEFIブートの仮想化の全体像は以下のようになります.

image.png

loadvmm.efiからbitvisor.elfの先頭部分(2nd-loader)を読み出し,BitVisor側に制御を移します.
2nd-loaderはbitvisorl.elf全体を読み出したあと,初期化をして最終的にVMMを起動します.
VMMはゲストとして最初トランポリンコードを実行し,このトランポリンコードからloadvmm.efiへreturnしますが,この時点で既にCPUは仮想化状態にあります.

なお,BitVisorは64bit UEFI firmwareのみサポートしています.この場合longmodeで処理が開始します.

UEFIブートの順序 (BSP)

entryから概ね以下の順序でVMMを起動します.

loader.elf:entry_func()
   => entry.s:entry
   => entry.s:uefi64_entry
   => uefi.c:uefi_init
   => entry.s:uefi_entry_start
   => entry.s:callmain64
   => main.c:vmm_main
   => call_initfunc("global")
   => main.c:start_all_processors
   => main.c:bsp_continue
   => (change stack)
   => main.c:bsp_proc
      => call_initfunc("bsp")
      => call_initfunc("para")
      => call_initfunc("pcpu")
  => main.c:create_pass_vm (from "pcpu" intfunc)
      => bps == true
      => vcpu.c:load_new_vcpu()
      => call_initfunc("pass")
      => vmmcall_boot.c:vmm_call_boot_enable()
         => create vmmcall_boot_thread
           => wait_for_boot_continue
           => continue_flag = false
           => wait until continue_flag become true
         => schedule => vmmcall_boot_thread
      => (scheduled from vmmcall_boot_thread)
   => vmctrl.start_vm (vt_main.c:vt_start_vm)
   => vt_mainloop()

init thread

vmmcall_boot.c:vmmcall_boot_thread
  => main.c:bsp_init_thread
    => main.c:initregs
       => calluefi.c:copy_uefi_bootcode()
         => if boot from uefi-loader-login
           => vmmcall_boot_continue
               => continue_flag = true
           => schedule
           => return to create_pass_vm
           => (run vm w/o driver initialization)
           => return to loader (boot/uefi-loader-login/loadvmm.c)
              => authentication is performed in the loader
              => loadvmm.c:decrypt_intl
                  => vmmcall_loadcfg64_intl => vmmcall_boot.c:loadcfg64
                  => vmmcall_boot_intel => vmmcall_boot.c:boot_guest
                      => wait_for_boot_continue
                      => continue_flag = false
                      => as a result, bsp_init_thread wakes up
                      => vmmcall handler wait until continue_flag become true
                      => call_initfunc("config0")
       => call_initfunc("drivers")
       => call_initfunc("config0")
  => continue_flag = true
  => thread exit
  => schedule
  => return to create_pass_vm

BitVisorのブートコードはBIOSブートの場合と,UEFIブートの場合両方の処理が記載されています.
UEFIブートの場合は基本if(uefi_booted)の中身を見ていくことになります.

init threadについては後述します.

initfunc

call_initfunc()ではINITFUNCマクロで登録された関数をアルファベット順に呼び出しています.

initfuncで呼ばれる関数はconfigにより変わりますが,例えば以下のような順序で呼ばれます. (CPUコア数2の場合)

global, global0, core/callrealmode.c:callrealmode_init_global
global, global0, core/panic.c:panic_init_global
global, global0, core/printf.c:printf_init_global
global, global0, core/seg.c:segment_init_global
global, global0, core/sleep.c:sleep_init_global
global, global0, core/tty.c:tty_init_global
global, global1, core/int.c:int_init_global
global, global1, core/main.c:print_boot_msg
global, global2, core/mm.c:mm_init_global
global, global3, core/acpi.c:acpi_init_global
global, global3, core/main.c:copy_minios
global, global3, core/main.c:get_shiftflags
global, global3, core/panic.c:panic_init_global3
global, global3, core/process.c:process_init_global
global, global3, core/thread.c:thread_init_global
global, global3, core/time.c:time_init_global
global, global3, core/tty.c:tty_init_global2
global, global3, core/vmmcall_dbgsh.c:vmmcall_dbgsh_init_global
global, global3, core/vmmcall_status.c:vmmcall_status_init_global
global, global4, core/cpu_mmu_spt.c:cpu_mmu_spt_init_global
global, global4, core/msg.c:msg_init_global
msg, msg0, core/beep.c:beep_init_msg
msg, msg0, core/keyboard.c:keyboard_init_msg
msg, msg0, core/panic.c:panic_init_msg
msg, msg0, core/reboot.c:reboot_init_msg
msg, msg0, core/serial.c:serial_init_msg
msg, msg0, core/sleep.c:sleep_init_msg
msg, msg0, core/time.c:time_init_msg
msg, msg0, core/vramwrite.c:vramwrite_init_msg
msg, msg0, drivers/pci_monitor.c:pci_monitor_init_msg
msg, msg0, ip/echoctl.c:echoctl_init_msg
msg, msg1, core/tty.c:tty_init_msg
global, global5, core/uefi_param_ext.c:uefi_param_ext_init
bsp, bsp0, core/gmm_pass.c:install_int0x15_hook
bsp, bsp0, core/main.c:debug_on_shift_key
bsp, bsp0, core/random.c:random_init_global
bsp, bsp0, core/vramwrite.c:vramwrite_init_bsp
paral0, paral00, core/vcpu.c:vcpu_init_global
paral0, paral01, core/iccard.c:idman_init_global
paral0, paral01, core/time.c:time_init_global_status
paral0, paral01, core/vt_main.c:vt_register_status_callback
paral1, paral10, core/vmmcall.c:vmmcall_init
vmmcal, vmmcal0, core/vmmcall_boot.c:vmmcall_boot_init
vmmcal, vmmcal0, core/vmmcall_dbgsh.c:vmmcall_dbgsh_init
vmmcal, vmmcal0, core/vmmcall_status.c:vmmcall_status_init
paral2, paral20, core/timer.c:timer_init_global
paral3, paral30, core/acpi.c:acpi_init_paral
pcpu, pcpu0, core/cpu_mmu_spt.c:cpu_mmu_spt_init_pcpu
pcpu, pcpu0, core/initipi.c:initipi_init_pcpu
pcpu, pcpu0, core/nmi.c:nmi_init_pcpu
pcpu, pcpu0, core/thread.c:thread_init_pcpu
pcpu, pcpu2, core/main.c:virtualization_init_pcpu
pcpu, pcpu3, core/panic.c:panic_init_pcpu
pcpu, pcpu4, core/cache.c:cache_init_pcpu
pcpu, pcpu4, core/time.c:time_init_pcpu
pcpu, pcpu5, core/main.c:create_pass_vm
vcpu, vcpu0, core/cache.c:cache_init_vcpu
vcpu, vcpu0, core/cpuid.c:cpuid_init
vcpu, vcpu0, core/initipi.c:initipi_init
vcpu, vcpu0, core/io_io.c:io_io_init
vcpu, vcpu0, core/localapic.c:localapic_init
vcpu, vcpu0, core/mmio.c:mmio_init
vcpu, vcpu0, core/msr.c:msr_init
vcpu, vcpu0, core/nmi.c:nmi_init
vcpu, vcpu0, core/xsetbv.c:xsetbv_init
pass, pass0, core/cache.c:cache_init_pass
pass, pass0, core/cpuid_pass.c:cpuid_pass_init
pass, pass0, core/exint_pass.c:exint_pass_init
pass, pass0, core/gmm_pass.c:gmm_pass_init
pass, pass0, core/initipi_pass.c:initipi_pass_init
pass, pass0, core/io_iopass.c:io_iopass_init
pass, pass0, core/msr_pass.c:msr_pass_init
pass, pass0, core/nmi_pass.c:nmi_pass_init
pass, pass0, core/xsetbv_pass.c:xsetbv_pass_init
pass, pass1, core/io_iohook.c:setiohooks
pass, pass5, core/localapic.c:localapic_init2
config0, config00, core/random.c:random_init_config0
driver, driver0, core/mmio.c:mmio_debug
driver, driver0, core/vga.c:vga_init
driver, driver0, drivers/core.c:core_init
driver, driver0, drivers/pci_match_compat.c:pci_match_compat_init
driver, driver0, net/netapi.c:netapi_init
driver, driver1, drivers/nvme/nvme_crypt.c:nvme_crypt_ext
driver, driver1, drivers/nvme/nvme_storage_io.c:nvme_storage_io_ext
driver, driver1, idman/kernel.c:idman_kernel_init
driver, driver1, ip/net_main.c:net_main_init
driver, driver1, storage/kernel.c:storage_kernel_init
driver, driver1, vpn/kernel.c:vpn_kernel_init
driver, driver2, ip/telnet-dbgsh.c:telnet_dbgsh_init
driver, driver2, storage_io/storage_io.c:storage_io_initfunc
driver, driver3, drivers/ata/ata_init.c:ata_init
driver, driver3, drivers/ieee1394.c:ieee1394_init
driver, driver3, drivers/ieee1394log.c:ieee1394_thunderbolt_conceal
driver, driver3, drivers/ieee1394log.c:ieee1394log_init
driver, driver3, drivers/net/bnx.c:bnx_init
driver, driver3, drivers/net/pro100.c:pro100_init
driver, driver3, drivers/net/pro1000.c:vpn_pro1000_init
driver, driver3, drivers/nvme/nvme.c:nvme_init
driver, driver3, drivers/pci_conceal.c:pci_conceal_init
driver, driver3, drivers/pci_monitor.c:pci_monitor_init
driver, driver3, drivers/usb/ehci.c:ehci_init
driver, driver3, drivers/usb/uhci.c:uhci_init
driver, driver3, drivers/usb/xhci.c:xhci_init
driver, driver3, drivers/x540.c:x540_init
driver, driver4, core/iccard.c:idman_init
driver, driver5, core/vmmcall_iccard.c:vmmcall_iccard_init
driver, driver90, drivers/pci_init.c:pci_init
driver, driver95, drivers/iommu.c:iommu_setup
driver, driver99, drivers/ieee1394log.c:ieee1394_hint
config1, config10, drivers/net/bnx.c:bnx_set_hotplug

ap, ap0, core/mm.c:unmap_user_area
ap, ap0, core/process.c:process_init_ap
pcpu, pcpu0, core/cpu_mmu_spt.c:cpu_mmu_spt_init_pcpu
pcpu, pcpu0, core/initipi.c:initipi_init_pcpu
pcpu, pcpu0, core/nmi.c:nmi_init_pcpu
pcpu, pcpu0, core/thread.c:thread_init_pcpu
pcpu, pcpu2, core/main.c:virtualization_init_pcpu
pcpu, pcpu3, core/panic.c:panic_init_pcpu
pcpu, pcpu4, core/cache.c:cache_init_pcpu
dbsp, dbsp4, core/cache.c:update_mtrr_and_pat
dbsp, dbsp4, core/time.c:time_init_dbsp
pcpu, pcpu4, core/time.c:time_init_pcpu
dbsp, dbsp5, core/main.c:wait_for_create_pass_vm
pcpu, pcpu5, core/main.c:create_pass_vm
vcpu, vcpu0, core/cache.c:cache_init_vcpu
vcpu, vcpu0, core/cpuid.c:cpuid_init
vcpu, vcpu0, core/initipi.c:initipi_init
vcpu, vcpu0, core/io_io.c:io_io_init
vcpu, vcpu0, core/localapic.c:localapic_init
vcpu, vcpu0, core/mmio.c:mmio_init
vcpu, vcpu0, core/msr.c:msr_init
vcpu, vcpu0, core/nmi.c:nmi_init
vcpu, vcpu0, core/xsetbv.c:xsetbv_init
pass, pass0, core/cache.c:cache_init_pass
pass, pass0, core/cpuid_pass.c:cpuid_pass_init
pass, pass0, core/exint_pass.c:exint_pass_init
pass, pass0, core/gmm_pass.c:gmm_pass_init
pass, pass0, core/initipi_pass.c:initipi_pass_init
pass, pass0, core/io_iopass.c:io_iopass_init
pass, pass0, core/msr_pass.c:msr_pass_init
pass, pass0, core/nmi_pass.c:nmi_pass_init
pass, pass0, core/xsetbv_pass.c:xsetbv_pass_init
pass, pass1, core/io_iohook.c:setiohooks
pass, pass5, core/localapic.c:localapic_init2

なお,call_initfunc("para")では名前的に並列に実行可能な関数を呼び出しているようです.
もともとBIOSからブートした場合,マルチコアであれば並行して初期化を進めますが,どれかのコアで一度だけ実行すれば良い処理がこのinitfuncのparaで定義されているのだと思います.
ただし,後述するようにUEFIではAPの仮想化は遅延されるので,call_initfunc("para")はBSPのみで逐次的に実行されます.

init thread

create_pass_vmの中でスレッドを生成して一部の初期化をしています.
この部分はローダーの種類により,処理内容が異なります.

なお,BitVisorのスレッドは割り込みによるプリエンプションはありません.
スレッドが自らschedule()を呼び出すと他のスレッドがスケジュールされます.

uefi-loader

boot/uefi-loaderでは,init threadは以下のような挙動になります.

image.png

結果的にはinit threadは実際にはただの関数呼び出しのようになりす.
何故こういうことをしているかというと,もともとBIOSブートの方で後述のuefi-loader-loginみたいなことをしていて,その構成を再利用したからじゃないかと思います.多分.

uefi-loader-login

boot/uefi-loaderでは,init threadは以下のような挙動になります.

image.png

uefi-loader-loginではディスクの暗号化に関して認証化をおこないます.
暗号鍵が分からないとデバイスドライバの初期化ができないため,一旦ゲストをデバイスドライバの初期化なしで起動し,その後loadvmm.efiからVMCALLでパスワードを受け取り,認証が成功したらデバイスドライバの初期化をおこなう,という挙動になっています.

これを実現するためにinit threadでboot_continueフラグをうまく利用してVMMとゲストとの同期をとっています.

ゲストのトランポリンコード

calluefi.cのcopy_uefi_bootcode()で,一番はじめにVMENTRYした際に実行されるコードを作成し,レジスタに設定しています.

このときのトランポリンコードのジャンプ先のアドレスuefi_entry_ret_addrは最初のuefi64_entryの中でストアしています.

APの仮想化

loadvmm.efiを実行終了した段階では,BSP (Bootstrap Processor; 最初に起動するコア)のみ仮想化されています.
AP (Application Processor; BSP以外のコア)はこの時点では仮想化されていません.

このことは,loadvmm.efi呼び出し直後にdbgshからログを確認すると分かります.

APの仮想化は,ゲストがAPの初期化をするためにStartup-IPIをAPに対して送信しようとした段階でおこなわれます.
このために,IPIを送信するためのLocal APICのメモリアクセスを最初補足するようにしています.

AP仮想化のおおまかなコードの流れは以下のようになります.

BSP側

localapic.c:mmio_apic
  => handle_ap_start
    => ap.c:ap_start
    - copy cpuinit_start code to apinit addr
        => ap_start_addr
        => apic_send_startup_ipi
        => start AP
  => call_initfunc("dbsp")
  => main.c:wait_for_create_pass_vm

AP側

receive SIPI
  => entry.s:cpuinit_start
  => entry.s:call_main64
  => ap.c:apinitproc0
  => (change stack)
  => ap.c:apinitproc1
  => int_init_ap() (initialize IDT)
  => initproc_ap (= ap_initproc = ap_proc)
    => call_initfunc("ap")
    => call_initfunc("para")
    => call_initfunc("pcpu")
  => create_pass_vm
    => load_new_vcpu
    => vmctl.vminit
    => call_initfunc("pass")
    => initregs
    => vmctrl.init_signal
  => start_vm

BSP側からap_start_addr()でINIT, SIPI, SIPIと3つIPIを飛ばしてAPを起動させます.
この処理はmultiprocessor specificationに基づくものです.

SIPIを受け取ったコアは,IPIのベクタ番号(下位8bit) × 4096 の物理アドレスからリアルモードで実行を開始します.
BitVisorでは物理アドレスの0番地に一時的にトランポリンコードを挟み,初期化関数に飛ぶようにしています (ap.c:ap_start()のmapmemの処理).
(そういえば,もし仮に物理アドレス0番地が使用不可能領域だと困りますね.まぁ普通そんなことはないでしょうか.)

BSP側のcall_initfunc("dbsp")(delayed BSP?)はAP側の初期化コード内にいくつかある同期をとるコード(sync_all_processors())のために存在します.
なお,いくつか同期を取る主な理由は実装都合(デバッグがしやすい等)みたいです.

参考文献

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away