如何设置 PE 标头特征标志

问题描述

我正在尝试使用 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,这意味着设置了这些标志:

0x0200 从映像文件删除调试信息。

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 链接,但存在一些问题。

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

--subsystem which:major.minor

指定您的程序将在哪个子系统下执行。这 合法值是 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