Page 1 of 1

Issues compiling UEFI application (undefined references)

Posted: Mon Aug 28, 2023 5:49 am
by nukespace
Hey all,

I'm getting started with UEFI applications for a kernel project I'm working on and running into some issues with the first compilation. I've generally followed the UEFI app bare bones guide to a T, but I'm consistently hitting my head on some undefined reference errors in the final of three compilation steps (the linking of the UEFI main executable and data.o). The UEFI application I'm attempting to compile is a basic "Hello, world!" consisting of the following:

Code: Select all

#include <efi.h>
#include <efilib.h>

EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
    EFI_STATUS Status;
    EFI_INPUT_KEY Key;
    EFI_SYSTEM_TABLE *ST = SystemTable;
    InitializeLib(ImageHandle, SystemTable);
    Print(L"Hello, world!\r\n");

    Status = ST->ConIn->Reset(ST->ConIn, FALSE);
    if (EFI_ERROR(Status))
        return Status;
    while ((Status = ST->ConIn->ReadKeyStroke(ST->ConIn, &Key)) == EFI_NOT_READY);

    return Status;
}
Build environment is a rather new install of Ubuntu 23.04, gcc-mingw-w64 version 12.2.0, binutils-mingw-w64 version 2.39.90. I've successfully installed GNU-EFI as well as made the prescribed modifications to gnuefi/lib/data.c. The error occurs on the final leg of the three-part compilation:

Code: Select all

$ x86_64-w64-mingw32-gcc -ffreestanding -I/usr/include/efi -I/usr/include/efi/x86_64 -I/usr/include/efi/protocol -c -o uefiboot.o uefiboot.c
$ x86_64-w64-mingw32-gcc -ffreestanding -I/usr/include/efi -I/usr/include/efi/x86_64 -I/usr/include/efi/protocol -c -o data.o /usr/lib/data.c
$ x86_64-w64-mingw32-gcc -nostdlib -Wl,-dll -shared -Wl,--subsystem,10 -e efi_main -o BOOTX64.EFI uefiboot.o data.o
# ---> /usr/bin/x86_64-w64-mingw32-ld: uefiboot.o:uefiboot.c:(.text+0x24): undefined reference to `InitializeLib'
# ---> /usr/bin/x86_64-w64-mingw32-ld: uefiboot.o:uefiboot.c:(.text+0x33): undefined reference to `Print'
# ---> collect2: error: ld returned 1 exit status
First order of business was to check to ensure I got the paths right - GNU-EFI apparently installs to /usr/lib on Ubuntu, as confirmed by dpkg:

Code: Select all

$ dpkg -L gnu-efi
/.
/usr
/usr/include
/usr/include/efi
/usr/include/efi/efi.h
/usr/include/efi/efi_nii.h
/usr/include/efi/efi_pxe.h
/usr/include/efi/efiapi.h
# ...
/usr/include/efi/efiui.h
/usr/include/efi/ia32
# ...
/usr/include/efi/lib.h
/usr/include/efi/libsmbios.h
/usr/include/efi/pci22.h
/usr/include/efi/protocol
/usr/include/efi/protocol/adapterdebug.h
# ...
/usr/include/efi/protocol/vgaclass.h
/usr/include/efi/romload.h
/usr/include/efi/x86_64
# ...
/usr/include/efi/x86_64/pe.h
/usr/lib
/usr/lib/crt0-efi-x86_64.o
/usr/lib/elf_x86_64_efi.lds
/usr/lib/libefi.a
/usr/lib/libgnuefi.a
/usr/lib32
# ...
At this point, I'm a bit stumped. I can obviously tell something's going on with the linking (not including or correctly binding an EFI lib), but I'm lost on how to address the matter further. I found this thread which has a similar issue, although I'm uncertain what the resolution ultimately was or whether or not it applies to this particular route (using the MinGW cross-compiler). I tried my hand at cobbling a Makefile for the process, although this gives me a different series of errors and I'm almost certain I messed something up somewhere. I'm by no means a novice to C/C++, but I have virtually zero experience with make - I've always gotten by using built-in IDE build systems.

Code: Select all

SHELL   = /usr/bin/env zsh
.PHONY: clean

ARCH         = $(shell uname -m)
CC           = x86_64-w64-mingw32-gcc

CCFLAGS      = -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar \
               -mno-red-zone -maccumulate-outgoing-args
LDFLAGS      = -nostdlib -Wl,-dll -shared -Wl,--subsystem,10 -e efi_main
EFIINC       = /usr/include/efi
EFIINCS      = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
LIB          = /usr/lib
EFILIB       = $(LIB)
EFI_CRT_OBJS = $(EFI_LIB)/crt0-efi-$(ARCH).o
EFI_LDS      = $(EFI_LIB)/elf_$(ARCH)_efi.lds
DATALOC      = $(LIB)/data.c

all: uefiboot data bootx64

uefiboot: uefiboot.c
	$(CC) $(CCFLAGS) $(EFIINCS) -c -o uefiboot.o uefiboot.c

data: data.c
	$(CC) $(CCFLAGS) $(EFIINCS) -c -o data.o $(DATALOC)

bootx64: uefiboot.o data.o
	$(CC) $(LDFLAGS) -o BOOTX64.EFI uefiboot.o data.o

clean:
	rm -f uefiboot.o data.o BOOTX64.EFI

# Errors:
# x86_64-w64-mingw32-gcc -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -I/usr/include/efi -I/usr/include/efi/x86_64 -I/usr/include/efi/protocol -c -o uefiboot.o uefiboot.c
# make: *** No rule to make target 'data.c', needed by 'data'.  Stop.
Any ideas? As is the case 90% of the time, I'm sure it boils down to some simple oversight of mine somewhere.

Re: Issues compiling UEFI application (undefined references)

Posted: Mon Aug 28, 2023 6:16 pm
by klange
There is no "InitializeLib" or "Print" function in UEFI.

These functions are provided by gnu-efi's utility library, and the guide you followed specifically states: "This example does not link against GNU-EFI or follow its build process; only the headers are used."

Re: Issues compiling UEFI application (undefined references)

Posted: Mon Aug 28, 2023 6:46 pm
by nukespace
klange wrote:There is no "InitializeLib" or "Print" function in UEFI.

These functions are provided by gnu-efi's utility library, and the guide you followed specifically states: "This example does not link against GNU-EFI or follow its build process; only the headers are used."
Well, I expected it to be a silly mistake, but I didn't think I would manage to checkmate myself in one.

Are there any functions I can depend on the EFI headers providing, or are all going to need to be implemented via uefi_call_wrapper()? I did a quick cat of the headers and they look to be mostly types, defines, and GUIDs.

Re: Issues compiling UEFI application (undefined references)

Posted: Mon Aug 28, 2023 7:04 pm
by klange
nukespace wrote:Well, I expected it to be a silly mistake, but I didn't think I would manage to checkmate myself in one.

Are there any functions I can depend on the EFI headers providing, or are all going to need to be implemented via uefi_call_wrapper()? I did a quick cat of the headers and they look to be mostly types, defines, and GUIDs.
EFI does not provide any functions; instead you will find function pointers from the child members of the system table. You already have two that you are calling in your code: ST->ConIn->Reset and ST->ConIn->ReadKeyStroke.

Instead of reading the UEFI headers, you should read the UEFI specification which better describes what functions are available from different objects, and which ones you will likely want to use.

You do not need gnu-efi's uefi_call_wrapper, either, as you are using a mingw toolchain and all that wrapper does is provide the Microsoft calling convention that UEFI expects.