UEFI時代のブート ローダ @syuu1228
UEFI時代のブートローダ@syuu1228
自己紹介
• Software Engineer at Cloudius Systems (OSv)
• FreeBSD developer (bhyve, network stack..)
UEFIのおさらい
BIOSブート• MBRからブートセクタをロード
• プログラム領域が足りないのでMBRの次のセクタなどを利用して更に大きなブートローダをロード(多段ブート)
• /bootファイルシステムをパースしてLinuxカーネルを見つけてロード&実行
←ディスクに1個
UEFIブート• ブートセクタなどなかった
• EFIパーティーション(FATファイルシステム)にブートローダ(UEFIイメージ)を配置
• NVRAMに設定がある→設定されたパスからブートローダをロード&実行
• NVRAMに設定がない→デフォルトパスからブートローダをロード&実行
• プロテクテッドモード、UEFI API
←ディスクにn個(いくらでも)
BIOSのブートイメージ• ブートセクタなどのとても狭い領域に固定的に配置
• リアルモード
• アドレッシング出来るメモリに大きな制約
• アセンブリ
• 古くさいBIOSコールによる限られたAPI
UEFIイメージ• PEバイナリ
(Windowsアプリと似たようなヘッダ)
• 32/64bitプロテクテッドモード(※但し両対応ファームはない)
• サイズ制限、メモリ容量制限なし
• 全てC言語で記述可能
• 豊富なAPI
参考:Runtime Services• ExitBootServices()後もUEFIがOSに対して提供するサービス
• 最低限の機能のみ
• Time (GetTime, SetTime...)
• Virtual Memory (SetirtualAddressMap...)
• Variable Services (GetVariable...)
• Miscellaneous Services(ResetSystem...)
参考:Boot Services• ExitBootServices()までUEFI Applicationに提供するサービス
• Task Priority Services (RaiseTPL...)
• Memory Services (AllocatePages...)
• Event & Timer Services (CreateEvent, SetTimer...)
• Protocol Handler Services (HandleProtocol...)
• Image Services (LoadImage, StartImage...)
• Miscellaneous Services (Stall, CopyMem...)
• Open and Close Protocol Services (OpenProtocol...)
• Library Services (LocateProtocol...)
• 32bit CRC Services (CalculateCrc32...)
参考:Procotols
• ネットワークプロトコルスタックのことではない
• UEFI上で提供される様々なサービスの事
• UEFI Driverを実装しUEFIへロードする事により、自作のProtocolを提供する事も可能
参考:定義されているProtocol
• EFI Loaded Image
• Device Path Protocol
• UEFI Driver Model
• Console Support
• Media Access
• PCI Bus Support
• SCSI Driver Models and Bus Support
• iSCSI Boot
• USB Support
• Debugger Support
• Compression Algorithm
• ACPI Protocols
• TCP/IP, IPSec, FTP
• ARP & DHCP
• UDP & MTFTP
• etc...
UEFIからのブート手順(デフォルト)
BootManagerの設定がデフォルト値の場合
1. UEFIがHDDを検出、GPTをロード
2. EFI System Partitionを検索
3. \EFI\BOOT\BOOTX64.EFIをロード(32bit UEFIならBOOTX86.EFI)
UEFIからのブート手順(カスタム)
以下の様な値をUEFI NVRAM variableに設定(efibootmgrなど)
• Boot####:ロードするUEFI applicationのPATH・又はディスクのデバイスPATH
• BootOrder:Boot####の試行順序(配列で指定)
• BootNext:次回起動時にロードするBoot####
(BootOrderより優先、一度起動すると削除)
• Timeout:設定秒数だけBoot Menuを表示(自動起動を遅延)
設定例
• efibootmgrで編集
• /sys/firmware/efi/vars, /sys/firmware/efi/efivars経由でUEFI NVRAM Variableへアクセス
/sys/firmware/efi/efivarssyuu@ubuntu:~$ ls /sys/firmware/efi/efivarsBoot0000-8be4df61-93ca-11d2-aa0d-00e098032b8cBoot0001-8be4df61-93ca-11d2-aa0d-00e098032b8cBoot0002-8be4df61-93ca-11d2-aa0d-00e098032b8cBoot0003-8be4df61-93ca-11d2-aa0d-00e098032b8cBoot0004-8be4df61-93ca-11d2-aa0d-00e098032b8cBootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c...
$ sudo cat /sys/firmware/efi/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c@EFI VMware Virtual SCSI Hard Drive (0.0) ?
Boot Manager
ブート項目の選択画面。OSは自分のブートローダをここに登録する
UEFI Shell
• 運が良ければROMに乗ってる無ければUSBからロード
さまざまなUEFIブート手順
UEFIでのLinuxブート方法(1)
• shim→grub2→Linux bzImage
• shim→grub2→Linux EFI Stub
• grub2→Linux bzImage
• grub2→Linux EFI Stub
• gummiboot→Linux EFI Stub
• Linux EFI Stub
←SecureBoot
UEFIでのLinuxブート方法(2)
• LinuxカーネルにEFI Stubを用意すれば直接Boot
Managerからロード出来る
• UEFIイメージから更に別のUEFIイメージをロード&実行できる
• UEFI APIがサポートしていないバイナリフォーマットよりもサポートが容易サポートすることによるデメリットも少ない
なるほど~?
非対応フォーマットの ロード&実行
• ヘッダのパース• メモリへの展開• CPUレジスタの初期化、辻褄合わせ
• エントリポイントへのジャンプ_人人人人人人_ > 結構面倒 < ‾Y^Y^Y^Y^Y‾
UEFIイメージのロード&実行
• UEFIイメージファイルをロードするAPI
にファイル名渡して終わりでは?
_人人人人人人_ > 簡単そう < ‾Y^Y^Y^Y^Y‾
ところで
• mrubyはUEFIで動く(mruby on EFI shell)
ということは
• mrubyにローダAPIを足せばmrubyスクリプトでブートローダを簡単に実装出来るのでは?
UEFI APIサポートon mruby
class BlockIoProtocol < UEFI::Protocol GUID = UEFI::Guid.new("964e5b21-6459-11d2-8e39-00a0c969723b") define_variable(:revision, :u64) define_variable(:media, :p) define_function(:reset, :e, [:p, :b]) define_function(:read_blocks, :e, [:p, :u32, :u64, :u64, :p]) end class Media < UEFI::Protocol define_variable(:media_id, :uint32) end handles = UEFI::BootService.locate_handle_buffer(BlockIoProtocol::GUID) handle = handles.first puts "handle: #{handle}" ptr = UEFI::BootService.handle_protocol(handle, BlockIoProtocol::GUID) bp = BlockIoProtocol.new(ptr) media = Media.new(bp.media) puts "media_id: #{media.media_id}"
これを使ってmrubyでローダを書けばいいんじゃね?
Cで書く→mrubyに起こす
• Cで書いてみたhttps://gist.github.com/syuu1228/d7ce6b949cbeec887ea0
どのAPIをmrubyでどう置き換えれば良いのか分からない
…(゜Д゜)
方針転換• 足りない機能は皆C拡張にしてしまえ
• さっきのコードをコピペしてmrubyのクラス化 →実行
• 何故かエラー…
• (゜Д゜)
む?
良くみたらsystem(3)あったわ
• EDK2にはlibc + BSD socketのテスト実装が載ってる(非公式あつかい)
• どうせmrubyは初めからこれをリンクしている
• system(3)もある
• 文字列組み立てて渡せば一行のCコードでバイナリ実行できんじゃん…
• 何を苦労していたんだろう
が…• mruby on EFI shellはmrubyの標準ビルドシステムを迂回してEDKのビルドシステムでビルドされてる
• mrubyのビルドシステムを使わないとmrbgemsを追加出来ない
• 殆どAPIが無い&簡単に足せない…
応急処置的に足す
• さっきのsystem(3)を呼び出すShell.exec()
• キーボード入力を受け付けるShell.gets()
• Dirクラス
完成!
• https://github.com/syuu1228/mruby_on_efi_shell/blob/862b7d95e399dc23744c589220e59d6e6f0adff3/example/bootloader.rb
デモ
TODO• mrbgems問題をどうにかするべき
• HTTPクライアントをポーティングしてカーネルもスクリプトも外から落としてきて実行させたい
• ファイルシステムドライバをインストールしてext[2-4]の/bootからカーネルをロードしたい
• ブートしたらツイートしたい
• もうちょっと整備すると結構便利になると思う
URL
• https://github.com/syuu1228/mruby_on_efi_shell/tree/devel