カーネルVM関西 7回目 発表資料

F28abade40c3fa0565f578f7dbae6560?s=47 orumin
December 06, 2014

カーネルVM関西 7回目 発表資料

忙しい(言い訳)という事でした.カーネルVM関西 7回目の発表資料です. 発表で口頭だった部分をちゃんと書きました.

F28abade40c3fa0565f578f7dbae6560?s=128

orumin

December 06, 2014
Tweet

Transcript

  1. 忙しくて時間がなくなったので 10 分でブートローダーをつくっ た話で茶濁す

  2. • orumin (@kotatsu_mi) • Google Summer of Code 2014 で

    OSv に Ruby を移植してました. • 以前 UEFI 遊びしてた
  3. • UEFI! • BIOS もう捨てよう • GRUB も要らない (UEFI のブートセレクタで良

    い )
  4. • UEFI とは ? • UEFI Advent Calender 2014 参照

    ! • http://qiita.com/advent-calendar/2014/uefi • 質問でも BIOS なブートローダーでも u-boot で も良いのでそれっぽいネタ大募集中
  5. • 本題,ブートローダー

  6. • UEFI の基本スペック • C 言語な SDK , EDK(NTDDK 風味

    ) • FAT32 なシステムパーティションを高レイヤに 読み書きできる • stdio.h もつかえるぜ !
  7. • でも今回は EDK つかいません

  8. • 表題通り時間がなかったので

  9. • 表題通り時間がなかったので • gnu-efi でお茶を濁す

  10. • gnu-efi: • 名前が GNU なのに License は BSDL •

    なぜ ???
  11. • Binutils とこれをビルドする必要…… • 今はなくなってる (?) • apt-get install gnu-efi

    • pacman -S gnu-efi-libs
  12. • #include<efi.h> • #include<efilib.h> • リンカオプションに -lefi -lgnuefi • あとはコード書くだけ

    !
  13. • https://github.com/orumin/SimpleMyLoader

  14. Makefile • ARCH = x86_64 • EFIROOT = /usr •

    HDDRROOT = $(EFIROOT)/include/efi • INCLUDES = -I. -I$(HDDRROOT) -I$(HDDRROOT)/ $(ARCH) -I$(HDDRROOT)/protocol • EFIROOT は configure で /usr/local とかにインスト ール先指定してたらそこに変更
  15. Makefile • CFLAGS = -O2 -fPIC -Wall -fshort-wchar -fno-strict-aliasing -fno-merge-constants

    -mno- red-zone • ifeq ($(ARCH),x86_64) • CFLAGS += -DEFI_FUNCTION_WRAPPER • endif • • CPPFLAGS = -DCONFIG_$(ARCH)
  16. Makefile • CFLAGS は -fPIC と -fshort-wchar ,あと x86_64 ならば

    -DEFI_FUNCTION_WRAPPER を忘れなけ ればあとは好きなようにして大丈夫 • CPP にも FLAG を設定する必要アリ
  17. Makefile • CRTOBJS= $(EFIROOT)/lib/crt0-efi-$(ARCH).o • FORMAT = efi-app-$(ARCH) • INSTALL

    = install • LDFLAGS = -nostdlib • LDSCRIPT = $(EFIROOT)/lib/elf_$(ARCH)_efi.lds • LDFLAGS += -T $(LDSCRIPT) -shared -Bsymbolic -L$(EFIROOT)/lib $(CRTOBJS) • LOADLIBS = -lefi -lgnuefi $(shell $(CC) -print- libgcc-file-name)
  18. Makefile • リンカスクリプト,スタートアップルーチン, 共に gnu-efi のものを使うようにする • -nostdlib -shared •

    先にも書いたように -lefi と -lgnuefi で gnu-efi をリンク
  19. Makefile • prefix = • CC = $(prefix)gcc • AS

    = $(prefix)as • LD = $(prefix)ld • AR = $(prefix)ar • RANLIB = $(prefix)ranlib • OBJCOPY = $(prefix)objcopy
  20. Makefile • もし binutils や gcc を UEFI アプリケーションの ためにビルドしてどこか特定のディレクトリに

    入れたなら prefix を指定.
  21. Makefile • %.efi: %.so • $(OBJCOPY) -j .text -j .sdata

    -j .data -j .dynamic -j .dynsym -j .rel \ • -j .rela -j .reloc --target=$(FORMAT) $*.so $@ • • %.so: %.o • $(LD) $(LDFLAGS) $^ -o $@ $(LOADLIBS) • • %.o: %.c • $(CC) $(INCLUDES) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
  22. Makefile • C のソースをオブジェクトファイルにするとこ ろ,オブジェクトファイルを so なリロケータ ブルバイナリにするのには先に指定したフラグ を使う. •

    とりたてて変わったことはしない.
  23. Makefile • 最後 *.efi を作る時はちょっと特殊 • so なファイルから objcopy で,

    ELF のセクショ ンの内必要な部分だけを取り出す
  24. ソースコード ( 前知識 ) • efi.h , gnuefi.h をインクルード •

    エントリポイントは efi_main(EFI_HANDLE, EFI_SYSTEM_TABLE) • UEFI は Protocol という API を使う • 基本, OpenProtocol で Protocol の Handle を開いて,必 要な Protocol の関数ポインタ群を対応する Protocol 構造 体に詰めて,そこから Protocol の関数を呼ぶ •
  25. ソースコード ( 前知識 ) • 本来は,たとえば BS->OpenProtocol() みたいに Protocol から直接関数呼び出し

    • しかし Microsoft な ABI……
  26. ソースコード ( 前知識 ) • gnu-efi の場合 uefi_call_wrapper() で第一引数に UEFI

    の Protocol の関数ポインタ指定してつかう • なお,ソースコード中の BS は BootServices で, EFI_BOOT_SERVICES というブートローダーに 必要なプロトコルのハンドラ.これは OpenProtocol とかしなくても最初からグローバルの BS という変 数に詰めてある ( でないと OpenProtocol が呼べな い )
  27. ソースコード ( 前知識 ) • なお EDK での前準備については 過去発表を参照されたし •

    https://speakerdeck.com/orumin/driver
  28. ソースコード • efi_main の引数の ImageHandle , SystemTable • 前者は自分自身のハンドル,後者はデバイスへ のハンドルやパスを手繰るための親のハンドル

    や関数が入ってる
  29. ソースコード • uefi_call_wrapper(BS->OpenProtocol, 6, ImageHandle, &LoadedImageProtocol, &LoadedImageParent, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

    • まず自分自身のハンドルから,既に読み込まれ てるメモリイメージ ( 実行バイナリイメージ ) についての Protocol を取得
  30. ソースコード • Path = FileDevicePath(LoadedImageParent- >DeviceHandle, L"\\vmlinuz"); • 先ほど取得した Protocol

    ハンドルにあるデバイ スへのハンドルと, Linux カーネルへのパスを 引数に,カーネルのファイルへのデバイスパス を取得
  31. ソースコード • この時指定するパスは, EFI のシステムパーテ ィションのパス. • カーネルもそこに置かないといけない • もし普通に

    ext4 とかに置かれたものを読みた ければ, ext4 について EFI_SIMPLE_FILESYSTEM プロトコルを提供でき るように EFI Driver を書くしかない
  32. ソースコード • rEFInd のコードがファイルシステムドライバの 参考になります • https://github.com/falstaff84/rEFInd/tree/master /filesystems

  33. ソースコード • uefi_call_wrapper(BS->LoadImage, 6, FALSE, ImageHandle, Path, NULL, 0, &Image);

    • 取得したデバイスパスをつかって,新たにメモ リイメージ ( 実行バイナリイメージ ) をメモリ にロード
  34. ソースコード • uefi_call_wrapper(BS->StartImage, 3, Image, NULL, NULL); • そして読み込んだイメージを起動 !

    完了 !
  35. ソースコード • というわけにいかない. • Linux の起動に root のパーティションとか指定 するカーネルコマンドライン (

    起動オプショ ン ) を指定したい
  36. ソースコード • StartImage を実行する前に……

  37. ソースコード • uefi_call_wrapper(BS->OpenProtocol, 6, Image, &LoadedImageProtocol, &LoadedImage, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

    • また LoadedImage の Protocol • 今度は自分自身のイメージではなく先ほど読み込 んだカーネルのイメージについて Protocol を取得
  38. ソースコード • LoadedImage->LoadOptions = Options; • LoadedImage->LoadOptionsSize = (StrLen(LoadedImage->LoadOptions)+1) *

    sizeof(CHAR16); • この Protocol のメンバに起動オプションのため のフィールドがあるので指定するだけ
  39. ソースコード • LoadOptions に渡してる Options • CHAR16 *Options = L"root=/dev/sda2

    rootfstype=btrfs rw quiet splash"; • こんな感じで CHAR16 型の文字列を指定すれば 良い • CHAR16 型なので文字列リテラルのプレフィク スに L を付けておく ← 大事
  40. ソースコード • 今度こそブートローダーとしての仕事完了 ! • LoadOptions を指定してから, StartImage! • その後の

    2,3 行は後処理コード
  41. ブートローダー • initrd 対応サボってるのでファイルシステムのドラ イバが組み込みじゃなくてモジュールになっちゃっ てる ( よくあるディストリビューション標準の ) カ

    ーネルは起動時に root mount でカーネルパニックし ます. • ハードコードしてるカーネルのファイルパスと起動 オプションは環境に合わせると誰でも使えると思 う.
  42. ブートローダー • 自作 OS でブートローダー書く時点で満足とい う話はおおかった. • UEFI ではもはやそんな事はないよ !

    • 自作 OS のカーネルも生バイナリつくって EFI システムパーティションに配置するだけで良い • とってもラク !
  43. • ちなみにこの発表は先ほどコーディングしたブ ートローダーを用いて起動した ArchLinux で行 ないました. • CHAR16 の変数を作るのに L

    を付けわすれてハ マった以外はバグが出ず,実行は一発で成功 • なんて簡単なんだ
  44. • さいごに : • みんなもスライドはちゃんと用意しましょう