问题描述
我正在尝试使用 gcc 在 64 位模式下为 UEFI 制作一个 UEFI 可引导的 PE32+ 文件。
首先我正在编译源代码。
cc -nostartfiles -o bootx64.o bootx64.c
然后我将丢弃除 .text 和 .data 部分之外的所有内容并重命名文件。
objcopy -j .text -j .data --target=pei-x86-64 --subsystem=10 --strip-unneeded bootx64.o
mv bootx64.o bootx64.efi
这会生成一个几乎完美的文件。除了 PE 头特性标志设置错误。 (https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics)
我通过以下方式获取特征:
objdump -p bootx64.efi
输出为:
bootx64.efi: file format pei-x86-64
characteristics 0x20f
relocations stripped
executable
line numbers stripped
symbols stripped
debugging information removed
因此将特性标志设置为 0x020f,这意味着设置了这些标志:
0x0001 仅映像、Windows CE 和 Microsoft Windows NT 及更高版本。这表明该文件不包含基址重定位,因此必须在其首选基址加载。如果基地址不可用,加载程序将报告错误。链接器的默认行为是从可执行 (EXE) 文件中去除基址重定位。
0x0002 仅图像。这表明映像文件有效并且可以运行。如果未设置此标志,则表示链接器错误。
0x0004 COFF 行号已被删除。此标志已弃用,应为零。
0x0008 本地符号的 COFF 符号表条目已被删除。此标志已弃用,应为零。
在设置/重置和测试每个标志后,我发现 UEFI 不喜欢 0x0001 标志,因为这意味着它必须在首选地址加载代码。我的代码不包含任何重定位,但是当我手动删除 0x0001 标志时它工作正常。旁注:objcopy 还设置了两个不推荐使用的标志(0x0004 和 0x0008),我也不希望这种情况发生。
objcopy 有什么办法可以设置 PE 头的特性标志,只设置 0x0002,然后 objcopy 直接取消设置 0x0001、0x0004 和 0x0008,而不是事后手动设置?
ld --oformat pei-x86-64 --subsystem 10 -o bootx64.efi bootx64.o
结果
ld: unrecognized option '--subsystem'
尽管 GNU binutils 文档 (https://sourceware.org/binutils/docs/ld/Options.html) 说:
--子系统哪个
--subsystem which:major
指定您的程序将在哪个子系统下执行。这 合法值是 native、windows、console、posix 和 xBox。 您也可以选择设置子系统版本。数值是 也接受了。 [此选项特定于 i386 PE 链接器的目标端口]
所以我需要一个选项“EFI”或数值为 10。此处不存在该选项。
我还尝试了 pe-x86-64 和 pei-i386 以及我在名称中找到的带有 pe 的所有内容。在没有子系统选项的情况下进行链接会导致标题几乎完全为零。
Objcopy 做得最好,但仍然不好(上面讨论的问题)我写了一个小实用程序来正确填写标题,所以最后我坚持使用我的实用程序。
解决方法
我认为您正在以一种不寻常的方式编译和链接(U}EFI 应用程序。我的经验是您构建一个共享对象(DLL 或 .so),然后将其转换为 UEFI 可执行文件 (.efi) ,通常使用特殊工具。
例如,查看 http://x86asm.net/articles/uefi-programming-first-steps/,一篇关于 EFI 工具包但仍然相关的旧文章。
cl /c /Zl /I"{EFI_Toolkit}\include\efi" /I"{EFI_Toolkit}\include\efi\em64t" hello.c
link /entry:main /dll /IGNORE:4086 hello.obj
fwimage app hello.dll hello.efi
fwimage
工作正常,但有点问题。
转向更现代的 EFI 构建环境,即 gnu-efi:
C = gcc
AS = as
LD = ld.bfd
AR = ar
RANLIB = ranlib
OBJCOPY = objcopy
ifeq ($(ARCH),x86_64)
CFLAGS += -mno-red-zone
LIBDIR := $(PREFIX)/$(ARCH)/lib
LIBDIR += $(PREFIX)/$(ARCH)/gnuefi
ifeq ($(HOSTARCH),ia32)
ARCH3264 := -m64
endif
endif
FORMAT = efi-app-$(ARCH)
LDSCRIPT = $(TOPDIR)/gnuefi/elf_$(ARCH)_efi.lds
CFLAGS = $(ARCH3264) -g -O0 -fpic -Wall -fshort-wchar -fno-strict-aliasing -fno-merge-constants --std=gnu99 -D_GNU_SOURCE
.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 $@ $^ $(LIBS)
gnu-efi
并非没有问题和批评。例如,参见https://dvdhrm.github.io/2019/01/31/goodbye-gnuefi/
最后一个例子,看看 EDKII 如何使用 genfw
构建 .efi:
GENFW = GenFw
CC_FLAGS = -g -Os -fshort-wchar -fno-builtin -fno-strict-aliasing -Wall -Werror -Wno-array-bounds -include AutoGen.h -fno-common -ffunction-sections -fdata-sections -DSTRING_ARRAY_NAME=$(BASE_NAME)Strings -Wno-parentheses-equality -Wno-tautological-compare -Wno-tautological-constant-out-of-range-compare -Wno-empty-body -Wno-unused-const-variable -Wno-varargs -Wno-unknown-warning-option -fno-stack-protector -mms-bitfields -Wno-address -Wno-shift-negative-value -Wno-unknown-pragmas -Wno-incompatible-library-redeclaration -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -msoft-float -mno-implicit-float -ftrap-function=undefined_behavior_has_been_optimized_away_by_clang -funsigned-char -fno-ms-extensions -Wno-null-dereference -m64 "-DEFIAPI=__attribute__((ms_abi))" -mno-red-zone -mcmodel=small -fpie -Oz -flto -target x86_64-pc-linux-gnu
CC = clang
DLINK_FLAGS = -nostdlib -Wl,-n,-q,--gc-sections -z common-page-size=0x40 -Wl,--entry,$(IMAGE_ENTRY_POINT) -u $(IMAGE_ENTRY_POINT) -Wl,-Map,$(DEST_DIR_DEBUG)/$(BASE_NAME).map,--whole-archive -flto -Wl,-Oz -Wl,-melf_x86_64 -Wl,--oformat=elf64-x86-64 -Wl,-pie -mcmodel=small
DLINK = clang
$(OUTPUT_DIR)/DateTime.obj : $(MAKE_FILE)
$(OUTPUT_DIR)/DateTime.obj : $(DEBUG_DIR)/AutoGen.h
$(OUTPUT_DIR)/DateTime.obj : $(WORKSPACE)/MyApps/DateTime/DateTime.c
"$(CC)" $(DEPS_FLAGS) $(CC_FLAGS) -c -o /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/OUTPUT/./DateTime.obj $(INC) /home/fpm/edk2/MyApps/DateTime/DateTime.c
$(OUTPUT_DIR)/DateTime.lib : $(OBJECT_FILES)
$(OUTPUT_DIR)/DateTime.lib : $(OBJECT_FILES_LIST)
$(RM) /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/OUTPUT/DateTime.lib
"$(SLINK)" cr /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/OUTPUT/DateTime.lib $(SLINK_FLAGS) @$(OBJECT_FILES_LIST)
$(DEBUG_DIR)/DateTime.dll : $(MAKE_FILE)
$(DEBUG_DIR)/DateTime.dll : $(STATIC_LIBRARY_FILES)
$(DEBUG_DIR)/DateTime.dll : $(STATIC_LIBRARY_FILES_LIST)
"$(DLINK)" -o /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/DEBUG/DateTime.dll $(DLINK_FLAGS) -Wl,--start-group,@$(STATIC_LIBRARY_FILES_LIST),--end-group $(CC_FLAGS) $(DLINK2_FLAGS)
"$(OBJCOPY)" $(OBJCOPY_FLAGS) /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/DEBUG/DateTime.dll
$(OUTPUT_DIR)/DateTime.efi : $(DEBUG_DIR)/DateTime.dll
$(CP) /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/DEBUG/DateTime.dll $(DEBUG_DIR)/$(MODULE_NAME).debug
$(OBJCOPY) --strip-unneeded -R .eh_frame /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/DEBUG/DateTime.dll
-$(OBJCOPY) $(OBJCOPY_ADDDEBUGFLAG) /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/DEBUG/DateTime.dll
-$(CP) $(DEBUG_DIR)/$(MODULE_NAME).debug $(BIN_DIR)/$(MODULE_NAME_GUID).debug
"$(GENFW)" -e $(MODULE_TYPE) -o /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/OUTPUT/DateTime.efi /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/DEBUG/DateTime.dll $(GENFW_FLAGS)
$(CP) /home/fpm/edk2/Build/MyApps/RELEASE_CLANG11/X64/MyApps/DateTime/DateTime/OUTPUT/DateTime.efi $(DEBUG_DIR)
$(CP)
此处描述了 genfw
工具:https://edk2-docs.gitbook.io/edk-ii-basetools-user-guides/genfw