'Linking EFI application with LLVM on MacOS

I'm attempting to cross-compile and link a very simple EFI application that is using the EFI headers from the Zircon kernel. Despite my best efforts, I am unable to link a working PE executable under macOS Montery (with apple silicon), due to the LLD flag -subsystem:efi_application not being valid. The full error is:

FAILED: test.efi: && /opt/local/bin/clang -target x86_64-none-elf -ffreestanding -nostdlib -fuse-ld=lld -dll -WX -Wl,-subsystem:efi_application -Wl,entry:efi_main src/main.c.obj -o test.efi   && :
ld.lld: error: unknown argument '-subsystem:efi_application'

Several guides indicate that lld-link is required to compile this correctly, however adding -fuse-ld=lld-link results in errors stating:

clang: error: invalid linker name in argument '-fuse-ld=lld-link'

This occurs despite the fact that I have lld-link in my $PATH. If I, instead, pass the full lld-link path to -fuse-ld=, I get the following error:

FAILED: test.efi: && /opt/local/bin/clang -target x86_64-none-elf -ffreestanding -target x86_64-none-elf -nostdlib -dll -WX -Wl,-subsystem:efi_application -Wl,-entry:efi_main -fuse-ld=/opt/local/bin/lld-link src/main.c.obj -o test.efi   && :
lld-link: warning: ignoring unknown argument '--eh-frame-hdr'
lld-link: warning: ignoring unknown argument '-m'
lld-link: warning: ignoring unknown argument '-dynamic-linker'
lld-link: warning: ignoring unknown argument '-o'
lld-link: warning: ignoring unknown argument '-L/opt/local/libexec/llvm-13/bin/../lib'
lld-link: warning: ignoring unknown argument '-L/usr/lib'
lld-link: error: could not open 'elf_x86_64': No such file or directory
lld-link: error: could not open '/lib64/ld-linux-x86-64.so.2': No such file or directory
lld-link: error: could not open 'test.efi': No such file or directory
lld-link: error: src/main.c.obj: unknown file type
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I am using the MacPorts LLVM-13 package. The result of $ lld-link --version is LLD 13.0.0.


For reference, the code I'm attempting to compile and link is, simply:

#include "efi/protocol/graphics-output.h"
#include "efi/protocol/loaded-image.h"
#include "efi/system-table.h"
#include "efi/types.h"

#define ERR(x) if (EFI_ERROR((x))) { return (x); }

efi_status efi_main(efi_handle handle __attribute__((unused)), efi_system_table* st) {
  efi_status status;

  /* clear the screen */
  status = st->ConOut->ClearScreen(st->ConOut);
  ERR(status);

  /* print 'Hello World' */
  status = st->ConOut->OutputString(st->ConOut, u"Hello World");
  ERR(status);

  return EFI_SUCCESS;
}

What do I need to change above in order to build an EFI application using LLVM on macOS?

I'd also be curious to know any ideas as to why -fuse-ld=lld-link would fail when it exists in my PATH, and why using -fuse-ld=/opt/local/bin/lld-link would result in other implicit linker flags not succeeding.


System Details:

  • Compiler Suite: LLVM-13 (Macport Installation)
  • Host OS: macOS Monterey (Apple Silicon, M1 Pro)
  • Target Arch: x86_64
  • Build System: CMake using custom toolchain (which passes the -nostdlib, -target ... args, etc.)


Solution 1:[1]

You can try to use clang-cl to compile your code, using /c option to compile only: do not link yet. Use /Fo option to specify the output object file.
The clang-cl offers compatibility options from MSVC cl.exe. For further details, run clang-cl --help. The build target of clang-cl is default to windows, so you don't have to reset the build target in order to compile for EFI applications.
Be warned that clang-cl does not have full support to MSVC intrinsics. Intrinsics like __vmx_vmptrld, __svm_vmload, etc. are broken. clang-cl would generate a call instruction rather than the specific instruction. Define them in inline assembly in GCC's syntax, if you want to use these intrinsics.
After you compiled all source files into object files, run lld-link to link all output object files with /SUBSYSTEM:EFI_APPLICATION parameter into an EFI application.

I haven't used the header files from Zircon yet, but I tried compiling EDK II libraries with LLVM into .lib files on Windows.
With the help of these .lib files, I made some simple EFI applications. For instance, you can check this partition enumerator out.
The batch scripts might not work on MacOS, but, by reading them, you can definitely figure out the general ideas about how to specify the parameters.
If you want to use EDK II by virtue of its rich libraries, you might want to install Netwide Assembler as well, in that the assembly codes in EDK II libraries are NASM.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zero Tang