Sorry for hijacking this old thread, but I'd like to shed some light about what works and what does not (and ask if you have any ideas to improve it).
- Unless you want to use Secure Boot, it does not matter if your BOOT_APPLICATION is signed or not - just /set nointegritychecks Yes (on your boot entry, not on {bootmgr}). For your BOOT_APPLICATION bcd entry you can /copy {memdiag} and change the path.
- As others already pointed out, your application needs to use /SUBSYSTEM:BOOT_APPLICATION. You can use Visual Studio 2015 (Community) to compile for this subsystem: while the value does not exist in the subsystem list, you can just set the subsystem to unset and add /SUBSYSTEM:BOOT_APPLICATION as custom linker option. If you want a template how to link against GNU EFI llibrary with Visual Studio, you can use uefi-simple as a template (In case you want to run and test it in QEMU from Visual Studio, you will have to tweak debug.vbs so that it copies the Windows bootloader and a pre-built BCD into the boot image and change the destination file name inside the boot image so that bootx64.efi is not overwritten with your binary).
- The entry point of the BOOT_APPLICATION takes one parameter of type PBOOT_APPLICATION_PARAMETER_BLOCK (The folks of the Reactos project have reverse engineered this struct and many related ones) and its return value is a normal NTSTATUS/EFI_STATUS. Apart from the signature ("BOOT APP") and version (2), the BOOT_APPLICATION_PARAMETER_BLOCK struct mainly contains offsets (in bytes from the start of the struct) to the BL_FIRMWARE_DESCRIPTOR (which has a version number of 2 and include the normal ImageHandle and SystemTable variables every UEFI programmer knows) and to the BL_APPLICATION_ENTRY (which is mainly useful since it is the start of a linked list of BCD parameters that are set for the image - so you can set your own custom:0x... options with bcdedit and read the values from your BOOT_APPLICATION).
- When calling UEFI IntializeLib, don't pass the ImageHandle on first call, since it will use the ImageHandle to determine the correct memory allocation type - and it does not know what is correct for BOOT_APPLICATION. When you pass NULL, it will use EfiBootServicesData, which works fine.
And now this is where my problems start:
While input (ST->ConIn) works fine (as well as most other BootServices like serial ports or filesystem access or even shutdown), everything that tries to access the screen (i. e. GRAPHICS_OUTPUT_PROTOCOL or SIMPLE_TEXT_OUTPUT_PROTOCOL) freezes the machine instead. Probably the boot loader initialized the graphics card strangely or did some non-standard memory mapping? (After exiting from the boot application and exiting from the boot manager, it works again, so it is not permanently damaged...). I also tried to debug the boot manager with WinDbg, but that debug support seems to be limited (no way to search for ASCII strings, but searching for DWORDs works fine; breakpoints sometimes work but most of the time don't, etc.) so I gave up on this one, too.
So while it is technically possible to LoadImage and StartImage another normal EFI binary, that one will probably also freeze quickly and be of no real use.
If you want to experiment with "what works", here is a small example that can shut down the machine or dump EFI variables to the serial console:
#include <efi.h>
#include <efilib.h>
typedef struct {
CHAR8 Signature[8]; // "BOOT APP"
UINT32 Version; // 2
UINT32 Irrelevant[7];
UINT32 AppEntryOffset;
UINT32 Irrelevant2;
UINT32 FirmwareParametersOffset;
} BOOT_APPLICATION_PARAMETER_BLOCK;
typedef struct {
UINT32 Type;
UINT32 DataOffset;
UINT32 DataSize;
UINT32 ListOffset;
UINT32 NextEntryOffset;
UINT32 Empty;
} BL_BCD_OPTION;
typedef struct {
CHAR8 Signature[8]; // "BTAPENT"
EFI_GUID Guid;
UINT32 Unknown[5];
BL_BCD_OPTION BcdData;
} BL_APPLICATION_ENTRY;
typedef struct {
UINT32 Version; // 2
UINT32 Padding;
EFI_HANDLE ImageHandle;
EFI_SYSTEM_TABLE *SystemTable;
} BL_FIRMWARE_DESCRIPTOR;
void SerWrite(EFI_SERIAL_IO_PROTOCOL* ser, CHAR16* buffer) {
CHAR8 buf[100];
UINTN len = StrLen(buffer), i;
if (len > 100) len = 100;
for (i = 0; i < len; i++) {
buf[i] = (CHAR8)buffer[i];
}
ser->Write(ser, &len, buf);
}
void DumpVars(EFI_SERIAL_IO_PROTOCOL* ser, BOOT_APPLICATION_PARAMETER_BLOCK* BootAppParameters) {
BL_APPLICATION_ENTRY* appentry = (BL_APPLICATION_ENTRY*)(((UINTN)BootAppParameters) + BootAppParameters->AppEntryOffset);
BL_BCD_OPTION* option = &appentry->BcdData;
UINTN firstOption = (UINTN)option;
CHAR16 buffer[40];
CHAR8* data;
UINT32 i;
while (TRUE) {
SPrint(buffer, 40, L"%08x: ", option->Type);
SerWrite(ser, buffer);
data = (CHAR8*)(((UINTN)option) + option->DataOffset);
if ((option->Type & 0x0F000000) == 0x02000000 && option->DataSize > 0) {
SerWrite(ser, L"\"");
SerWrite(ser, (UINT16*)data);
SerWrite(ser, L"\"");
}
else if (((option->Type & 0x0F000000) == 0x03000000 && option->DataSize == 16) || ((option->Type & 0x0F000000) == 0x04000000 && option->DataSize % 16 == 0)) {
for (i = 0; i < option->DataSize; i += 16, data += 16) {
SPrint(buffer, sizeof(buffer), L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} ", *((UINT32*)data), *((UINT16*)(data + 4)), *((UINT16*)(data + 6)),
*((UINT8*)(data + 8)), *((UINT8*)(data + 9)), *((UINT8*)(data + 10)), *((UINT8*)(data + 11)),
*((UINT8*)(data + 12)), *((UINT8*)(data + 13)), *((UINT8*)(data + 14)), *((UINT8*)(data + 15)));
SerWrite(ser, buffer);
}
}
else if (((option->Type & 0x0F000000) == 0x05000000 && option->DataSize == 8) || ((option->Type & 0x0F000000) == 0x07000000 && option->DataSize % 8 == 0)) {
for (i = 0; i < option->DataSize; i += 8) {
SPrint(buffer, 40, L"%ld ", *((UINT32*)(data + i)));
SerWrite(ser, buffer);
}
}
else if ((option->Type & 0x0F000000) == 0x06000000 && option->DataSize == 2 && data[0] == 0 && data[1] == 0) {
SerWrite(ser, L"false");
}
else if ((option->Type & 0x0F000000) == 0x06000000 && option->DataSize == 2 && data[0] == 1 && data[1] == 0) {
SerWrite(ser, L"true");
}
else {
for (i = 0; i < option->DataSize; i++) {
SPrint(buffer, 40, L"%02x ", *((UINT8*)(data + i)));
SerWrite(ser, buffer);
}
}
SerWrite(ser, L"\r\n");
if (option->NextEntryOffset == 0)
return;
option = (BL_BCD_OPTION*)(firstOption + option->NextEntryOffset);
}
}
EFI_STATUS BootApplicationEntryPoint(BOOT_APPLICATION_PARAMETER_BLOCK* BootAppParameters) {
BL_FIRMWARE_DESCRIPTOR* fwdesc = (BL_FIRMWARE_DESCRIPTOR*)(((UINTN)BootAppParameters) + BootAppParameters->FirmwareParametersOffset);
EFI_HANDLE ImageHandle = fwdesc->ImageHandle;
EFI_SYSTEM_TABLE* SystemTable = fwdesc->SystemTable;
EFI_GUID serGUID = EFI_SERIAL_IO_PROTOCOL_GUID;
EFI_INPUT_KEY key;
UINTN Event;
EFI_SERIAL_IO_PROTOCOL* ser;
InitializeLib(NULL, SystemTable);
InitializeLib(ImageHandle, SystemTable);
BS->SetWatchdogTimer(0, 0, 0, NULL);
LibLocateProtocol(&serGUID, &ser);
ser->Reset(ser);
ser->SetAttributes(ser, 115200, 0, 0, DefaultParity, 8, DefaultStopBits);
CHAR16* header = L"Hello, serial!\r\nD to dump variables, R to reboot, H to halt, or X to exit\r\n\r\n";
SerWrite(ser, header);
ST->ConIn->Reset(SystemTable->ConIn, TRUE);
while (TRUE) {
BS->WaitForEvent(1, &ST->ConIn->WaitForKey, &Event);
ST->ConIn->ReadKeyStroke(ST->ConIn, &key);
switch (key.UnicodeChar) {
case 'd':
case 'D':
DumpVars(ser, BootAppParameters);
break;
case 'h':
case 'H':
RT->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
break;
case 'r':
case 'R':
RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
break;
case 'x':
case 'X':
return EFI_SUCCESS;
break;
}
}
}
In case anyone has any more information, please share :-)
mihi