查看“UEFI”的源代码
←
UEFI
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
(U) EFI或(统一)可扩展固件接口是针对x86、x86-64、ARM和安腾平台的规范,用于定义操作系统和平台固件/BIOS之间的软件接口。 最初的EFI是在20世纪90年代中期由Intel开发的,用于为安腾平台开发固件/BIOS。 2005年,英特尔将该规范移交给一个名为“统一EFI论坛”的新工作组,该工作组由AMD、微软、苹果和英特尔等公司组成。 所有现代PC都附带UEFI固件,UEFI广泛受商业和开源操作系统支持。 为旧式操作系统提供了向后兼容性。 == UEFI基础知识 == ===Downloading UEFI images=== 如果您选择[[VirtualBox]]进行虚拟化,则已包含UEFI,无需手动下载映像。 您只需在VM的首选项中通过单击“设置”/“系统”/“启用EFI(仅限特殊操作系统)”复选框来启用它。 Otherwise for emulation and virtual machines, you'll need an OVMF.fd firmware image. It might be tricky to find, so here are some alternative download links too: * [https://github.com/tianocore/tianocore.github.io/wiki/OVMF TianoCore Download] (official link) * [https://github.com/BlankOn/ovmf-blobs OVMF-blobs] (unofficial precompiled 32 bit and 64 bit versions, easy to use) * [https://www.kraxel.org/repos/ RPM packages] (official TianoCore packages) * [https://packages.debian.org/sid/ovmf Debian packages] * [https://packages.gentoo.org/useflags/ovmf Gentoo packages] * [https://aur.archlinux.org/packages/ovmf-git/?comments=all Arch packages] Under Linux, you can also install these with your distro's package manager, for example: '''Debian / Ubuntu''' <source lang="bash"> # apt-get install ovmf </source> '''RedHat / CentOS''' <source lang="bash"> # yum install ovmf </source> '''MacOS''' Use the OVMF-blobs repo. '''Windows''' 使用OVMF blobs repo或下载RPM版本,然后使用“7-Zip文件管理器”提取OVMF。从下载的存档文件中删除fd文件。 ===UEFI vs. legacy BIOS=== 一个常见的误解是UEFI是BIOS的替代品。 事实上,传统主板和基于UEFI的主板都配有BIOS ROM,其中包含固件,在将某些第三方代码加载到内存并跳转到内存之前执行系统的初始开机配置。 传统BIOS固件和UEFI BIOS固件之间的区别在于它们在何处找到代码,它们在跳转到系统之前如何准备系统,以及它们为代码在运行时调用提供了哪些方便功能。 ====平台初始化==== 在传统系统上,BIOS执行所有常见的平台初始化(内存控制器配置、PCI总线配置和条形图映射、图形卡初始化等)。),但随后进入向后兼容的实模式环境。 引导加载程序必须启用A20门,配置GDT和IDT,切换到保护模式,对于x86-64 CPU,配置分页并切换到长模式。 UEFI固件执行相同的步骤,但也准备了一个具有平面分段的保护模式环境,并为x86-64 CPU准备了一个具有标识映射分页的长模式环境。 A20门也已启用。 此外,UEFI固件的平台初始化程序已标准化。这允许UEFI固件以供应商中立的方式进行扩展。 ====Boot mechanism==== 传统BIOS从引导设备的MBR将512字节的扁平二进制blob加载到物理地址7C00处的内存中,并跳转到该内存。 引导加载程序无法返回BIOS。 UEFI固件将任意大小的UEFI应用程序(可重新定位的PE可执行文件)从GPT分区引导设备上的FAT分区加载到运行时选择的某个地址。 然后调用该应用程序的主入口点。 应用程序可以将控制权返回到固件,固件将继续搜索其他引导设备或打开诊断菜单。 ====System discovery==== 旧式引导加载程序扫描内存中的[[EBDA]]、[[SMBIOS]]和[[ACPI]]表等结构。 它使用PIO与根[[PCI]]控制器通信并扫描PCI总线。 内存中可能存在冗余表(例如,SMBIOS中的[[MP|U Specification | MP]]表包含同样存在于ACPI[[DSDT]]和[[MADT]]中的信息),引导加载程序可以选择使用哪个。 当UEFI固件调用UEFI应用程序的入口点函数时,它会传递一个“系统表”结构,其中包含指向系统所有ACPI表、内存映射和其他与操作系统相关的信息的指针。 内存中可能不存在旧表(如MP表)。 ====Convenience functions==== 传统BIOS钩住各种中断,引导加载程序可以触发这些中断来访问磁盘和屏幕等系统资源。 除历史惯例外,这些中断没有标准化。 每个中断使用不同的寄存器传递约定。 UEFI固件在内存中建立了许多可调用函数,这些函数被分组到称为“协议”的集合中,并可通过系统表发现。 每个协议中每个函数的行为由规范定义。 UEFI应用程序可以定义自己的协议,并将其保存在内存中,供其他UEFI应用程序使用。 函数是通过许多C编译器支持的标准化、现代调用约定来调用的。 ====Development environment==== 可以在任何可以生成平面二进制映像的环境中开发遗留引导加载程序:NASM、GCC等。 UEFI应用程序可以用任何语言开发,这些语言可以编译并链接到[[PE]]可执行文件中,并支持用于访问UEFI固件在内存中建立的功能的调用约定。 实际上,这意味着三种开发环境之一:EDK2、GNU-EFI或POSIX-UEFI。 [[EDK2]]是一个大型、复杂但功能丰富的环境,有自己的构建系统。 它可以配置为使用GCC、LLVM、MinGW、微软Visual C++等。 作为交叉编译器。 它不仅可以用来编译UEFI应用程序,还可以用来编译UEFI固件以闪存到BIOS ROM。 [[GNU-EFI]]是一组库和头文件,用于使用系统的本机GCC编译UEFI应用程序(不适用于LLVM CLang)。它不能用于编译UEFI固件。 由于它只是几个库,UEFI应用程序可以与之链接,因此它比TianoCore更易于使用。 [[POSIX-UEFI]]与GNU-EFI非常相似,但它主要作为源代码分发,而不是作为二进制库分发,具有类似ANSI C的名称,并与GCC以及LLVM CLang一起使用。 它附带了一个为您设置编译器标志的Makefile。 ====模拟==== [[OVMF]],一种流行的开源UEFI固件,已经移植到QEMU(但不是Bochs)仿真机器上。 因为它实现了UEFI规范,所以其行为与实际机器上的商用UEFI固件非常相似。 (OVMF本身是由TianoCore建造的,它的源船也是由TianoCore建造的,但可以使用预建图像。) ===传统引导加载程序还是UEFI应用程序?=== 如果您的目标是UEFI不可用或不可靠的遗留系统,则应开发遗留引导加载程序。 这需要熟悉x86或x86-64 CPU的16位寻址和向后兼容性功能。 如果您的目标是现代系统,您应该开发UEFI应用程序。 许多UEFI固件可以配置为模拟传统BIOS,但这些模拟环境之间的差异甚至比真正的传统BIOS之间的差异更大。 尽管在熟悉UEFI开发环境、使用系统表和访问UEFI提供的协议(功能)方面有一个很小的学习曲线,但要想在真正的机器上与各种迅速过时的遗留生物保持兼容,“陷阱”要少得多。 UEFI是所有现代PC的标准。 ===UEFI class 0-3 and CSM=== PC被归类为UEFI 0、1、2或3级。0类机器是具有传统BIOS的传统系统;i、 e.根本不是UEFI系统。 1级机器是一种UEFI系统,专门在兼容性支持模块(CSM)模式下运行。 CSM是UEFI固件如何模拟传统BIOS的规范。 CSM模式下的UEFI固件加载旧引导加载程序。 1类UEFI系统可能根本不公布UEFI支持,因为它不向引导加载程序公开。 它只是BIOS中的UEFI。 2级机器是一种UEFI系统,可以启动UEFI应用程序,但也包括在CSM模式下运行的选项。 大多数现代PC是UEFI 2级机器。 有时,运行UEFI应用程序的选择与。 CSM是BIOS配置中的一个或另一个设置,其他情况下,BIOS将在选择引导设备并检查其是否具有旧引导加载程序或UEFI应用程序后决定使用哪个。 3级机器是不支持CSM的UEFI系统。 UEFI 3类计算机仅运行UEFI应用程序,不实现CSM以向后兼容旧引导加载程序。 ===安全引导=== 安全引导是UEFI应用程序的数字签名方案,由四个组件组成: * '''PK''': Platform Key * '''KEK''': Key Exchange Keys * '''db''': Whitelist database * '''dbx''': Blacklist database 支持安全引导的UEFI固件始终处于以下三种状态之一: * 设置模式,安全引导“关闭” * 用户模式,安全引导“关闭” * 用户模式,安全引导“开启” 在设置模式下,任何UEFI应用程序都可以更改或删除PK、从KEK添加/删除密钥,以及从db或dbx添加/删除白名单或黑名单条目。 在用户模式下,无论安全引导是打开还是关闭: * PK只能由已经具有当前PK的UEFI应用程序更改或删除。 * 只有具有PK的UEFI应用程序才能从KEK添加/删除密钥。 * 只有在KEK中有任意一个密钥的UEFI应用程序才能从db和dbx中添加/删除白名单和黑名单条目。 最后,在安全引导“开启”的用户模式下,UEFI应用程序必须满足以下四项要求之一才能启动: * 已签名,签名在db中,而不是在dbx中 * 由db中的密钥而不是dbx中的密钥签名 * 用桶里的钥匙签名 * 未签名,但应用程序的哈希值在db中,而不是dbx中 请注意,UEFI应用程序不是由PK签名的,除非PK恰好也在KEK中。 并非所有UEFI固件都支持安全引导,尽管这是Windows 8的一项要求。 一些UEFI固件支持安全引导,并且没有禁用的选项,这给无法访问PK或KEK中任何密钥的独立开发人员带来了问题,因此无法在白名单数据库中安装自己的密钥或应用程序签名或哈希。 独立开发人员应该在不支持安全引导或具有关闭安全引导选项的系统上开发。 使用由Microsoft签名的加载程序的简单方法,允许您加载另一个由您拥有的密钥和证书(称为MOK,机器所有者密钥)签名的二进制文件。 这样的装载机是[https://github.com/rhboot/shimshim],被RedHat、Fedora、Suse、Ubuntu、Arch和许多其他发行版用来加载GRUB。 EFI可执行文件的文件名在shim中是硬连线的,但是如果将加载程序重命名为GRUBX64。EFI(或GRUBIA32.EFI),您可以使用MOK密钥和证书对其进行签名[https://github.com/imedias/sbsigntoolsbsigntool],然后您可以在安全引导中加载所需的任何加载程序。 尽管某些固件没有关闭安全引导的选项,但Win8的bootmgr中存在巨大的安全漏洞。efi,允许加载[https://www.xda-developers.com/microsofts-debug-mode-flaw-and-golden-key-leak-allows-disabling-of-secure-boot/关闭任何计算机上的安全引导]。 无法修补此缺陷,因为任何人都可以简单地更换固定的Win10 bootmgr。使用原始Win8 bootmgr的efi。efi没有问题(它们使用相同的Microsoft密钥签名)。 ===如何使用UEFI=== 像Windows和Linux这样的传统操作系统有一个现有的软件体系结构和一个庞大的代码库来执行系统配置和设备发现。 由于其复杂的抽象层,它们不能直接从UEFI中获益。 因此,他们的UEFI引导加载程序除了为它们的运行准备环境外几乎没有做什么。 独立开发人员可能会在使用UEFI编写功能完整的UEFI应用程序中发现更多价值,而不是将UEFI视为在引导过程中抛弃的临时启动环境。 与传统引导加载程序不同,传统引导加载程序通常只与BIOS交互以启动操作系统,UEFI应用程序可以在UEFI的帮助下实现复杂的行为。 换句话说,独立开发商不应该急于离开“UEFI土地”。 一个好的起点是编写一个UEFI应用程序,该应用程序使用系统表获取内存映射,并使用“文件”协议从FAT格式的磁盘读取文件。 下一步可能是使用系统表来定位ACPI表。 ==用POSIX-UEFI开发== :''Main article: [[POSIX-UEFI]] 在类似POSIX的系统上编译UEFI应用程序的一个选项是POSIX-UEFI。 它为您的EFI应用程序提供了一个类似[[libc]]的API,并附带了一个Makefile,可以为您检测和设置工具链。 它可以使用GCC或LLVM,默认使用主机编译器,但仍然建议使用交叉编译器。 它使用POSIX样式的typedef(如“uintn_t”而不是“uintn”),并且不附带标准EFI头。 通过从GNU-EFI或EDK2安装EFI头,仍然可以获得POSIX-UEFI(如GOP)未涵盖的接口。 此外,它使用MS ABI进行编译,这意味着只要您的应用程序也使用它进行编译,就可以在本地调用UEFI服务(即,不使用UEFI_call_包装器)。 传统的“你好,世界”UEFI程序是这样的。 <source lang="c"> #include <uefi.h> int main (int argc, char **argv) { printf("Hello, world!\n"); return 0; } </source> Makefile looks like this: <source lang="make"> TARGET = main.efi include uefi/Makefile </source> Run make to build it. The result of this process is a PE executable file ''main.efi''. ==用GNU-EFI开发== :''Main article: [[GNU-EFI]] GNU-EFI可用于开发32位和64位UEFI应用程序。 本节仅讨论64位UEFI应用程序,并假设开发环境本身在x86_64系统上运行,因此不需要交叉编译器。 有关正确(非gnu efi)开发环境的更全面的介绍,请参见[[UEFI应用程序裸体]]。 GNU-EFI includes four things: * '''crt0-efi-x86_64.o''': 一种CRT0(C运行时初始化代码),它提供了UEFI固件在启动应用程序时将调用的入口点,而UEFI固件又将调用开发人员编写的“efi_main”函数。 * '''libgnuefi.a''': 一个库,包含CRT0使用的单个函数(“重新定位”)。 * '''elf_x86_64_efi.lds''': 用于将ELF二进制文件链接到UEFI应用程序的链接器脚本。 * '''efi.h''' 和其他头:提供结构、typedef和常量的方便头可以提高访问系统表和其他UEFI资源时的可读性。 * '''libefi.a''': 一个库,包含方便的函数,如CRC计算、字符串长度计算和简单的文本打印。 * '''efilib.h''': “”libefi的标题。a“”。 至少64位UEFI应用程序需要链接到“crt0-efi-x86_64”。o“和”libgnuefi。a“”使用“elf_x86_64_efi”。lds“”链接器脚本。 最有可能的情况是,您还需要使用提供的标题和便利库,本节将假设继续。 传统的“Hello,world”UEFI程序如下所示。 <source lang="c"> #include <efi.h> #include <efilib.h> EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); Print(L"Hello, world!\n"); return EFI_SUCCESS; } </source> 几点注意事项 * efi.h is included so we can use types like EFI_STATUS, EFI_HANDLE, and EFI_SYSTEM_TABLE. * When creating a 32-bit UEFI application, EFIAPI is empty; GCC will compile the "efi_main" function using the standard C calling convention. When creating a 64-bit UEFI application, EFIAPI expands to "__attribute__((ms_abi))" and GCC will compile the "efi_main" function using Microsoft's x64 calling convention, as specified by UEFI. Only functions that will be called directly from UEFI (including main, but also callbacks) need to use the UEFI calling convention. * "InitializeLib" and "Print" are convenience functions provided by libefi.a with prototypes in efilib.h. "InitializeLib" lets libefi.a store a reference to the ImageHandle and SystemTable provided by BIOS. "Print" uses those stored references to print a string by reaching out to UEFI-provided functions in memory. (Later on we will see how to find and call UEFI-provided functions manually.) 该程序的编译和链接如下。 <source lang="bash"> $ gcc main.c \ -c \ -fno-stack-protector \ -fpic \ -fshort-wchar \ -mno-red-zone \ -I /path/to/gnu-efi/headers \ -I /path/to/gnu-efi/headers/x86_64 \ -DEFI_FUNCTION_WRAPPER \ -o main.o $ ld main.o \ /path/to/crt0-efi-x86_64.o \ -nostdlib \ -znocombreloc \ -T /path/to/elf_x86_64_efi.lds \ -shared \ -Bsymbolic \ -L /path/to/libs \ -l:libgnuefi.a \ -l:libefi.a \ -o main.so $ objcopy -j .text \ -j .sdata \ -j .data \ -j .dynamic \ -j .dynsym \ -j .rel \ -j .rela \ -j .reloc \ --target=efi-app-x86_64 \ main.so \ main.efi </source> 此过程的结果是44 kB PE可执行文件“”main。efi“”。 在实际项目中,您可能需要使用make或其他构建工具,并且可能需要构建交叉编译器。 GNU-EFI的工作方式有点做作:您正在将普通编译器生成的ELF文件包装到PE中。 ==使用QEMU和OVMF进行仿真== 任何最新版本的QEMU和最新版本的OVMF都足以运行UEFI应用程序。 QEMU二进制文件可用于许多平台,在[http://www.tianocore.org/ovmf/TianoCore]网站。 QEMU(无任何启动磁盘)可以按如下方式调用。 (要防止QEMU的最新版本在未找到启动磁盘时尝试PXE(网络)启动,请使用<code>-net none</code>。 建议使用OVMF(对于QEMU 1.6或更新版本)的方法是使用<code>pflash</code>参数。 下面的说明假设您将OVMF映像拆分为单独的代码和VARS部分。 <source lang="bash"> $ qemu-system-x86_64 -cpu qemu64 \ -drive if=pflash,format=raw,unit=0,file=path_to_OVMF_CODE.fd,readonly=on \ -drive if=pflash,format=raw,unit=1,file=path_to_OVMF_VARS.fd \ -net none </source> 如果您喜欢在没有显示器的终端上工作,或者通过SSH/telnet,您将希望使用<code>-nographic</code>标志在没有图形支持的情况下运行QEMU。 如果OVMF找不到具有正确命名的UEFI应用程序的引导磁盘(稍后将对此进行详细介绍),它将放入UEFI外壳中。 [[file:OVMF_shell.png]] 您可以找到shell命令的列表[http://www.sysadminshare.com/2012/01/efi-shell-commands.html此处]或者您可以在shell中键入“帮助”。 ===创建磁盘映像=== :''Main article: [[Bootable Disk]] 要启动UEFI应用程序,您需要创建磁盘映像并将其呈现给QEMU。 UEFI固件期望UEFI应用程序存储在[[GPT]]分区磁盘上的FAT12、FAT16或FAT32文件系统(称为[[EFI系统分区]])中。 许多固件只支持FAT32,所以这就是您想要使用的。 根据您的平台,有几种不同的方法来创建包含UEFI应用程序的磁盘映像,但它们都是从创建零磁盘映像文件开始的。 FAT32分区的最小大小为33548800字节,此外,主GPT表和辅助GPT表还需要一些空间,再加上一些空闲空间,以便正确对齐分区。 在这些示例中,我们将创建一个48000000字节(93750512字节扇区,或48MB)的磁盘映像。 <source lang="bash"> $ dd if=/dev/zero of=/path/to/uefi.img bs=512 count=93750 </source> ====uefi-run helper application==== uefi运行应用程序对于快速测试非常有用。它创建一个包含EFI应用程序的临时FAT映像,并启动qemu。 <source lang="bash"> $ uefi-run -b /path/to/OVMF.fd -q /path/to/qemu app.efi -- <extra_qemu_args> </source> uefi-run is not currently packaged for any distribution. You can install it using cargo (the Rust package manager) though ("cargo install uefi-run"). ====Linux, root required==== This approach requires root privileges and uses '''gdisk''', '''losetup''', and '''mkdosfs'''. First, use gdisk to create a GPT partition table with a single [[EFI System Partition]]. <source lang="bash"> $ gdisk /path/to/uefi.img GPT fdisk (gdisk) version 0.8.10 Partition table scan: MBR: not present BSD: not present APM: not present GPT: not present Creating new GPT entries. Command (? for help): o This option deletes all partitions and creates a new protective MBR. Proceed? (Y/N): y Command (? for help): n Partition number (1-128, default 1): 1 First sector (34-93716, default = 2048) or {+-}size{KMGTP}: 2048 Last sector (2048-93716, default = 93716) or {+-}size{KMGTP}: 93716 Current type is 'Linux filesystem' Hex code or GUID (L to show codes, Enter = 8300): ef00 Changed type of partition to 'EFI System' Command (? for help): w Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING PARTITIONS!! Do you want to proceed? (Y/N): y OK; writing new GUID partition table (GPT) to uefi.img. Warning: The kernel is still using the old partition table. The new table will be used at the next reboot. The operation has completed successfully. </source> Now you have disk image with [[GPT|GUID partition table]] on it and an unformatted EFI partition starting at sector 2048. Unless you deviated from the commands shown above, the disk image will use 512-byte sectors, so the EFI partition starts at byte 1,048,576 and is 46,934,528 bytes in length. Use losetup to present the partition to Linux on a loopback device. <source lang="bash"> losetup --offset 1048576 --sizelimit 46934528 /dev/loop0 /path/to/uefi.img </source> (If /dev/loop0 is already in use you will need to select a different loopback device.) Format the partition for FAT32 with mkdosfs. <source lang="bash"> mkdosfs -F 32 /dev/loop0 </source> 现在可以挂载分区,这样我们就可以将文件复制到它。 在本例中,我们使用“/mnt”目录,但您也可以创建一个本地目录以供临时使用。 <source lang="bash"> mount /dev/loop0 /mnt </source> 将要测试的任何UEFI应用程序复制到文件系统。 <source lang="bash"> $ cp /path/to/main.efi /mnt/ $ ... </source> 最后,卸载分区并释放环回设备。 <source lang="bash"> $ umount /mnt $ losetup -d /dev/loop0 </source> ''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT32 file system, containing one or more UEFI applications. ====Linux,不需要root==== This approach uses '''parted''', '''mformat''', and '''mcopy''' and can be performed with user privileges. 首先,使用parted创建主GPT头和辅助GPT头,以及一个与上述方法相同范围的EFI分区。 <source lang="bash"> $ parted /path/to/uefi.img -s -a minimal mklabel gpt $ parted /path/to/uefi.img -s -a minimal mkpart EFI FAT16 2048s 93716s $ parted /path/to/uefi.img -s -a minimal toggle 1 boot </source> 现在创建一个新的临时映像文件,该文件将包含EFI分区数据,并使用mformat使用FAT16对其进行格式化。 <source lang="bash"> dd if=/dev/zero of=/tmp/part.img bs=512 count=91669 mformat -i /tmp/part.img -h 32 -t 32 -n 64 -c 1 </source> 使用mcopy将要测试的任何UEFI应用程序复制到文件系统。 <source lang="bash"> $ mcopy -i /tmp/part.img /path/to/main.efi :: $ ... </source> 最后,将分区映像写入主磁盘映像。 <source lang="bash"> $ dd if=/tmp/part.img of=/path/to/uefi.img bs=512 count=91669 seek=2048 conv=notrunc </source> ''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications. ====FreeBSD, root required==== This approach requires root privileges and uses '''mdconfig''', '''gpart''', '''newfs_msdos''', and '''mount_msdosfs'''. First, create a device node that presents the zeroed disk image as a block device. This will let us work on it using standard partitioning and formatting tools. <source lang="bash"> $ mdconfig -f /path/to/uefi.img md0 </source> In this example the new block device is ''md0''. Now create the empty primary and secondary GPT tables on the device. <source lang="bash"> $ gpart create -s GPT md0 md0 created </source> 现在我们可以向磁盘添加一个分区。 We'll specify an "EFI" partition, which just means that GPT will set that partition's GUID to the special "EFI" type. 并不是所有的bios都需要这个,分区仍然可以在Linux、FreeBSD和Windows上正常安装和浏览。 <source lang="bash"> $ gpart add -t efi md0 md0p1 added </source> 接下来,在新分区上创建FAT16文件系统。 如果愿意,可以为文件系统指定各种参数,但这不是必需的。 理想情况下,您可以创建一个FAT32分区以实现最佳固件兼容性,但FreeBSD似乎创建了OVMF无法读取的FAT32分区。 <source lang="bash"> $ newfs_msdos -F 16 md0p1 newfs_msdos: trim 2 sectors to adjust to a multiple of 9 /dev/md2p1: 93552 sectors in 11694 FAT16 clusters (4096 bytes/cluster) BytesPerSec=512 SecPerClust=8 ResSectors=1 FATs=2 RootDirEnts=512 Media=0xf0 FATsecs=46 SecPerTrack=9 Heads=16 HiddenSecs=0 HugeSectors=93681 </source> 现在可以挂载分区,这样我们就可以将文件复制到它。 在本例中,我们使用'/mnt''目录,但您也可以创建一个本地目录以供临时使用。 <source lang="bash"> $ mount_msdosfs /dev/md0p1 /mnt </source> 将要测试的任何UEFI应用程序复制到文件系统。 <source lang="bash"> $ cp /path/to/main.efi /mnt/ $ ... </source> 最后,卸载分区并释放块设备。 <source lang="bash"> $ umount /mnt $ mdconfig -d -u md0 </source> ''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications. ====Mac OS (root not required)==== Mac OS has a single tool (hdiutil) that creates the disk image and copy files at same time. Let's say that you're creating a UEFI boot for x86_64. 根据定义,文件名应该是BOOTX64。EFI和该文件应位于/EFI/BOOT文件夹中。 首先,让我们创建一个临时文件夹,其中包含启动UEFI所需的所有文件和文件夹。 <source lang="bash"> $ mkdir -p diskImage/EFI/BOOT </source> 其次,让我们将引导应用程序复制到所需位置: <source lang="bash"> $ cp bootx64.efi diskImage/EFI/BOOT/BOOTX64.EFI </source> 最后,让我们创建一个用GPT分区的磁盘映像,用fat32(-fs fat32)格式化,如果需要可以覆盖目标文件(-ov),定义磁盘大小(-size 48m),定义卷名(-volname NEWOS), 磁盘将被编码的文件格式(-format UDTO-与DVD/CD相同)和包含将被复制到新磁盘的文件的源文件夹: <source lang="bash"> $ hdiutil create -fs fat32 -ov -size 48m -volname NEWOS -format UDTO -srcfolder diskImage uefi.cdr </source> uefi.cdr should be ready to be used by QEMU. ===启动UEFI应用程序=== 一旦您的磁盘映像准备好,您就可以如下调用QEMU。 <source lang="bash"> $ qemu-system-x86_64 -cpu qemu64 -bios /path/to/OVMF.fd -drive file=uefi.disk,if=ide </source> 当OVMF放入UEFI外壳时,您将在“映射表”中看到一个附加条目,标记为“FS0”。 这表示固件检测到磁盘,发现了分区,并能够装载文件系统。 您可以通过使用DOS风格的语法“FS0:”切换到文件系统来浏览文件系统,如下所示。 [[File:OVMF_browse_fs.png]] You can launch a UEFI application by entering its name. [[File:OVMF_run_app.png]] 请注意,一旦应用程序终止,UEFI外壳将恢复。 当然,如果这是一个正确的引导程序,它将永远不会恢复,而是启动一个操作系统。 一些商用UEFI固件提供UEFI外壳或启动用户选择的UEFI应用程序的功能,如HP EliteBook系列笔记本电脑附带的固件。 但是,大多数人不会向最终用户公开此功能。 ===调试=== OVMF可以在调试模式下构建,并将日志消息输出到IO端口0x402。 您可以使用下面的一些标志来捕获输出。 <code>-debugcon file:uefi_debug.log -global isa-debugcon.iobase=0x402</code> 请注意,发布版本不会输出调试消息,或者会减少输出。 See [[Debugging UEFI applications with GDB]]. ==在真正的硬件上运行== ===NVRAM variables=== {{Main|UEFI NVRAM}} UEFI固件将通过文本或图形配置菜单显示其大部分配置选项,就像传统BIOS一样。 在这些菜单中进行的选择在重新启动之间存储在NVRAM芯片中。 然而,与传统BIOS不同的是,固件开发人员可以选择通过启动时固件驻留在RAM中的方便功能向操作系统和最终用户公开部分或全部这些“NVRAM变量”。 Linux“efivarfs”内核模块将使用这些函数在“/sys/firmware/efi/efivars”文件中列出NVRAM变量。 NVRAM变量也可以通过“dmpstore”命令从UEFI外壳本身中转储。 设备引导顺序始终可通过NVRAM变量访问。 Linux程序“efibootmgr”专门用于引导顺序NVRAM变量。 UEFI shell提供了用于相同目的的“bcfg”命令。 ===可引导的UEFI应用程序=== 引导顺序NVRAM变量确定固件将在引导时在何处查找要启动的UEFI应用程序。 虽然这是可以更改的(例如,OS安装程序可能会自定义安装它的硬盘驱动器的引导条目),但固件通常会查找名为“boot”的UEFI应用程序。efi" (for 32-bit applications) or "BOOTX64.efi" (for 64-bit applications) stored in the "/EFI/BOOT" path in the boot device's file system. 这是OVMF的默认路径和名称。 与从shell启动的UEFI应用程序不同,如果可引导UEFI应用程序返回,BIOS将继续搜索其他引导设备。 ===Exposed functionality=== 真正的PC机向用户提供的UEFI功能数量各不相同。 例如,即使是3级机器也可能在其BIOS配置中未提及UEFI,也可能不提供UEFI外壳。 此外,一些BIOS供应商使其UEFI固件配置屏幕看起来与传统BIOS配置屏幕相同。 2类机器可能会出现一些混乱的启动菜单和配置选项。 例如,一家笔记本电脑制造商包括一个名为“OS:Windows 8”的配置选项,用于启用/禁用UEFI(即UEFI和CSM行为之间的切换)。如果另一台笔记本电脑在所选引导设备上找不到可引导的UEFI应用程序(或者该应用程序返回EFI_SUCCESS以外的状态),它将返回CSM行为,然后抱怨驱动器的MBR已损坏。 随着时间的推移,随着第3类机器的出现,UEFI引导行为的清晰度将会提高。 为了使在真实硬件上的测试更容易,您可以将可引导UEFI应用程序安装到提供引导菜单的系统的内部硬盘驱动器上,如[http://www.rodsbooks.com/refind/重新查找]。 这对于多引导场景也很方便。 ===PC firmware developers=== On x86 and x86-64 platforms, the following BIOS developers offer UEFI firmware: * AMI (Aptio). * Phoenix (SecureCore, TrustedCore, AwardCore). * Insyde (InsydeH20). ===Apple systems=== 苹果系统实现EFI 1.0,与UEFI不同,UEFI应用程序是从HFS+文件系统而不是FAT12/16/32加载的。 Additionally, those UEFI applications must be "blessed" (either directly, or by residing in a blessed directory) to be loaded. 福佑在HFS+文件系统中设置标志,苹果的固件在加载应用程序之前对其进行检查。 The open-source '''hfsutils''' package includes support for blessing files within HFS file systems, but not directories nor HFS+. == UEFI applications in detail == === Binary Format === UEFI executables are regular PE32 / PE32+ (Windows x32 / x64) images, with a specific '''subsystem'''. 每个UEFI应用程序基本上都是一个没有符号表的windows EXE(或DLL)。 {| class="wikitable" |+ Types of UEFI images |- ! Type ! Description ! Subsystem |- | Applications || OS loaders and other utility programs. || 10 |- | Boot service driver || Drivers used by the firmware when booting (e.g. disk drivers, network drivers). || 11 |- | Runtime driver || Drivers which may stay loaded even after the OS loads and exits the boot services. || 12 |} UEFI图像还必须指定其包含的机器代码类型。 UEFI加载程序将拒绝启动不兼容的映像。 {| class="wikitable" |+ Types of machines |- ! Name / arch ! Value |- |x86||0x014c |- |x86_64||0x8664 |- |Itanium x64||0x0200 |- |UEFI Byte Code||0x0EBC |- |ARM<sup>1</sup>||0x01C2 |- |AArch (ARM x64)||0xAA64 |- |RISC-V x32||0x5032 |- |RISC-V x64||0x5064 |- |RISC-V x128||0x5128 |} [1] ARM means you can use Thumb / Thumb 2 instructions, but UEFI interfaces are in ARM mode. ==== Initialization ==== Applications must either load an OS and exit boot services, or return from the main function (in which case the boot loader will look for the next app to load). 驱动程序必须初始化,然后在成功时返回0或错误代码。 如果所需的驱动程序未能加载,则计算机可能也无法启动。 ==== Memory ==== UEFI返回的内存映射将标记驾驶员使用的内存区域。 一旦操作系统加载程序完成,内核就可以重用加载引导加载程序的内存。 The memory types are <code>Efi{Loader/BootServices/RuntimeServices}{Code/Data}</code>. 退出引导服务后,您可以重用引导驱动程序使用的任何非只读内存。 However, memory used by the runtime drivers must '''never''' be touched - the runtime drivers stay active and loaded for as long as the computer runs. 查看包含UEFI应用程序的PE文件的分解的一种方法是通过<source lang=“bash”>$objdump--all headers/path/to/main。efi</source> 它的产量相当长。 除此之外,它还显示了“子系统”,即前面提到的UEFI映像的类型。 === Calling Conventions === UEFI指定以下调用约定: * '''cdecl''' for x86 UEFI functions * '''Microsoft's 64-bit calling convention''' for x86-64 UEFI functions * '''SMC''' for ARM UEFI functions 这对UEFI应用程序开发人员有两个影响: * UEFI应用程序的主入口点必须使用相应的调用约定进行调用。 * UEFI应用程序调用的任何UEFI提供的函数必须使用相应的调用约定进行调用。 请注意,应用程序内部的函数可以使用开发人员选择的任何调用约定。 ==== POSIX-UEFI, GNU-EFI and GCC ==== {{Main|GNU-EFI}} cdecl是GCC使用的标准调用约定,因此在使用GNU-EFI开发的x86 UEFI应用程序中编写主入口点或调用UEFI函数不需要特殊属性或修饰符。 For x86-64, however, the entry point function must be declared with the "___attribute___((ms_abi))" modifier and all calls to UEFI-provided functions must be made through the "uefi_call_wrapper" thunk. 此thunk使用cdecl调用,但在调用请求的UEFI函数之前,会转换为Microsoft x86-64调用约定。 这是必要的,因为较旧版本的GCC不支持为函数指针指定调用约定。 For [[POSIX-UEFI]], which also uses GCC, your entry point looks like the standard main(), and no special ABI is required. Also the build environment takes care of the compiler flags for you, so you can simply call UEFI functions without "uefi_call_wrapper", no matter if you're using the host gcc or a cross-compiler. For developer convenience, both POSIX-UEFI and GNU-EFI provides the "EFIAPI" macro, which expands to "cdecl" when targeting x86 and "__attribute__(ms_abi))" when targeting x86-64. Additionally, the "uefi_call_wrapper" thunk will simply pass the call through on x86. This allows the same source code to target x86 and x86-64. For example, the following main function will compile with the correct calling convention on both x86 and x86-64 and the call through the "uefi_call_wrapper" thunk will select the correct calling convention to use when calling the UEFI function (in this case, printing a string). <source lang="c"> EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { EFI_STATUS status = uefi_call_wrapper(SystemTable->ConOut->OutputString, 2, SystemTable->ConOut, L"Hello, World!\n"); return status; } </source> === Language binding === UEFI applications are typically written in C, although bindings could be written for any other language that compiles to machine code. Assembly is also an option; a [[uefi.inc]] is available for [[FASM]] that allows UEFI applications to be written as below. <source lang="asm"> format pe64 dll efi entry main section '.text' code executable readable include 'uefi.inc' main: ; initialize UEFI library InitializeLib jc @f ; call uefi function to print to screen uefi_call_wrapper ConOut, OutputString, ConOut, _hello @@: mov eax, EFI_SUCCESS retn section '.data' data readable writeable _hello du 'Hello World',13,10,0 section '.reloc' fixups data discardable </source> As a UEFI application contains normal x86 or x86-64 machine code, inline assembly is also an option in compilers that support it. === EFI Byte Code=== UEFI还包括一个基于称为EFI字节码(EBC)的字节码格式的虚拟机规范,该规范可用于编写独立于平台的设备驱动程序,但不能用于UEFI应用程序。 截至2015年,EBC的使用有限。 == Common Problems == === My UEFI application hangs/resets after about 5 minutes === 当固件将控制权交给UEFI应用程序时,它会设置一个5分钟的看门狗计时器,之后固件会重新调用,因为它假定应用程序已挂起。 在这种情况下,固件通常会尝试重置系统(尽管VirtualBox中的OVMF固件只会导致屏幕变黑并挂起)。 为了解决这个问题,您需要在看门狗计时器超时之前刷新它。 Alternatively, you can disable it completely with code like <source lang="C">SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);</source>Obviously this is not a problem for most bootloaders, but can cause an issue if you have an interactive loader which waits for user input. 还请注意,如果退出固件,则需要禁用看门狗定时器。 === My bootloader hangs if I use user defined EFI_MEMORY_TYPE values === 对于EFI中的内存管理功能,操作系统应能够出于自身目的使用0x8000000以上的“内存类型”值。 在OVFM EFI固件版本“r11337”(用于Qemu等)中,存在一个错误,固件假定内存类型在为EFI自身使用定义的值范围内,并将内存类型用作阵列索引。 最终的结果是“数组索引超出范围”错误;较高的内存类型值(例如,0x8000000以上的完全合法值)导致64位版本的固件崩溃(页面错误),并导致32位版本的固件报告不正确的“属性”值。 同样的错误也存在于任何版本的EFI固件VirtualBox中(看起来像旧版本的OVFM); 我怀疑(但不知道)该漏洞可能存在于从tianocore项目(不仅仅是OVFM)衍生的各种固件中。 == See also == === Articles === * [[UEFI Bare Bones]] * [[EFI System Partition]] * [[PE]] file format * [[TianoCore]] * [[POSIX-UEFI]] * [[GNU-EFI]] * [https://github.com/nebulaeonline/nebulae/tree/UefiBarebones Uefi Barebones MSVC/Clang/Visual Studio] === External Links === * [https://uefi.org/specifications UEFI specifications et al.] * [http://www.tianocore.org/ Intel TianoCore EDK2] * [https://github.com/tianocore/tianocore.github.io/wiki/OVMF OVMF firmware images] for use with [[QEMU]] * [https://web.archive.org/web/20181028201454/http://wiki.phoenix.com/wiki/index.php/Main_Page Phoenix UEFI Wiki] * [http://x86asm.net/articles/others/index.html Several articles about UEFI] * [https://www.microsoft.com/en-us/download/details.aspx?id=19509 PE specification covering the (U)EFI binary format] * [https://uefi.blogspot.com/ Blog about UEFI, with bits about UEFI development] * [https://web.archive.org/web/20160316192235/http://internshipatdell.wikispaces.com/file/view/How+to+build+an+UEFI+application.pptx Presentation guiding through simple UEFI application setup] * [https://uefi.org/sites/default/files/resources/UEFI-Plugfest-WindowsBootEnvironment.pdf Presentation giving an overview of windows uefi booting] * [https://gitlab.com/bztsrc/posix-uefi POSIX-UEFI] documentation and source * [[Wikipedia:Extensible_Firmware_Interface|Wikipedia Article on EFI]] [[Category:x86]] [[Category:x86-64]] [[Category:IA-64]] [[Category:ARM]] [[Category:Firmware]] [[Category:UEFI]] [[de:Unified_Extensible_Firmware_Interface]]
本页使用的模板:
模板:Main
(
查看源代码
)
返回至“
UEFI
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息