Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

UEFIによるELFバイナリの起動

Write your own bootloader that handles ELF binaries using UEFI.

  • Be the first to comment

UEFIによるELFバイナリの起動

  1. 1. UEFIによる ELFバイナリの起動 @uchan_nos 2017年9月18日,第8回 自作OSもくもく会
  2. 2. このスライドの目標 ELFバイナリ(自作OS)を • Grubなどの既存ブートローダの力を借りず UEFIでブートさせてみたい人が • 実際のブートローダのコードを読めるようになる こと.
  3. 3. ELF : Executable and Linking Format • a.out や COFF とかの仲間 • 現在の Unix, Linux での標準 • オブジェクトファイル,実行可能ファイルどちらもOK • ツールチェーンで良くサポートされている • a.out や COFF に比べ柔軟 • a.out には text, bss, data セクションしかない. C++ のコンストラクタや例外テーブルとかサポートできない. • COFF は a.out の拡張.でも,いろいろ制限がある. • ELF は柔軟で,新しい言語とかのサポートがしやすい
  4. 4. ELF ファイルフォーマット • ELFヘッダ、SHT 、PHT 、セクション群からなる • SHT:セクション・ヘッダ・テーブル • PHT:プログラム・ヘッダ・テーブル • セクション:.text, .data, .bss, .shstrtab など • 詳しくは 『リンカ・ローダ実践開発テクニック』坂井弘亮, 2010 ELFヘッダ PHT SHT セクション
  5. 5. ELF64ヘッダフォーマット off field 意味 0x00 e_ident 先頭4バイトは 0x7f, ‘E’, ‘L’, ‘F’ 0x10 e_type ファイルタイプ ET_EXEC, ET_REL など 0x12 e_machine EM_386 など 0x14 e_version ファイルバージョン 0x18 e_entry エントリポイントのアドレス 0x20 e_phoff プログラムヘッダテーブルのファイル位置 0x28 e_shoff セクションヘッダテーブルのファイル位置 0x30 e_flags 未使用 0x34 e_ehsize ELF ヘッダサイズ 0x36 … 0x3e e_shstrndx セクション名格納用セクションの番号 ELFヘッダ PHT SHT セクション
  6. 6. 基本となるアイデア • ELFファイルを適当なアドレス (ADDR_FOO)に配置 • 初期化 • .bssを0クリアしたり • グローバル変数のコンストラクタを呼 び出したり • ADDR_FOO+e_entryにジャンプ メモリ ELFファイル ADDR_FOO +e_entry .text
  7. 7. 問題:空きメモリはどこにある? • マシンごとにいろいろ違う • アーキテクチャ(x86,ARM) • 搭載メモリ量 • UEFIファームウェアの実装・バージョン • 当然,メモリマップは異なる • メモリマップ:メモリのどこに,何があるか • UEFI Memory Map メモ http://uchan.hateblo.jp/entry/2017/07/18/231528 • お行儀よくするためには • メモリマップを取得して • ELFファイルが乗る大きさの空き領域を探す
  8. 8. メモリマップ実例 使用用途 開始アドレス ページ数 0 EfiBootServicesCode 00000000 1 1 EfiConventionalMemory 00001000 9F 2 EfiConventionalMemory 00100000 700 3 EfiACPIMemoryNVS 00800000 8 4 EfiConventionalMemory 00808000 8 5 EfiACPIMemoryNVS 00810000 8 6 EfiConventionalMemory 00818000 8 7 EfiACPIMemoryNVS 00820000 E0 8 EfiBootServicesData 00900000 A00 … 太字:ExitBootServices()の呼出し後に自由に使えるメモリ領域
  9. 9. 実行可能ファイルとアドレス • 一般に,オブジェクトファイルをリンクすると,アドレスが固 定化される • というより,アドレスを固定化する作業=リンク • しかし,空き領域のアドレスは固定でない • →困った!
  10. 10. 2つの解決策 • 常に空いてる固定領域があると仮定する • 手持ちのマシンで確認した感じでは, 0x00100000(1MiB)から数MBは空いてるっぽい • 位置非依存の実行可能ファイルを生成する • Position Independent Executable • どこに配置しても動く • clang -fPIE -wl,-pie
  11. 11. 解決策:空き領域を仮定 • 手持ちのマシンで確認した感じでは, 0x00100000(1MiB)から数MBは空いてるっぽい • どんなマシンでも空いているかは不明 • 0x00100000に配置する設定でリンク • UEFIが提供する,指定アドレスにメモリを確保するAPIを使い, 0x00100000を先頭とするメモリ領域を割り当てる • 割り当てられなかったらあきらめる
  12. 12. 解決策:位置非依存実行可能ファイル • PIEでコンパイル&リンクすると,どこに配置しても動くよう に,相対ジャンプとかを使った機械語になる • どうしても無理なものもあり,リロケーションが必須 • .ctors セクション • .ctors には,初期化関数のアドレスが格納されている • PIEモードではリンク時にアドレスは決まらない • データのアドレス • 文字列リテラルのアドレス,グローバル変数のアドレスなど • 関数じゃないので,相対ジャンプではどうしようもできない
  13. 13. .ctorsについて • ctors = Contructors の略 • dtors = Destructors • コンストラクタとは,変数の初期化用関数のこと • 変数を「組み立てる(construct)」もの • std::vector<int> numbers(128); • こう書くと,コンストラクタが引数128で呼ばれ, 要素数128の整数配列が生成される. • グローバル変数のコンストラクタは メイン関数を実行する前に呼ばないといけない • グローバル変数の初期値をメイン関数実行前に設定するのと同じ理由.
  14. 14. UEFIブートローダー
  15. 15. UEFIとは • Unified Extensible Firmware Interface • Unified:各メーカーで統一された規格 • Extensible:あとあと拡張しやすい規格 • Firmware Interface:ファームウェアとOSのインターフェース • BIOSを置き換えるファームウェアインターフェースの規格 • IntelとHPが開発したものが元となっている. • インターフェースなので,プロセッサ非依存. • 実装はいろいろ.「OVMF」はオープンソース実装 • 今のPCは既にUEFIに置き換わっている • BIOSエミュレーションがある機種もある
  16. 16. BIOS時代のブートローダー • 512バイトのブートレコード • 16ビット実アドレスモード&アセンブラで頑張る • 保護モード,IA32eモードの遷移は自力で. • ※IA32eモード:64ビットモード • ファイルシステムの解析も自力 • USBメモリが使えるかは機種依存
  17. 17. UEFI時代のブートローダー • サイズ制限はない • 最初からIA32eモードで起動する • UEFIが64ビットUEFIの場合. • 最初からC言語で書ける! • FATファイルシステムは標準装備 • USBメモリは標準サポート OVMF搭載 MinnowBoard Turbot
  18. 18. UEFIアプリの自動起動 • UEFIアプリ • UEFIの作法にのっとって作ったアプリ • 中身はPEバイナリ • ブートローダーもUEFIアプリとして作る • 自動起動 • USBメモリをFATでフォーマット • 固定パスにUEFIアプリを配置 • /EFI/BOOT/BOOTX64.EFI
  19. 19. ブートローダーのコード解説 概要 • 参考文献:EDK II で UEFI アプリケーションを作る • http://osdev-jp.readthedocs.io/ja/latest/2017/create-uefi-app-with-edk2.html • ソース:https://github.com/uchan-nos/edk2/tree/bootloader/MyBootLoader • ファイル構成 • MyBootLoader.dsc:パッケージ記述ファイル • MyBootLoader.dec:パッケージ宣言ファイル • Loader.inf:モジュール定義ファイル • Loader.c:メイン関数を含むソースコード • *.c:その他ソースコード
  20. 20. ブートローダーのコード解説 処理の流れ(本質のところだけ) • グラフィックモードを取得(後でカーネルに渡すため) • カーネルファイルを開く • ファイルの大きさだけメモリを確保 • ファイルを読み込む • グローバル変数のコンストラクタを呼ぶ • メモリマップを得る • ブートサービスを抜ける • エントリポイントにジャンプ
  21. 21. グラフィックモードを取得 EFI_STATUS EFIAPI UefiMain(…) { // コンソールを最大サイズにする gST->ConOut->SetMode(gST->ConOut, FindLargestConMode()); // グラフィックモードを取得 struct GraphicMode GraphicMode; GetGraphicMode(ImageHandle, &GraphicMode); • 沢山表示できるように,コンソールサイズを最大にする. • カーネルに渡すためのグラフィックモードを得る. (画面サイズやピクセルフォーマットなど)
  22. 22. カーネルファイルを開く // ルートディレクトリを開く EFI_FILE_PROTOCOL *RootDir = NULL; OpenFileProtocolForThisAppRootDir(ImageHandle, &RootDir); // カーネルファイルを開く UINTN KernelFileSize; EFI_FILE_PROTOCOL *KernelFile; OpenFileForRead(RootDir, L"kernel.elf", &KernelFile, &KernelFileSize); • 起動パーティションのルートディレクトリを開く. • ルートディレクトリ直下の kernel.elf を開く. • ファイルサイズが KernelFileSize に書かれる.
  23. 23. ファイルの大きさだけメモリを確保 // 0x100000からメモリを確保 EFI_PHYSICAL_ADDRESS KernelFileAddr = 0x00100000lu; gBS->AllocatePages( AllocateAddress, EfiLoaderData, (KernelFileSize + 4095) / 4096, &KernelFileAddr); • 0x100000を先頭としたメモリ領域を確保. • 確保するメモリタイプはEfiLoaderData • (KernelFileSize + 4095) / 4096 でページ数を計算.
  24. 24. ファイルを読み込む KernelFile->Read(KernelFile, &KernelFileSize, (VOID*)KernelFileAddr); // ELFのマジックナンバを確認 Elf64_Ehdr *Ehdr = (Elf64_Ehdr*)KernelFileAddr; if (AsciiStrnCmp((CHAR8*)Ehdr->e_ident, "x7f" "ELF", 4) != 0) { Print(L"Kernel file is not elf.n"); return EFI_LOAD_ERROR; } • 確保したメモリ領域にファイル全体を読み込む • マジックナンバでEFLファイルであることを確認
  25. 25. グローバル変数のコンストラクタを呼ぶ typedef void (CtorType)(void); Elf64_Shdr *CtorsSection = Elf64_FindSection(Ehdr, ".ctors"); for (UINT64 *Ctor = (UINT64*)CtorsSection->sh_addr; Ctor < (UINT64*)(CtorsSection->sh_addr + CtorsSection->sh_size); ++Ctor) { CtorType *F = (CtorType*)*Ctor; F(); } • .ctorsセクションの内容は,64ビット整数の配列 • 配列の要素は初期化関数のアドレス
  26. 26. 補足:ELFのセクションヘッダ ELFヘッダ PHT SHT セクション .ctors sh_addr sh_size ctor0のアドレス ctor1のアドレス ctorNのアドレス セクションの 先頭アドレス セクションの 大きさ(バイト)
  27. 27. メモリマップを得る // メモリマップを書き込むためのメモリを確保 struct MemoryMap MemoryMap; AllocateMemoryMap(&MemoryMap, 4096); // メモリマップを取得 GetMemoryMap(&MemoryMap); • メモリマップ用には4KiBもあれば十分. • メモリマップの1行につき40バイト程度必要. • 余程のことがなければ100行を超えないだろう…
  28. 28. ブートサービスを抜ける Status = gBS->ExitBootServices(ImageHandle, MemoryMap.MapKey); if (EFI_ERROR(Status)) { Status = GetMemoryMap(&MemoryMap); if (EFI_ERROR(Status)) { return Status; } Status = gBS->ExitBootServices(ImageHandle, MemoryMap.MapKey); if (EFI_ERROR(Status)) { return Status; } } • 前回のメモリマップの取得からExitBootServicesの呼び出しの 間にメモリマップが変わると,ExitBootServicesが失敗する • 失敗したら,再度メモリマップを取得すればOK
  29. 29. エントリポイントにジャンプ // カーネルパラメータを準備 struct BootParam BootParam; … BootParam.graphic_mode = &GraphicMode; // エントリポイントにジャンプ EntryPoint(&BootParam); • カーネルに渡す引数を準備して • エントリポイントにジャンプ

    Be the first to comment

    Login to see the comments

  • forestsource

    Sep. 18, 2017
  • acerola243

    Sep. 26, 2018
  • noriyotcp

    Dec. 14, 2018
  • TomoyaONAI

    Jan. 5, 2020
  • DaichiTsuzuki

    Mar. 1, 2021

Write your own bootloader that handles ELF binaries using UEFI.

Views

Total views

4,044

On Slideshare

0

From embeds

0

Number of embeds

698

Actions

Downloads

31

Shares

0

Comments

0

Likes

5

×