Step to UEFI (45) —– 在程序中执行另外的程序

某些情况下,我们有在自己的程序中调用另外一个 EFI 程序的需求。

关于这个问题【参考1】建议参考Shell的源程序。如果有时间,建议阅读这一段代码,相信对于具体的实现很有帮助。

“RunCommandOrFile()
==> case Efi_Application:
InternalShellExecuteDevicePath()
==> Status = gBS->LoadImage( … ) ”

另外,【参考2】介绍了一下调用的流程:

1. BS->LoadImage 加载你要调用的 EFI 到内存
2. BS->StartImage 执行你加载的EFI程序
3. BS->UnLoadImage 执行完成之后释放EFI

了解了基本流程原理,下面就要认真阅读函数的原型。

LoadImage的原型如下,来自【参考3】

loadimage

BootPolicy 告诉加载的EFI是否为可启动的选项
ParentImageHandle 是调用者的Handle
DevicePath 告诉要调用的EFI文件的位置
SourceBuffer 可选如果不为NULL的话,是指向内存中的要加载的EFI的指针
SourceSize 如果上面这个指针存在的话,给出指向内存的大小
EFI_HANDLE 加载之后Image的Handle

StartImage 原型

startimage

ImageHandle  前面LoadImage给出来的EFI Image Handle
ExitDataSize 下面ExitData的大小
ExitData 看起来在一个 EFI 结束的时候,可以返回一些内容

UnLoadImage 原型

unimage

给出要释放的EFI的Handle即可

根据上面的介绍,再结合Shell.c中的具体实现,编写程序如下。为了方便验证和保持整个程序的简洁,程序固定调用“HellowWorld.efi”。这个程序的作用是输出一段String。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

extern EFI_BOOT_SERVICES           	 *gBS;
extern EFI_SYSTEM_TABLE				 *gST;
extern EFI_RUNTIME_SERVICES 		 *gRT;

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;

extern EFI_HANDLE					 gImageHandle;
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  EFI_HANDLE	NewHandle;
  EFI_STATUS	Status;
  UINTN			ExitDataSizePtr;
  CHAR16 *R=L"HelloWorld.efi";
  
  Print(L"File [%s]\n",R);

  DevicePath=ShellGetDevicePath(R);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\n",Status);
    return (Status);
  }
  

  gBS->UnloadImage (NewHandle);  
  return EFI_SUCCESS;
}

 

运行结果:

execres

工作的视频:

代码下载

exec

最后,如果你只是想简单的执行一个程序,可以考虑直接使用 EFI_SHELL_PROTOCOL 的 EfiShellExecute 或者 EFI_SHELL_ENVIRONMENT2的 Execute ,这样会简单许多。

参考:
1. http://biosren.com/viewthread.php?tid=7440&highlight=%BC%D3%D4%D8%2B%B3%CC%D0%F2 请问:在shell下,应用程序的.efi文件被加载到内存的基地址为多少?
2. http://blog.csdn.net/kaven128708/article/details/6042307 EFI Load Image
3. UEFI Spec 2.4 P196

Step to UEFI (45) —– 在程序中执行另外的程序》上有 7 条评论

    1. ziv2013 文章作者

      你要 Load 的代码是不是用到 clib

      我这边发现无法 run clib编写的程序,原因还不清楚,回头我debug一下,怀疑是加载命令行参数的问题

      回复
          1. 初心者

            好的 ,ShellExecute好像不用System table來調用service就能使用?
            而LoadImage要調用System table 才能使用,如gBS->LoadImage

发表评论

电子邮件地址不会被公开。 必填项已用*标注