从 UEFI 引导加载程序调用单独 EFI 文件的入口点

问题描述 投票:0回答:1

我希望从使用 UEFI 的引导加载程序中调用单独的 EFI 文件中的函数,这两个文件都位于同一虚拟硬盘中。我研究了规范中定义的

LoadImage
StartImage
函数,但我无法弄清楚应该如何使用
EFI_DEVICE_PATH_PROTOCOL
类型来发挥我的优势。

根据我的研究,我发现其他人遇到的几乎所有示例或问题都是通过使用

EDK2
GNU-EFI 中提供的一些 FileDevicePath 函数来解决的,我在那一刻我发现从头开始的学习经历非常有趣且富有启发性。我的观察得出的结论是,这个
FileDevicePath
函数能够将与文件路径名和文件媒体类型相关的数据粉碎到
EFI_DEVICE_PATH_PROTOCOL
变量中,但我不确定如何自己执行此操作,因为两个源代码示例都使用“ “魔法数字”的助手”函数和宏。也就是说,我正在努力理解设备路径协议结构所需的数据(路径、类型属性、长度等)如何适合规范的结构声明。如果有人能在这个主题上为我指明正确的方向,我非常尊重您使用这个系统的智慧和知识。感谢大家抽出宝贵的时间来做这件事。

operating-system uefi
1个回答
2
投票

设备路径由一个或多个节点组成。每个节点都是一个具有相同 header 的结构,但标头后可以包含不同的数据。 要迭代设备路径,您必须从标头开头前进 Length 字节才能到达下一个。 如果设备路径有效,则以类型为 0x7F 和子类型为 0x010xFF 的结束节点终止。我见过使用 0xFF 作为终端节点类型的系统,您也可以检查一下。

您需要通过 EFI_LOADED_IMAGE_PROTOCOL 协议获取父设备(加载引导加载程序的分区)的设备路径。 您需要的字段是 DeviceHandle,使用 HandleProtocolOpenProtocol 从该句柄获取设备路径。

创建设备路径的副本,如果您不想使用辅助函数,则必须迭代设备路径节点,直到找到结束节点以获取长度。您需要在副本中添加一些空间,以便可以插入文件路径节点

在结束节点之前插入包含 efi 文件路径的文件路径节点。

使用新的设备路径调用LoadImage,并使用LoadImage返回的图像句柄调用StartImage

这是一个基于 EDK2 的示例,它尝试从同一设备/分区加载并启动另一个文件:

EFI_STATUS EFIAPI DevicePathLengthWithoutEnd(
    IN  CONST EFI_DEVICE_PATH_PROTOCOL* DevicePath,
    OUT UINTN* Length)
{
    UINTN TotalLength;
    UINT16 CurrentLength;
    CONST EFI_DEVICE_PATH_PROTOCOL* CurrentNode = NULL;

    if (DevicePath == NULL || Length == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    *Length = 0;

    TotalLength = 0;
    for (
        CurrentNode = DevicePath;
        // You may want to check for the SubType also
        CurrentNode->Type != 0x7F;
        CurrentNode = ((CONST EFI_DEVICE_PATH_PROTOCOL*)(((UINT8*)CurrentNode) + (*(UINT16*)CurrentNode->Length)))) {

        CurrentLength = *((UINT16*)CurrentNode->Length);
        // Check for invalid nodes
        if (CurrentLength < sizeof(EFI_DEVICE_PATH_PROTOCOL)) {
            return EFI_INVALID_PARAMETER;
        }

        TotalLength += (UINTN)CurrentLength;
    }

    *Length = TotalLength;

    return EFI_SUCCESS;
}

EFI_STATUS EFIAPI LoadFile(
    IN  CONST CHAR16* PathOnCurrentDevice)
{
    EFI_STATUS Status;

    UINTN PathSize;
    UINTN OriginalDevicePathLength;
    UINTN NewDevicePathLength;

    EFI_LOADED_IMAGE_PROTOCOL* LoadedImage = NULL;
    CONST EFI_DEVICE_PATH_PROTOCOL* OriginalDevicePath = NULL;
    EFI_DEVICE_PATH_PROTOCOL* NewDevicePath = NULL;
    EFI_DEVICE_PATH_PROTOCOL* NodeHelper = NULL;
    UINT8* ByteHelper = NULL;

    EFI_HANDLE ImageHandle;

    if (PathOnCurrentDevice == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // Get the size of the string (with \0) in bytes
    PathSize = StrSize(PathOnCurrentDevice);

    // Get the loaded image protocol from the current images handle
    Status = gBS->HandleProtocol(
        gImageHandle,
        &gEfiLoadedImageProtocolGuid,
        (VOID**)&LoadedImage);
    if (EFI_ERROR(Status)) {
        Print(u"%a:%d -> %r\r\n", __FILE__, __LINE__, Status);
        goto finish;
    }

    // Get the device path from the current images source device handle
    Status = gBS->HandleProtocol(
        LoadedImage->DeviceHandle,
        &gEfiDevicePathProtocolGuid,
        (VOID**)&OriginalDevicePath);
    if (EFI_ERROR(Status)) {
        Print(u"%a:%d -> %r\r\n", __FILE__, __LINE__, Status);
        goto finish;
    }

    // Get the length of the device path without the end node
    Status = DevicePathLengthWithoutEnd(OriginalDevicePath, &OriginalDevicePathLength);
    if (EFI_ERROR(Status)) {
        Print(u"%a:%d -> %r\r\n", __FILE__, __LINE__, Status);
        goto finish;
    }

    // You need the old path + the file node + an end node
    NewDevicePathLength = 
        OriginalDevicePathLength +
        sizeof(EFI_DEVICE_PATH_PROTOCOL) + PathSize +
        sizeof(EFI_DEVICE_PATH_PROTOCOL);

    Status = gBS->AllocatePool(
        EfiLoaderData,
        NewDevicePathLength,
        (VOID**)&NewDevicePath);
    if (EFI_ERROR(Status)) {
        Print(u"%a:%d -> %r\r\n", __FILE__, __LINE__, Status);
        goto finish;
    }

    ByteHelper = (UINT8*)NewDevicePath;

    // Clone the old path
    gBS->CopyMem(ByteHelper, (VOID*)OriginalDevicePath, OriginalDevicePathLength);

    ByteHelper += OriginalDevicePathLength;

    // Insert the file node after the old path
    NodeHelper = (EFI_DEVICE_PATH_PROTOCOL*)ByteHelper;
    NodeHelper->Type = 0x04;    // 0x04: Media
    NodeHelper->SubType = 0x04; // 0x04: File
    *((UINT16*)NodeHelper->Length) = (UINT16)(sizeof(EFI_DEVICE_PATH_PROTOCOL) + PathSize);

    ByteHelper += sizeof(EFI_DEVICE_PATH_PROTOCOL);

    gBS->CopyMem(ByteHelper, (VOID*)PathOnCurrentDevice, PathSize);

    ByteHelper += PathSize;

    // Add the end node
    NodeHelper = (EFI_DEVICE_PATH_PROTOCOL*)ByteHelper;
    NodeHelper->Type = 0x7F;    // 0x7f: End
    NodeHelper->SubType = 0xFF; // 0xFF: End entire device path
    *((UINT16*)NodeHelper->Length) = sizeof(EFI_DEVICE_PATH_PROTOCOL);

    // Load and start the image
    Status = gBS->LoadImage(
        FALSE,
        gImageHandle,
        NewDevicePath,
        NULL,
        0,
        &ImageHandle);
    if (EFI_ERROR(Status)) {
        Print(u"%a:%d -> %r\r\n", __FILE__, __LINE__, Status);
        goto finish;
    }

    Status = gBS->StartImage(
        ImageHandle,
        NULL,
        NULL);
    if (EFI_ERROR(Status)) {
        Print(u"%a:%d -> %r\r\n", __FILE__, __LINE__, Status);
        goto finish;
    }

    Status = EFI_SUCCESS;
    goto finish;

finish:
    if (NewDevicePath) gBS->FreePool(NewDevicePath);

    return Status;
}
© www.soinside.com 2019 - 2024. All rights reserved.