Page 1 of 1

EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 12:59 pm
by anatolik
Hello, I am implementing a simple UEFI bootloader. Most EFI functions that I tried work me - I can clear Console, print text, successfully read files from system partition, allocate memory pages.

But if I try to read memory map and then call ExitBootServices() QEMU crashes for me:
qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000b0000

Here is my test-case program that uses GNU-EFI library

Code: Select all

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

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SysTab) {
    InitializeLib(ImageHandle, SysTab);

    uint8_t buffer[4900];
    UINTN msize = sizeof(buffer);
    UINTN mkey = 0, dsize = 0;
    UINT32 dversion;
    SysTab->BootServices->GetMemoryMap(&msize, (EFI_MEMORY_DESCRIPTOR*)buffer, &mkey, &dsize, &dversion);

    EFI_STATUS st = SysTab->BootServices->ExitBootServices(ImageHandle, mkey);
    // QEMU crashes here ^^^^^^^^^^^^^^^^^^^^^^^^^^
    // qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000b0000

    Print(L"Press any key: %d\n", st);

    UINTN index;
    EFI_EVENT event = SysTab->ConIn->WaitForKey;
    SysTab->BootServices->WaitForEvent(1, &event, &index);

    return EFI_SUCCESS;
}

Here is a Makefile for it

Code: Select all

ARCH            = x86_64

OBJS            = bootloader.o
TARGET          = bootloader.efi

EFIINC          = /usr/include/efi
EFIINCS         = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
EFILIB          = /usr/lib
EFI_CRT_OBJS    = $(EFILIB)/crt0-efi-$(ARCH).o
EFI_LDS         = $(EFILIB)/elf_$(ARCH)_efi.lds

CFLAGS          = $(EFIINCS) -fno-stack-protector -fPIC -fshort-wchar -mno-red-zone -Wall -std=c11

ifeq ($(ARCH),x86_64)
  CFLAGS += -DHAVE_USE_MS_ABI
endif

LDFLAGS         = -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L $(EFILIB) $(EFI_CRT_OBJS) 

all: $(TARGET)

bootloader.so: $(OBJS)
	ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi

%.efi: %.so
	objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym  -j .rel -j .rela -j .reloc --target=efi-app-$(ARCH) $^ $@

hda/EFI/BOOT/BOOTX64.EFI: bootloader.efi
	mkdir -p hda/EFI/BOOT/
	cp bootloader.efi hda/EFI/BOOT/BOOTX64.EFI

run: hda/EFI/BOOT/BOOTX64.EFI
	qemu-system-x86_64 --bios OVMF_CODE.fd -hda fat:rw:hda -net none


I think I tried everything: GCC vs clang, compiling/running at Arch vs Debian, tried different OVMF version (from Arch, Debian, Fedora). The crash is still here. I am staring at the code and do not see any problems with it.

Does anybody have a clue what is going here and why ExitBootServices() crashes?

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 1:59 pm
by anatolik
I also tried to use AllocatedPool instead of static array but result is the same:

Code: Select all

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

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SysTab) {
    InitializeLib(ImageHandle, SysTab);

    UINTN mkey = 0, dsize = 0;
    UINT32 dversion = 0;
    UINTN msize = 32768;
    EFI_MEMORY_DESCRIPTOR *mmap = AllocatePool(msize);
    SysTab->BootServices->GetMemoryMap(&msize, mmap, &mkey, &dsize, &dversion);

    EFI_STATUS st = SysTab->BootServices->ExitBootServices(ImageHandle, mkey);
    // QEMU crashes here ^^^^^^^^^^^^^^^^^^^^^^^^^^
    // qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000b0000

    FreePool(mmap);
    Print(L"Press any key: %d\n", st);

    UINTN index;
    EFI_EVENT event = SysTab->ConIn->WaitForKey;
    SysTab->BootServices->WaitForEvent(1, &event, &index);

    return EFI_SUCCESS;
}
I also tried to debug this crash and enabled QEMU cpu debugging. It looks like at some point IP jumps to address 0 and start executing it. Begginning of the address space is filled with zeros and x86 emulator executes as valid instructsions ('addl' if I remember).

But the it reaches 0x00000000000b0000 that is some kind of system memory (video?) and value of this memory cell is invalid instruction that crashes QEMU.

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 3:32 pm
by zaval
You cannot use Boot Services after ExitBootServices(). Like here:

Code: Select all

   EFI_STATUS st = SysTab->BootServices->ExitBootServices(ImageHandle, mkey);
    // QEMU crashes here ^^^^^^^^^^^^^^^^^^^^^^^^^^
    // qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000b0000

    FreePool(mmap);
    Print(L"Press any key: %d\n", st);

    UINTN index;
    EFI_EVENT event = SysTab->ConIn->WaitForKey;
    SysTab->BootServices->WaitForEvent(1, &event, &index);

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 3:35 pm
by anatolik
Here is simplified version of the same code. The issue still exists

Code: Select all

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

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SysTab) {
    InitializeLib(ImageHandle, SysTab);

    UINTN mkey = 0, dsize = 0;
    UINT32 dversion = 0;
    UINTN msize = 32768;
    EFI_MEMORY_DESCRIPTOR *mmap = AllocatePool(msize);
    if (!mmap) {
        Print(L"AllocatePool failed\n");
        goto error;
    }

    SysTab->BootServices->GetMemoryMap(&msize, mmap, &mkey, &dsize, &dversion);

    EFI_STATUS st = SysTab->BootServices->ExitBootServices(ImageHandle, mkey);
    // QEMU crashes here ^^^^^^^^^^^^^^^^^^^^^^^^^^
    // qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000b0000
error:
    return EFI_SUCCESS;
}

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 3:52 pm
by zaval
Does GetMemoryMap return success?

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 4:07 pm
by anatolik
zaval wrote:Does GetMemoryMap return success?
Yes it does. And the memory descriptors info seems fine. Though it does not really matter. In case if GetMemoryMap() is unsuccessfull, subsequent ExitBootServices() should return INVALID_PARAMETER error. But definitely not crash.

I think I tried every possible combination with the code. And now I think the problem either in my CFLAGS/LDFLAGS or something seriously wrong with OVMF firmware. Are there any alternative UEFI firmware blobs that I can try instead of OVMF?

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 4:35 pm
by zaval
anatolik wrote:
zaval wrote:Does GetMemoryMap return success?
Yes it does. And the memory descriptors info seems fine. Though it does not really matter. In case if GetMemoryMap() is unsuccessfull, subsequent ExitBootServices() should return INVALID_PARAMETER error. But definitely not crash.

I think I tried every possible combination with the code. And now I think the problem either in my CFLAGS/LDFLAGS or something seriously wrong with OVMF firmware. Are there any alternative UEFI firmware blobs that I can try instead of OVMF?
Here, I only can suggest you to report a problem on their mailing list ([email protected]). They answer.

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Tue Mar 13, 2018 10:00 pm
by Brendan
Hi,
anatolik wrote:Yes it does. And the memory descriptors info seems fine. Though it does not really matter. In case if GetMemoryMap() is unsuccessfull, subsequent ExitBootServices() should return INVALID_PARAMETER error. But definitely not crash.
Would you mind compiling without "-fno-stack-protector" or "-mno-red-zone" to see if that works?

I'm thinking that maybe something (e.g. the "-fno-stack-protector" or "-mno-red-zone") causes some kind of quirk in GNU's tools that results in the code using calling conventions that are different to what UEFI expects. Note that UEFI expects "Microsoft x64" calling conventions while GNU prefers "System V AMD64 ABI", and I'd assume that the "$(EFIINCS)" takes care of setting up the calling conventions to suit UEFI/Microsoft.


Cheers,

Brendan

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Thu Mar 15, 2018 11:35 am
by anatolik
Alright I think I figured out the problem
You cannot use Boot Services after ExitBootServices()
I had to think on it one more time. I also checked the function docs and I see it say
On success, several fields of the EFI System Table should be set to NULL.
Setting NULL to a function pointer and later trying to call it sounds exactly like my issue.

But in my simplified example above I did not call any UEFI function after ExitBootServices(), I just returned exit code. Oh wait, what happens *after* I return from efi_main()? What if UEFI Shell tries to use BootServices?

So I added an infinite loop (I cannot use printing for debug purposes) and then attached to the app via GDB and indeed, my app called ExitBootServices() successfully without any crash. Aha, so it is UEFI Shell that crashes. It means once I called ExitBootServices() I cannot do print, I cannot return to shell, I cannot do pretty much anything except just jumping to my OS code. So I added "jmp" and now I see that execution flow reaches my OS startup code correctly.

Re: EFI ExitBootServices() function crashes in QEMU

Posted: Thu Mar 15, 2018 12:35 pm
by zaval
It means once I called ExitBootServices() I cannot do print, I cannot return to shell, I cannot do pretty much anything except just jumping to my OS code. So I added "jmp" and now I see that execution flow reaches my OS startup code correctly.
Yes, it is supposed to be that way - ExitBootServices() is a no return to FW and jump into your OS loader/OS realm. Good to hear you resolved the problem.

Online resources are good, but don't forget - your main source is the freely available UEFI specification. Not that it's the best reading all over the world, but it is the ultimate documentation and is helpful. And it's yet not as bloated as could be! :lol: