Arm64-UEFI 启动Linux内核-Image文件-PE格式
背景介绍:
参考:
http://www.wowotech.net/linux_kenrel/UEFI.html
1、UEFI是什么鬼?
BIOS实际上就是IBM PC兼容机(多么古老的一个词汇啊)主板上的固件(firmware),这些固件可以在系统启动过程中初始化硬件,self test,加载bootloader或者OS kernel,并且能为OS提供一些基础的服务。
Intel提出来EFI(Extensible Firmware Interface)来取代BIOS interface。2005年,Intel终止了EFI规范的开发,替代它的是Unified EFI Forum负责的UEFI
2、UEFI关ARM什么事?
如果ARM仅仅是将目光放在移动(嵌入式)市场,那么UEFI当然不关ARM什么事情。
但是,在推出ARMv8以及64 bit架构的的处理器之后,ARM的野心已经不满足在移动市场上称王了。
3、UEFI如何定义系统的启动过程?
在UEFI规范中定义了BOOT manager,它会根据保存在NVRAM中的参数来决定如何加载 EFI Application
EFI Application 是 PE(Portable Executable )格式的文件。
PE格式 是一种二进制可执行文件的格式(在linux世界中,我们多半熟悉的是ELF格式),由微软开发,广泛应用在Windows平台上。
uefi 的boot manager --> PE 格式的 bootloader (Uboot) --> linux kernel ? 这样不够直观。PE 格式的 uboot 只是转接信息。
因此,linux kernel image自身也可以包装成一个EFI application image,由boot manager直接加载,完成启动过程。
uefi 的boot manager --> PE 格式的 linux kernel .
PE格式-Linux内核Image
PE 格式
下面的图片是一个PE文件格式的示意图:
arm64 kernel image 生成PE格式
MS DOS头里面的内容,由 arch/arm64/kernel/head.S 填充。
head.S 内容
__HEAD _head: /* * DO NOT MODIFY. Image header expected by Linux boot-loaders. */ #ifdef CONFIG_EFI /* * This add instruction has no meaningful effect except that * its opcode forms the magic "MZ" signature required by UEFI. */ add x13, x18, #0x16 b primary_entry #else b primary_entry // branch to kernel start, magic .long 0 // reserved #endif .quad 0 // Image load offset from start of RAM, little-endian le64sym _kernel_size_le // Effective size of kernel image, little-endian le64sym _kernel_flags_le // Informative flags, little-endian .quad 0 // reserved .quad 0 // reserved .quad 0 // reserved .ascii ARM64_IMAGE_MAGIC // Magic number #ifdef CONFIG_EFI .long pe_header - _head // Offset to the PE header. pe_header: __EFI_PE_HEADER #else .long 0 // reserved #endif
其中 ARM64_IMAGE_MAGIC 是一个魔数,4 个字节, 其定义如下(代码位于arch/arm64/include/asm/image.h
#define ARM64_IMAGE_MAGIC "ARM\x64"
上面,从 _head 到 pe_header 之间有 7*8 bytes + 4 bytes + 4bytes
MS DOS 结构体
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // MZ标记 0x5a4d 2 byte WORD e_cblp; // 最后(部分)页中的字节数 2 byte WORD e_cp; // 文件中的全部和部分页数 2 byte WORD e_crlc; // 重定位表中的指针数 2 byte WORD e_cparhdr; // 头部尺寸以段落为单位 2 byte WORD e_minalloc; // 所需的最小附加段 2 byte WORD e_maxalloc; // 所需的最大附加段 2 byte WORD e_ss; // 初始的SS值(相对偏移量) 2 byte WORD e_sp; // 初始的SP值 2 byte WORD e_csum; // 补码校验值 2 byte WORD e_ip; // 初始的IP值 2 byte WORD e_cs; // 初始的SS值 2 byte WORD e_lfarlc; // 重定位表的字节偏移量 2 byte WORD e_ovno; // 覆盖号 2 byte WORD e_res[4]; // 保留字 8 bytes WORD e_oemid; // OEM标识符(相对m_oeminfo) 2 byte WORD e_oeminfo; // OEM信息 2 byte WORD e_res2[10]; // 保留字 10 * 2 bytes LONG e_lfanew; // NT头(PE标记)相对于文件的偏移地址 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
这一结构体有 4 + 4 + 2*8 + 2* 8 + 3*8 = 7*8 + 4 + 4 个字节
head.S 的 7*8 + 4 + 4 个字节 可以和 结构体的 7*8 + 4 + 4 个字节 一一匹配。 主要就是 e_lfanew 对应到 head.S 里面的
.long pe_header - _head
pe header 和 arch/arm64/kernel/efi-header.S 内容
head.S 中 pe_header 后面跟的 __EFI_PE_HEADER 是一个宏定义,在 arch/arm64/kernel/efi-header.S 中,里面的内容和 PE 文件格式一一对应
.macro __EFI_PE_HEADER .long PE_MAGIC coff_header: .short IMAGE_FILE_MACHINE_ARM64 // Machine .short section_count // NumberOfSections .long 0 // TimeDateStamp .long 0 // PointerToSymbolTable .long 0 // NumberOfSymbols .short section_table - optional_header // SizeOfOptionalHeader .short IMAGE_FILE_DEBUG_STRIPPED | \ IMAGE_FILE_EXECUTABLE_IMAGE | \ IMAGE_FILE_LINE_NUMS_STRIPPED // Characteristics optional_header: .short PE_OPT_MAGIC_PE32PLUS // PE32+ format .byte 0x02 // MajorLinkerVersion .byte 0x14 // MinorLinkerVersion .long __initdata_begin - efi_header_end // SizeOfCode .long __pecoff_data_size // SizeOfInitializedData .long 0 // SizeOfUninitializedData .long __efistub_efi_pe_entry - _head // AddressOfEntryPoint .long efi_header_end - _head // BaseOfCode extra_header_fields: .quad 0 // ImageBase .long SEGMENT_ALIGN // SectionAlignment
……