2

While porting UEFI application from AARCH64 to X64 i ran into following problem: application was hanging after enabling periodic timer (timer is used in application for I/O polling). Timer callback finishes and nothing executes afterwards.

During debugging i tried to find error in code but failed to do so. Now i have very small helloworld example with following properties:

  • It was built in EDK2 according to their instructions (Ubuntu 20 PC with GCC 9.4.0);
  • It works perfectly on AARCH64 device;
  • It works perfectly in X64 QEMU;
  • It hangs on any actual X64 computer (i tested Gigabyte AM2, MSI LGA1200, Asus LGA1700 motherboards)

Is this example broken or is there a bug in AMI BIOS?

Here is the whole source code of crashing application. As you can see, it only tries to overwrite two bytes in static arrays in timer callback, however application hangs after first execution of callback. Removing any part of code (using only 1 array, inlining functions) prevents application from hanging. What was expected: spam of "Hello World", "timer begin", "timer end" messages. This is what happens in QEMU and on AARCH64 device. What was received: few "Hello World" messages, then one iteration of timer callback, after that application hangs. By default EDK2 uses "-Os" optimization which requires more complex code to crash, so this functions are explicitly have "-O0" optimization. Is there a severe bug in modern AMI BIOS implementations or there is something wrong with my code?


#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>

static volatile UINT8 test_buf1[200];
static volatile UINT8 test_buf2[200];
void* timer;

__attribute((optimize("O0")))
void assign(volatile UINT8* ptr, int ind)
{
    ptr[ind] = 10;
}

__attribute((optimize("O0")))
void test_func(void)
{
    assign(test_buf1, 0);
    assign(test_buf2, 0);
}

__attribute((optimize("O0")))
void test_interrupt(EFI_EVENT Event, VOID *Context) 
{
    DEBUG ((EFI_D_ERROR, "timer begin\r\n"));
  test_func();
    DEBUG ((EFI_D_ERROR, "timer end\r\n"));
}

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  Print(L"Hello World \n"); 

  Status = gBS->CreateEvent( EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, (EFI_EVENT_NOTIFY)test_interrupt, NULL, &timer);
    if (EFI_ERROR(Status)) {
        return -1;
    }

    Status = gBS->SetTimer( timer, TimerPeriodic, EFI_TIMER_PERIOD_MILLISECONDS (16));
    if (EFI_ERROR(Status)) {
        return -1;
    }

  while(1)
    Print(L"Hello World \n"); 

  return EFI_SUCCESS;
}

  • May be that some register, which the BIOS expects to be preserved, is instead overwritten by your code? – linuxfan says Reinstate Monica Aug 24 '23 at 05:12
  • @linuxfan says Reinstate Monica, I suspect either this or stack corruption during timer callback. However I haven't found any info about manual register preservation procedure in UEFI. Every timer tutorial I found doesn't bother saving registers, and UEFI specifications don't mention such a procedure. Maybe there is some sneaky GCC flag? During build I used default edk2 GCC flags. – user2779778 Aug 24 '23 at 05:43

1 Answers1

2

Timer callback function was not declared as EFIAPI. This attribute successfully fixed everything.

EFIAPI void test_interrupt(EFI_EVENT Event, VOID *Context) 
  • 1
    Any idea what `EFIAPI` does? Does it change the calling convention? Apparently yes, a [reddit thread](https://www.reddit.com/r/osdev/comments/oxia7q/what_is_efiapi_in_uefi_programming/) suggests that it'll be defined as `__attribute__((ms_abi))` if using [gnu-efi](https://sourceforge.net/projects/gnu-efi/). Presumably functions declared without that will use the x86-64 System V ABI's calling convention. ([Different](https://stackoverflow.com/q/4429398) arg-passing registers, and some regs are call-clobbered in that which are call-preserved in the Windows x64 calling convention EFI uses.) – Peter Cordes Aug 24 '23 at 17:35
  • I'd have expected the whole UEFI application to be compiled with `-mabi=ms` instead of mixing and matching calling conventions. Windows x64 isn't a bad calling convention in general. Anyway, I guess this happened to work in QEMU since your `test_interrupt` function wasn't using its args. If it had, it would be getting invalid pointers with the calling-convention mismatch. – Peter Cordes Aug 24 '23 at 17:40