So I’ve just realized why I haven’t been able to get any of the EFI System Table BootServices functions to work in my own code -
I didn’t really understand the underlying framework all that well until today, but most of the applications provided with the TianoCore EFI Toolkit are written using some kind of C Std Lib compatibility layer to allow things like passing argc and argv to main(), which is not a standard part of the EFI specification.
To do so, the MAKE files specify the entry point to the linker as _LIBC_Start_A which I’m guessing is defined way down in the C libraries. Don’t know much else about it because I don’t really care to know - to write a pure EFI application, the entry point here needs changed to your main() function so that you can receive the EFI ImageHandle and SystemTable parameters as described in the EFI specification instead of the _LIBC stepping on them.
So, to be fair to the developers, some of the complaints above are ‘correct’ in that they’re written with the assumption that this abstraction layer providing the C Std Lib will always be available. That said, the code is still written like garbage and this C library blows the binary size up seven times larger than just following the EFI implementation and using the standardized EFI system calls.
That too, but my goal here is that once I get this working I’ll have an EFI partition that can be portable to any system, to boot an arbitrary image in RAM from anything that can be detected as a storage device. No Linux kernel, etc.
And that’s some fast registered 2R DDR3 too. If they’re still there tomorrow night when I get paid I’mma buy eight sticks of that for the old server board.
Unfortunately I’d need 32 sticks of it for this system and going from Rocket Lake to Ivy Bridge would be a bit rough lol
Buckle up, there ain’t no strings where we’re going.
No argc/*argv[] either, the EFI shell gives you a null-terminated string of the command line that was used to call the program and it’s yours from there.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Populates args, argc, and argv with arguments extracted from command line. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
EFI_STATUS parseCommandLine(
CHAR16 * command,
UINTN commandSize,
CHAR16 ** args,
UINTN * argc,
CHAR16 *** argv)
{
UINTN position;
position = 0;
*argc = 0;
if (*args != NULL)
{
FreePool(*args);
}
if (*argv != NULL)
{
FreePool(*argv);
}
// Absorb leading whitespace.
while (command[position] == L' ')
{
position = position + 1;
}
// Check for no arguments.
if (command[position] == L'\0')
{
return EFI_INVALID_PARAMETER;
}
*args = AllocateZeroPool(commandSize);
if (*args == NULL)
{
return EFI_OUT_OF_RESOURCES;
}
// Copy command line to string buffer.
StrCpy(*args, command);
// Parse arguments until NULL terminator is reached.
while ((*args)[position] != L'\0')
{
// Absorb whitespace.
while ((*args)[position] == L' ')
{
position = position + 1;
}
// Check for end of command line string.
if ((*args)[position] != L'\0')
{
// Allocate new pointer for argument.
if (*argc == 0)
{
*argv = AllocateZeroPool(sizeof(CHAR16*));
}
else
{
ReallocatePool(*argv, *argc * sizeof(CHAR16*), (*argc + 1) * sizeof(CHAR16*));
}
if (argv == NULL)
{
FreePool(*args);
return EFI_OUT_OF_RESOURCES;
}
// Update pointer, increment count.
(*argv)[*argc] = *args + position;
*argc = *argc + 1;
// Absorb argument.
while (((*args)[position] != L' ') && ((*args)[position] != L'\0'))
{
position = position + 1;
}
// Null terminate argument, move to next.
if ((*args)[position] != L'\0')
{
(*args)[position] = L'\0';
position = position + 1;
}
}
}
// Done, return.
return EFI_SUCCESS;
}
If I had any idea what I was doing when I got into this, it wouldn’t have taken a month, but at this point I’m pretty confident in writing basically anything I could ever need to run in the EFI environment pre-boot.
Same lol, 90% of the actual work is done now and I just need to script it all together now.
I predict I’m going to have a whole new issue if I can’t get Windows to recognize the EFI disk driver, but we’ll deal with that when we come to it.
Honestly wouldn’t still be plugging away at this a month later if I’d just used the C Std Library that came with the TianoCore toolkit, but I’d just end up not really understanding how the standard EFI protocols work anyway if I just abstract it away like that. It’s a learning experience.
I was surprised, UEFI networking is surprisingly easy to do with the amount of built-in protocol support that already exists. I wouldn’t want to be the one tasked with making a graphical browser that does CSS rendering/etc, but to just fetch a BIOS file from an address actually isn’t all that tough at least.
Welp, time to rewrite part of this. A thought occurred, I wrote this tool to use the EFI shell device name mapping (BLK0, BLK1, FS0, etc) to identify the source/destination. Each of these is stored in an environment variable with the corresponding hardware device path.
Well, I wasn’t thinking about these not being static, and when you boot this thing with a USB drive that wasn’t in there before, BLK4 might become BLK5 and you’ve just accidentally nuked one random storage device with another. Woops.
Sooo it’s a pain in the ass to type out but it’s using the device GUID now which is guarenteed to be static and unique.