Как установить флаги характеристик заголовка PE

Я пытаюсь создать UEFI-загрузочный файл PE32+ для UEFI в 64-битном режиме с помощью gcc.

Сначала я компилирую исходники.

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, а 0x0001, 0x0004 и 0x0008 будут удалены objcopy напрямую, вместо того, чтобы делать это вручную после этого?

РЕДАКТИРОВАТЬ: я пытался связать файл с 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.minor

Указывает подсистему, в которой будет выполняться ваша программа. Допустимые значения для которых: native, windows, console, posix и xbox. При желании вы также можете установить версию подсистемы. Принимаются также числовые значения, для которых . [Этот параметр относится к целевому порту i386 PE компоновщика]

Поэтому мне понадобится опция EFI или 10 в числовом значении. этой опции здесь нет.

Я также пробовал pe-x86-64 и pei-i386 и все, что я нашел с pe в названии. Связывание без опции подсистемы приводит к почти полностью заполненному нулями заголовку.

Objcopy справился лучше всех, но все же не очень хорошо (проблемы обсуждались выше) Я написал маленькую утилиту, которая правильно заполняет шапку, так что в итоге я застрял на своей утилите.


person colchy    schedule 17.04.2021    source источник
comment
Начиная с binutils 2.34, вы можете указать ld всегда создавать таблицу перемещений (с помощью -Wl,--enable-reloc-section).   -  person ssbssa    schedule 17.04.2021
comment
Может быть, используя objcopy --set-section-flags?   -  person Olaf Dietsche    schedule 17.04.2021
comment
@ssbssa --enable-reloc-section поддерживается только для i386pe и i386pep. не для pei-x86-64 - как мне это использовать с pei-x86-64? Я добавил уточнение в исходный вопрос. спасибо!   -  person colchy    schedule 17.04.2021
comment
@OlafDietsche Как установить флаги характеристик заголовка PE с помощью --set-section-flags? Благодарю вас!   -  person colchy    schedule 17.04.2021
comment
Теперь я вижу, что ld вообще никогда не вызывается, поэтому похоже, что вам нужно изменить objcopy, чтобы он делал именно то, что вам здесь нужно.   -  person ssbssa    schedule 17.04.2021
comment
@colchy Я только что заглянул в man objcopy и нашел этот вариант. Я не пробовал и не знаю на самом деле. Я бы просто поиграл и попробовал разные комбинации, может быть --set-section-flags .text=<some flags>. Возможно, это не подходит для вашей проблемы.   -  person Olaf Dietsche    schedule 17.04.2021


Ответы (1)


Я думаю, вы собираетесь компилировать и связывать приложение (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 создает .efi, используя genfw:

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

person fpmurphy    schedule 22.04.2021
comment
Спасибо за ваш ответ! Это очень полезно, потому что я не знал большинства инструментов, и я буду изучать их! Я использовал gnu-efi, но стараюсь его избегать, потому что он сначала перемещает весь код, а затем каждый раз, когда вы хотите вызвать функцию, вам сначала нужно вызвать функцию-оболочку в соглашении о вызовах System V ABI, которое затем перетасовывает аргументы, а затем вызывает фактическую функцию в правильном Microsoft ABI. Это не очень элегантно, и вы также не можете настроить заголовок в gnu-efi. Я использую __attribute__((ms_abi)) поэтому он просто правильно вызывает функцию. - person colchy; 23.04.2021
comment
@колчи. ИМХО, gnu-efi был полезным набором инструментов 10 лет назад, но превратился в беспорядок с тех пор, как была добавлена ​​​​поддержка кросс-компиляции и дополнительных архитектур. - person fpmurphy; 23.04.2021