EFI stubなLinux kernelのヘッダ部分を見てみる
EFI環境においてLinux kernelの起動方法には ブートローダを用いる方法とEFI stubの2通りがある. EFI stubではbzImageに対してEFI Application相当のヘッダを付加することで EFIから直接kernelを起動する. ここでは,EFI stubなLinux kernelのヘッダが実際に見ることで どのように直接起動できるようにしているかを見ていく. 実際の記事を書いたのが相当前なので,ここではLinux kernel 4.5を対象としている.
最初にCONFIG_EFI_STUB=y
としてビルドしたときのbzImageのhexdumpの一部を以下に示す.
00000000: 4d5a ea07 00c0 078c c88e d88e c08e d031 MZ.............1
00000010: e4fb fcbe 4000 ac20 c074 09b4 0ebb 0700 ....@.. .t......
00000020: cd10 ebf2 31c0 cd16 cd19 eaf0 ff00 f000 ....1...........
00000030: 0000 0000 0000 0000 0000 0000 8200 0000 ................
00000040: 5573 6520 6120 626f 6f74 206c 6f61 6465 Use a boot loade
00000050: 722e 0d0a 0a52 656d 6f76 6520 6469 736b r....Remove disk
00000060: 2061 6e64 2070 7265 7373 2061 6e79 206b and press any k
00000070: 6579 2074 6f20 7265 626f 6f74 2e2e 2e0d ey to reboot....
00000080: 0a00 5045 0000 6486 0400 0000 0000 0000 ..PE..d.........
00000090: 0000 0100 0000 a000 0602 0b02 0214 4020 ..............@
000000a0: 5f00 0000 0000 c0ad e800 1040 0000 0002 _..........@....
000000b0: 0000 0000 0000 0000 0000 2000 0000 2000 .......... ... .
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 00d0 4701 0002 0000 0000 0000 0a00 ....G...........
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0000 0000 0000 0600 0000 0000 0000 0000 ................
00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000130: 0000 0000 0000 0000 0000 2e73 6574 7570 ...........setup
00000140: 0000 e03b 0000 0002 0000 e03b 0000 0002 ...;.......;....
00000150: 0000 0000 0000 0000 0000 0000 0000 2000 .............. .
00000160: 5060 2e72 656c 6f63 0000 2000 0000 e03d P`.reloc.. ....=
EFI ApplicationはPEフォーマットを採用している. PEフォーマットではDOSヘッダが最初にあり,次にPEヘッダが置かれている. それぞれのシグネチャを手がかりに先のhexdumpの結果を見ていく. 最初に0x00に0x4d5a(=“MZ”)があり,DOSヘッダであることを示している. 次に0x3cを見ると,0x0082があり,これがPEヘッダの先頭位置を表している. 0x0082には0x5045(=“PE”)があり.確かにこれがPEヘッダの始まりであることがわかる. 以下、PEヘッダの定義が続くが、ここでは深追いしない. 以上のように,確かにbzImageがEFI Applicationの形式となっていることがわかる.
ところで.bzImageはgccなどの通常のツールチェーンを用いてビルドされるため,
直接PEフォーマットを出力することはできない.
次にどのようにしてPEフォーマットを出力するのかを見ていく.
最初にarch/x86/boot/Makefile
の一部を示す.
sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [ABCDGRSTVW] \(startup_32\|startup_64\|efi32_stub_entry\|efi64_stub_entry\|efi_pe_entry\|input_data\|_end\|z_.*\)$$/\#define ZO_\2 0x\1/p'
quiet_cmd_zoffset = ZOFFSET $@
cmd_zoffset = $(NM) $< | sed -n $(sed-zoffset) > $@
targets += zoffset.h
$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
$(call if_changed,zoffset)
ここではsedを用いてarch/x86/boot/compressed/vmlinux
のnmの出力結果から
エントリポイントとなるシンボルのアドレスをzoffset.hに出力していることがわかる.
次に出力されたzoffset.hの一部を示す.
#define ZO__end 0x0000000000609000
#define ZO_efi64_stub_entry 0x0000000000000390
#define ZO_efi_pe_entry 0x0000000000000210
#define ZO_input_data 0x00000000000003b4
#define ZO_startup_32 0x0000000000000000
#define ZO_startup_64 0x0000000000000200
#define ZO_z_extract_offset 0x0000000000e74000
#define ZO_z_extract_offset_negative 0xffffffffff18c000
#define ZO_z_input_len 0x00000000005e4c23
#define ZO_z_output_len 0x00000000014468b8
#define ZO_z_run_size 0x0000000001553000
ここで特に重要なのはZO_efi_pe_entry
である.これは後で利用される.
次にsrc/arch/x86/boot/tools/build.c
(以下,build.c
)を利用する.
これはbzImageを作成するためのツールである.
build.c
は以下の4つの引数をとる.
- setup.bin
- vmlinux.bin
- zoffset.h
- 出力先
build.c
の処理を実際にみていく.
ここではPEヘッダのエントリポイントの設定に注目する.
build.c
の一部を示す.
static void update_pecoff_text(unsigned int text_start, unsigned int file_sz)
{
unsigned int pe_header;
unsigned int text_sz = file_sz - text_start;
pe_header = get_unaligned_le32(&buf[0x3c]);
/*
* Size of code: Subtract the size of the first sector (512 bytes)
* which includes the header.
*/
put_unaligned_le32(file_sz - 512, &buf[pe_header + 0x1c]);
/*
* Address of entry point for PE/COFF executable
*/
put_unaligned_le32(text_start + efi_pe_entry, &buf[pe_header + 0x28]);
update_pecoff_section_header(".text", text_start, text_sz);
}
bufはsetup.binをベースにbzImageの最初の512byteとなる部分である.
pe_headerはbufの0x3cにある32bitを取得する.先のバイナリでは0x0082となる.
次にPEヘッダのSize of code
をbufに書き込む.
次にPEヘッダのAddress Of Entry Point
をbufに書き込む.
text_startはrelocを考慮したときのsetup.binのサイズであり,
efi_pe_entryは先のzoffset.hから得たZO_efi_pe_entry
の値,0x0210となる.
書き込む位置は[pe_header + 0x28] = 0x82 + 0x28 = 0xaa
となる.
最初に示したhexdumpの0xaaをみると、0x4010が書き込まれている.
ここから,EFIがbzImageを0x4010から実行されることがわかる.
まとめると,EFIはEFI Applicationをロードすると,
PEヘッダに記載されているAddress Of Entry Point
で指定されているアドレスから実行する.
EFI stubなLinux kernelのbzImageの場合,efi_pe_entryから実行されるため,
efi_pe_entryのアドレスがAddress Of Entry Point
に入っている必要がある.
build.c
はこのアドレスをsetup.bin,vmlinux.binから算出し,埋め込んでいることがわかった.
最後にbzImageの0x4010以降とefi_pe_entryとを比較してみる.
bzImageのhexdumpの結果を示す.
00004010: 4889 0d81 e15e 0048 8915 82e1 5e00 488d H....^.H....^.H.
00004020: 0573 e15e 0048 8905 64e1 5e00 e800 0000 .s.^.H..d.^.....
00004030: 005d 4881 ed31 0200 0048 012d b0e1 5e00 .]H..1...H.-..^.
00004040: 4889 c7e8 a8a1 5e00 4883 f800 743f 4889 H.....^.H...t?H.
00004050: c648 8d05 a8fd ffff 8986 1402 0000 eb18 .H..............
arch/x86/boot/compressed/head64.o
のobjdumpの結果を示す.
0000000000000210 <efi_pe_entry>:
210: 48 89 0d 00 00 00 00 mov %rcx,0x0(%rip) # 217 <efi_pe_entry+0x7>
217: 48 89 15 00 00 00 00 mov %rdx,0x0(%rip) # 21e <efi_pe_entry+0xe>
21e: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 225 <efi_pe_entry+0x15>
225: 48 89 05 00 00 00 00 mov %rax,0x0(%rip) # 22c <efi_pe_entry+0x1c>
22c: e8 00 00 00 00 callq 231 <efi_pe_entry+0x21>
231: 5d pop %rbp
232: 48 81 ed 00 00 00 00 sub $0x0,%rbp
239: 48 01 2d 00 00 00 00 add %rbp,0x0(%rip) # 240 <efi_pe_entry+0x30>
240: 48 89 c7 mov %rax,%rdi
243: e8 00 00 00 00 callq 248 <efi_pe_entry+0x38>
248: 48 83 f8 00 cmp $0x0,%rax
24c: 74 3f je 28d <fail>
24e: 48 89 c6 mov %rax,%rsi
251: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 258 <efi_pe_entry+0x48>
258: 89 86 14 02 00 00 mov %eax,0x214(%rsi)
25e: eb 18 jmp 278 <handover_entry+0x18>
おまけ
参考までにEDK2でビルドしたHello WorldをUEFI Shell上で出力するEFI Applicationのhexdumpの結果を以下に示す.
00000000: 4d5a 0000 0000 0000 0000 0000 0000 0000 MZ..............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 8000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 5045 0000 6486 0300 0000 0000 0000 0000 PE..d...........
ここからわかるように,EFIではDOSヘッダについてはSignatureと0x3c位置の部分しか見ないようである. そのため,先のbzImageではこの他の領域をLegacy Bootのために有効活用しているようである.