I'm trying to make an UEFI-bootable PE32+ file for UEFI in 64bit-mode with gcc.
First I'm compiling the source.
cc -nostartfiles -o bootx64.o bootx64.c
Then I'm throwing away everything except the .text and .data section and renaming the file.
objcopy -j .text -j .data --target=pei-x86-64 --subsystem=10 --strip-unneeded bootx64.o
mv bootx64.o bootx64.efi
That generates an almost perfect file. Except the PE header Characteristics flags are set wrong. (https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics)
I get the characteristics by:
objdump -p bootx64.efi
and the output is:
bootx64.efi: file format pei-x86-64
Characteristics 0x20f
relocations stripped
executable
line numbers stripped
symbols stripped
debugging information removed
So the characteristics flag is set to 0x020f that means that these flags are set:
0x0200 Debugging information is removed from the image file.
0x0001 Image only, Windows CE, and Microsoft Windows NT and later. This indicates that the file does not contain base relocations and must therefore be loaded at its preferred base address. If the base address is not available, the loader reports an error. The default behavior of the linker is to strip base relocations from executable (EXE) files.
0x0002 Image only. This indicates that the image file is valid and can be run. If this flag is not set, it indicates a linker error.
0x0004 COFF line numbers have been removed. This flag is deprecated and should be zero.
0x0008 COFF symbol table entries for local symbols have been removed. This flag is deprecated and should be zero.
After setting/reseting and testing every flag I found out that UEFI does not like the 0x0001 flag, because it would mean that it has to load the code at the prefered address. My code does not contain any relocations, but it works fine when I just manually remove the 0x0001 flag. Sidenote: objcopy also sets two deprecated flags (0x0004 and 0x0008), and I would also like that to not happen.
Is there any way objcopy can set the PE header Characteristics flags to only have 0x0002 set, and 0x0001, 0x0004 and 0x0008 be unset by objcopy directly, instead of doing it manually afterwards?
EDIT: I tried to link the file with ld, but there are some problems with that.
ld --oformat pei-x86-64 --subsystem 10 -o bootx64.efi bootx64.o
results in
ld: unrecognized option '--subsystem'
Despite GNU binutils documentation (https://sourceware.org/binutils/docs/ld/Options.html) saying that:
--subsystem which
--subsystem which:major
--subsystem which:major.minor
Specifies the subsystem under which your program will execute. The legal values for which are native, windows, console, posix, and xbox. You may optionally set the subsystem version also. Numeric values are also accepted for which. [This option is specific to the i386 PE targeted port of the linker]
So I would need an option "EFI" or 10 in numeric value. that option doesn't exist here.
I also tried pe-x86-64 and pei-i386 and everything I found with pe in the name. Linking without the subsystem option results in an almost entirely zero filled header.
Objcopy did the best job, but still not good (issues discussed above) I wrote a little utility that fills out the header correctly, so in the end I stuck with my utility.
I think you are going about compiling and linking a (U}EFI application in an unusual way. My experience is that you build a shared object (DLL or .so) and then convert it to a UEFI executable (.efi), typically using a special tool.
For example, look at http://x86asm.net/articles/uefi-programming-first-steps/, an old article about the EFI toolkit but still relevant.
fwimage
worked but was somewhat buggy.Turning to a more modern EFI build environment, i.e. gnu-efi:
gnu-efi
is not without its problems and critics. For example, see https://dvdhrm.github.io/2019/01/31/goodbye-gnuefi/For final example, look at how EDKII builds a .efi using
genfw
:The
genfw
tool is described here: https://edk2-docs.gitbook.io/edk-ii-basetools-user-guides/genfw