Page 1 of 1

Try to load and start 'EFI\Boot\bootx64.efi' from UEFI code

Posted: Wed Jul 12, 2023 8:50 am
by DeadBrother
Good day, dear colleagues! I am having the following problem.

I'm trying to launch the OS from my own DXE-module through efi-loader. My DXE-module starts at the moment BSD-phase, when READY_TO_BOOT events is occurred. I correctly get the path to the efi-loader (in fact, I manually select this file from the file browser implemented in DXE-module so that the path is exactly correct):

Code: Select all

PciRoot(0x0)/Pci(0x17,0x0)/Pci(0x0,0x0)/NVMe(0x2,00-00-00-00-00-00-00-00)/HD(1,GPT,0DDBB405-D540-4260-90F4-DDB764D58459,0x800,0x64000)/\EFI\Microsoft\Boot\bootmgfw.efi
Then I call the LoadImage() function and get file handler:

Code: Select all

bs->LoadImage(TRUE, imageHandle, path_to_efi_loader, NULL, NULL, &fileHandler);
Then I call the StartImage() function:

Code: Select all

bs->StartImage(fileHandler, &exitSize, &exitData)
However, after launching the efi-loader, an attempt is made to start the Windows OS and an error window is displayed:
Windows recovery screen
Windows recovery screen
Those, the path to the efi-loader is passed correctly, the loading and launch is performed correctly, but is there some error in the launch environment?

I tried first to load the efi-loader into memory by passing the buffer to it in LoadImage(). Also, between LoadImage() and StartImage(), I tried to get EFI_LOADED_IMAGE_PROTOCOL for the efi-loader and reset its ParentHandle.

It didn't help either.

What could be the problem here?

Thank you!

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Wed Jul 12, 2023 6:36 pm
by Octocontrabass
DeadBrother wrote: \EFI\Boot\BOOTX64.efi
That's not a complete device path. Where's the device?

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Thu Jul 13, 2023 9:18 pm
by zaval
Exactly like Octacontrabass says, you flattered to yourself a bit, when told, your DXE module file browser (?!!) has chosen Device Path correctly. It apparently hasn't. What you show - efi\boot\bootx64.efi is only a Filesystem part of the Device Path. It's not even a relative one. it's Type 4, Subtype 4 node(s). You need to pass the entire Device Path to LoadImage(). If your DXE module file manager gets a relative paths (those very useful for Load Options ones, that start with 4.1 HD() nodes), then you should resolve it to find the full path. The key here is the HD() node - it is present in both, the relative path starts with it, the absolute DP, enumerated by UEFI ends with it. You enumerate Block IO devices (LocateHandleBuffer()), open Block IO Protocol on them, filter out non-logical partitions (BIOP->Media->LogicalPartition is true for logical partitions), then open Device Path protocol on every handle, that is a logical partition and look for your HD() node in logical partitions' DPs. In case of MBR, you have to match signature and start, in case of GPT only signature (UniquePartitionGUID) is enough. This way, you'll find the full DP enumerated by UEFI, where your Filesystem part does reside.

PS. and as always. The note on the abusing efi\boot\bootXXXX.efi. using the last resort path (this efi\boot\bootXXXX.efi) is an awful idea. for non-removable devices, the firmware isn't even obligated to check it. but even for removable devices, it should be checked if only nothing else in the Load Option list worked out. However, admittedly, there are moronic Boot Managers, breaking both the specification and common sense. they allow one to load from, say USB stick, using only this poor efi\boot\bootXXXX.efi way. Xiaomi laptops are an example. :evil:

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Fri Jul 14, 2023 9:27 am
by nullplan
zaval wrote:PS. and as always. The note on the abusing efi\boot\bootXXXX.efi. using the last resort path (this efi\boot\bootXXXX.efi) is an awful idea. for non-removable devices, the firmware isn't even obligated to check it. but even for removable devices, it should be checked if only nothing else in the Load Option list worked out. However, admittedly, there are moronic Boot Managers, breaking both the specification and common sense. they allow one to load from, say USB stick, using only this poor efi\boot\bootXXXX.efi way. Xiaomi laptops are an example. :evil:
Yeah, unfortunately, a lot of UEFI vendors do what BIOS vendors used to do: If Windows boots, they call wrap-up. So the only names they ever check are bootXXX.efi and the name Windows uses.

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Fri Jul 14, 2023 6:32 pm
by zaval
nullplan wrote:
zaval wrote:PS. and as always. The note on the abusing efi\boot\bootXXXX.efi. using the last resort path (this efi\boot\bootXXXX.efi) is an awful idea. for non-removable devices, the firmware isn't even obligated to check it. but even for removable devices, it should be checked if only nothing else in the Load Option list worked out. However, admittedly, there are moronic Boot Managers, breaking both the specification and common sense. they allow one to load from, say USB stick, using only this poor efi\boot\bootXXXX.efi way. Xiaomi laptops are an example. :evil:
Yeah, unfortunately, a lot of UEFI vendors do what BIOS vendors used to do: If Windows boots, they call wrap-up. So the only names they ever check are bootXXX.efi and the name Windows uses.
For me, it looks more like some extraterrestrial logic of the vendors. Windows, btw, also has to duplicate their loader in the installation medium into \efi\boot\bootXXXX.efi to fit this martian approach. Whereas such a nice option as "load from file" would be as much an effort but so much more convenient, logical and in line with the specification.

But I came to clarify on my previous post. I meant searching for the full Device Path in that unspecified DXE module file browser of the OP. When you say:
in fact, I manually select this file from the file browser implemented in DXE-module so that the path is exactly correct): \EFI\Boot\BOOTX64.efi
then this could mean only that you had enumerated all Block IO (or, just Simple File System in case of FAT) partitions and then traversed through their filesystem tree, finding the file path of your interest. Correct? Then, you have to pass to LoadImage() the device path (instance) of the same (BIOP/SFSP) handle with the concatenated to it FilePath part, you've just found in your file browser. concatenation can be done, using EFI_DEVICE_PATH_UTILITIES_PROTOCOL (of course, if it's present :D). 4.4 nodes don't support SFSP/BIOP, 4.1 do. Admittedly, the LoadImage() description is silent on whether it can load, using relative DPs, those, starting with 4.1 nodes - HD()\efi\yourcompany\yourimage.efi that is, you can check it though, but it definitely can load from full paths, which you will have on the BIOP/SFSP devices enumeration inside of your miraculous DXE module.
Hopefully, it's clearer, than the LoadImage() description, :D

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Fri Jul 14, 2023 6:40 pm
by DeadBrother
Octocontrabass wrote:
DeadBrother wrote: \EFI\Boot\BOOTX64.efi
That's not a complete device path. Where's the device?
Octocontrabass, I apologize for the inaccuracy.

The full path is as follows:

Code: Select all

PciRoot(0x0)/Pci(0x17,0x0)/Pci(0x0,0x0)/NVMe(0x2,00-00-00-00-00-00-00-00)/HD(1,GPT,0DDBB405-D540-4260-90F4-DDB764D58459,0x800,0x64000)/\EFI\Microsoft\Boot\bootmgfw.efi
(I corrected the path in the first post)

The path is obtained using the function FileDevicePath() for volume handler and relative file path.
The string representation of a path is obtained using function ConvertDevicePathToText() for EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Fri Jul 14, 2023 6:50 pm
by DeadBrother
zaval wrote:Exactly like Octacontrabass says, you flattered to yourself a bit, when told, your DXE module file browser (?!!) has chosen Device Path correctly. It apparently hasn't. What you show - efi\boot\bootx64.efi is only a Filesystem part of the Device Path. It's not even a relative one. it's Type 4, Subtype 4 node(s). You need to pass the entire Device Path to LoadImage(). If your DXE module file manager gets a relative paths (those very useful for Load Options ones, that start with 4.1 HD() nodes), then you should resolve it to find the full path. The key here is the HD() node - it is present in both, the relative path starts with it, the absolute DP, enumerated by UEFI ends with it. You enumerate Block IO devices (LocateHandleBuffer()), open Block IO Protocol on them, filter out non-logical partitions (BIOP->Media->LogicalPartition is true for logical partitions), then open Device Path protocol on every handle, that is a logical partition and look for your HD() node in logical partitions' DPs. In case of MBR, you have to match signature and start, in case of GPT only signature (UniquePartitionGUID) is enough. This way, you'll find the full DP enumerated by UEFI, where your Filesystem part does reside.

PS. and as always. The note on the abusing efi\boot\bootXXXX.efi. using the last resort path (this efi\boot\bootXXXX.efi) is an awful idea. for non-removable devices, the firmware isn't even obligated to check it. but even for removable devices, it should be checked if only nothing else in the Load Option list worked out. However, admittedly, there are moronic Boot Managers, breaking both the specification and common sense. they allow one to load from, say USB stick, using only this poor efi\boot\bootXXXX.efi way. Xiaomi laptops are an example. :evil:
zaval, thanks for your comment.

I corrected the inaccuracy and gave the full path to the EFI-application:

Code: Select all

PciRoot(0x0)/Pci(0x17,0x0)/Pci(0x0,0x0)/NVMe(0x2,00-00-00-00-00-00-00-00)/HD(1,GPT,0DDBB405-D540-4260-90F4-DDB764D58459,0x800,0x64000)/\EFI\Microsoft\Boot\bootmgfw.efi
Also, the function LoadImage() for this path does not return a error EFI_NOT_FOUND or other.
And the function StartImage() tried to start this EFI-application.
Thus, if I understand correctly - the EFI-file full path is correct, but an error occurs (see screenshot above) already in the process of execution.

p.s.
By file browser, I mean my own implemented functionality, which allows you to view files in all sections in text mode and select one of them. As a result, the browser returns the volume handle (obtained by the enum for EFI_SIMPLE_FILE_SYSTEM_PROTOCOL) and the relative path to the file.

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Mon Jul 17, 2023 10:09 am
by Octocontrabass
Does it work if you use the firmware's boot manager instead of your DXE driver?

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Mon Jul 17, 2023 10:22 am
by DeadBrother
Octocontrabass wrote:Does it work if you use the firmware's boot manager instead of your DXE driver?
Yes, sure.
If I exit from the DXE-driver (without call LoadImage() and StartImage(), of course), then the OS boot normally.

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Mon Jul 17, 2023 12:18 pm
by Octocontrabass
Huh, I'm not sure what else it could be. You might have to spend some time in a debugger to figure out where the difference is.

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Mon Jul 17, 2023 12:56 pm
by DeadBrother
Octocontrabass wrote:Huh, I'm not sure what else it could be. You might have to spend some time in a debugger to figure out where the difference is.
Octocontrabass, can you tell me what suitable debugging method can be applied - for VMWare and bootmgfw.efi?

And do I understand correctly that the actions that I am doing (LoadImage, StartImage, manual run bootmgfw.efi app instead standart boot loader usage) are correct and they should lead to OS loading?

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Mon Jul 17, 2023 1:20 pm
by Octocontrabass
DeadBrother wrote:Octocontrabass, can you tell me what suitable debugging method can be applied - for VMWare and bootmgfw.efi?
I believe VMWare has a GDB stub, but I've never used it. I have used QEMU with GDB to debug UEFI programs, though.
DeadBrother wrote:And do I understand correctly that the actions that I am doing (LoadImage, StartImage, manual run bootmgfw.efi app instead standart boot loader usage) are correct and they should lead to OS loading?
Yes, as far as I can tell that's correct. Maybe comparing what you're doing against what EDK2 does will help.

Re: Try to load and start 'EFI\Boot\bootx64.efi' from UEFI c

Posted: Sun Jul 23, 2023 4:32 pm
by zaval
I see. OS Loaders make use information in the associated Load Options (LOs), Windows Boot Manager does so as well. Tell me, when you load it, do you fill in LoadOptions pointer (inside of the ImageHandle's EFI_LOADED_IMAGE_PROTOCOL, LIP, instance) before starting the image?
The caller may fill in the image’s “load options” data, or add additional protocol support to the handle before passing control to the newly loaded image by calling StartImage().
Basically, you would need to take BootXXXX variable contents and pass its OptionalData address to bootmgr.efi. Because you load OSL instead of UEFI Boot Manager, you would need to mimick the process as if bootmgr.efi was picked during the Load Option processing, basically, you need to find what BootXXXX is related to the bootmgr.efi. For example, by enumerating BootXXXX and finding among them the one, having the same Device Path in the FilePathList[0], but remember, it almost certainly would be a relative one, thus starting with the 4.1 node, like HD()\<FilePath>. Then you should pass the pointer to OptionalData part of the LO via the LIP->LoadOptions field. Don't forget to fill in LoadOptionsSize field too. Useful quote from the spec (Boot Manager, Load Options):
OptionalData
The remaining bytes in the load option descriptor are a binary
data buffer that is passed to the loaded image. If the field is zero
bytes long, a NULL pointer is passed to the loaded image. The
number of bytes in OptionalData can be computed by
subtracting the starting offset of OptionalData from total size
in bytes of the EFI_LOAD_OPTION.
I haven't used LoadImage() and may be wrong, but I think, the sequence should be like this:
1. you call LoadImage.
2. On success, you open EFI_LOADED_IMAGE_PROTOCOL (LIP) on the ImageHandle
3. If its LoadOptions field is NULL and LoadOptionsSize is 0, then we got it right, so you proceed. If its not NULL, print it to see, what's there. have no clue what it can be there. because LoadImage() has nothing to write there.
4. Find Windows bootmgr.efi Load Option (BootXXXX) by enumerating these vars and searching in their FilePathList[0] for HD() node with exactly the same identifiers (UniquePartitionGUID for GPT, DiskId and Start for MBR)
5. On finding this LO and you already have its contents read as a buffer, calculate the address of its OptionalData part and pass it via LIP->LoadOptions, calculate its Size and fill in LIP->LoadOptionsSize as well.

ALSO, you might need to set BootCurrent variable with the index of BootXXXX, that corresponds to bootmgr.efi, since bootmgr.efi could use this mechanism to get its data. As we can see, there are many ways of how OSLs can be started and this complicates for them using persistently stored (in LO) information. Also, there are OSL launch scenarios, where the OS is not installed yet and where it is.
As an example. My loader distinguishes Preinstallation run and Postinstallation one, by reading the BootCurrent and analyzing contents of the corresponding BootXXXX variable. PreInst won't have any info set up by us in it, so we know, we are started not through LO processing, but manually - "Load From File" or via that poor efi\boot\bootxxxx.efi or whatever, but normal way and this is exactly the way of the preinstallation phase. If BootCurrent points to the LO, containing what we expect it to contain, then it's PostInstall, so we safely can read the needed info. I use FilePathList[1] for storing OS BootVolume HD() node (4.1) and System Root Directory (4.4). This is needed for finding where the OS is installed (I don't use my 2-part loader with my own "Boot Manager" as Windows does), but also - it allows to distinguish between PreInst and PostInst phases and serves as a marker of the LO, being ours (set by Installer). It's just an example to demonstrate, OSL needs to handle different scenarios of its launch. It is not brilliant, since, if a user launches our loader via, say, UEFI CLI, our loader would think it is PreInst or Live session and most probably will fail or even worse - would start the OS from the "setup" directory, that might screw up the already installed OS. Unlikely, but not too satisfying...

Windows doesn't use FPL[1], but it makes use OptionalData Load Option container (see the image below), storing there some BCD related stuff, basically, text represented GUID like BCD_OBJECT={<Guid here>}. I don't know, how bootmgr.efi handles this variety in the ways of starting it, but it apparently doesn't fail, when launched manually via UEFI Boot Manager or CLI, so, obviously, it uses some of the above - BootCurrent (may use it in PostInstall), LIP LoadOptions pointer. In the PreInst case, when started from UEFI CLI, LIP->LoadOptions points to the command line, you typed, when picking your .efi file. But bootmgr.efi doesn't fail in this case, doesn't it? Maybe it fails if LoadOptions is NULL?
1. If it's PostInstall, LoadOptions would have the contents of LO->OptionalData, set by the Windows Installer, apparently, it uses it.
2. If it's PreInstall, LoadOptions are not NULL and are filled with something, for example, as said above, for the CLI, it would be command like, so at least the image name would be there, kind of argv[0].
3. If it's a plain LoadImage() call, like you did, LoadOptions is NULL and then bootmgr.efi fails.

Try to pass to bootmgr.efi via LIP->LoadOptions the contents of its BootXXXX OptionalData buffer (1) or, at least its name (2). I feel, it will get healed. :)
You have touched a quite complex thing.

Image