UEFI原理与编程(三):UEFI工程模块文件-Shell应用程序工程模块

UEFI工程模块文件-Shell应用程序工程模块

前言

  UEFI标准应用程序工程模块不方便处理命令行参数。但是一般在shell中执行的命令都会带有命令行参数。为了解决这个问题,EDK2提供了Shell应用程序工程模块。

一、源文件(入口函数)

Shell应用程序工程模块的入口函数:

INTN ShellAppMain(IN UINTN Argc, IN CHAR16 **Argv)

仿照标准应用程序工程模块编写Shell应用程序工程模块的源文件:

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
INTN ShellAppMain(IN UINTN Argc, IN CHAR16 **Argv)
{
    gST->ConOut->OutputString (gST->ConOut,L"Hello man, welcome to UEFI world \n");
    return 0;
}

现在可以看到入口函数ShellAppMain 有了类似于C/C++ 中的参数。但是,现在没有了SystemTable,所以可以通过全局变量gTS使用系统表。Argv列表中的每个参数都是Unicode字符串。

二、工程文件

1. [Defines]块

基本与标准应用程序工程模块相同,除了EntryPoint。在标准应用程序工程模块中,入口函数名,需要开发者自己定义,需要保持工程文件与源文件一致,一般设置为UefiMain。但是在Shell应用程序工程模块中,它必须设置为ShellAppMain

2. [Package]块

语法与标准应用程序工程模块基本相同,只是必须列出MdePkg/MdePkg.decShellPkg/ShellPkg.dec

3. [LibraryClasses]块

必须列出ShellCEntryLib,通常还要列出 UefiBootServicesTableLibUefiLib

4. 其它块

其它块语法与标准应用程序工程模块相同。

5. Shell 应用程序工程模块工程文件示例

[Defines]
  INF_VERSION       = 0x00010006
  BASE_NAME         = HelloWorld
  FILE_GUID         = 00000000-0000-0000-0000-000000000000
  MODULE_TYPE       = UEFI_APPLICATION
  VERSION_STRING    = 1.0
  ENTRY_POINT       = ShellCEntryLib

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  ShellCEntryLib
  UefiBootServicesTableLib
  UefiLib

[BuildOptions]
  MSFT:*_*_*_CC_FLAGS = /w

三、编译运行

基本上和标准应用程序工程模块相同,我们可以将工程文件相对于EDK2根目录的路径名添加到ShellPkg.dsc [Components]里。
执行下面的命令:

build -p ShellPkg\ShellPkg.dsc -m [工程文件相对EDK2根目录路径名] -a IA32 (64位用:X64)

运行结果:
这里写图片描述

四、Shell应用程序工程模块源码解析

在UEFI原理与编程(二):UEFI工程模块文件-标准应用程序工程模块中提到,它是应用程序工程模块的基础,那么Shell应用程序工程模块入口函数是如何做到可以传递命令行参数的?可以查看ShellCEntryLib的源代码:

//路径:*/ShellPkg/Library/UefiShellCEntryLib/UefiShellCEntryLib.c*
EFI_STATUS
EFIAPI
ShellCEntryLib (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  INTN                           ReturnFromMain;
  EFI_SHELL_PARAMETERS_PROTOCOL *EfiShellParametersProtocol;
  EFI_SHELL_INTERFACE           *EfiShellInterface;
  EFI_STATUS                    Status;

  ReturnFromMain = -1;
  EfiShellParametersProtocol = NULL;
  EfiShellInterface = NULL;

  Status = SystemTable->BootServices->OpenProtocol(ImageHandle,
                             &gEfiShellParametersProtocolGuid,
                             (VOID **)&EfiShellParametersProtocol,
                             ImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_GET_PROTOCOL
                            );
  if (!EFI_ERROR(Status)) {
    //
    // use shell 2.0 interface
    //
    ReturnFromMain = ShellAppMain (
                       EfiShellParametersProtocol->Argc,
                       EfiShellParametersProtocol->Argv
                      );
  } else {
    //
    // try to get shell 1.0 interface instead.
    //
    Status = SystemTable->BootServices->OpenProtocol(ImageHandle,
                               &gEfiShellInterfaceGuid,
                               (VOID **)&EfiShellInterface,
                               ImageHandle,
                               NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL
                              );
    if (!EFI_ERROR(Status)) {
      //
      // use shell 1.0 interface
      //
      ReturnFromMain = ShellAppMain (
                         EfiShellInterface->Argc,
                         EfiShellInterface->Argv
                        );
    } else {
      ASSERT(FALSE);
    }
  }
  return ReturnFromMain;
}

从源代码的入口函数:

EFI_STATUS EFIAPI
ShellCEntryLib (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)

可以看到它的参数可标准应用程序工程模块的入口函数参数实现共同的。在ShellCEntryLib中,首先打开EFI_SHELL_PARAMETERS_PROTOCOL可以获取命令行参数 Argc,Argv,然后调用ShellAppMain 函数。

关于 EFI_SHELL_PARAMETERS_PROTOCOL源代码

typedef struct _EFI_SHELL_PARAMETERS_PROTOCOL {
  ///
  /// Points to an Argc-element array of points to NULL-terminated strings containing
  /// the command-line parameters. The first entry in the array is always the full file
  /// path of the executable. Any quotation marks that were used to preserve
  /// whitespace have been removed.
  ///
  CHAR16 **Argv;

  ///
  /// The number of elements in the Argv array.
  ///
  UINTN Argc;

  ///
  /// The file handle for the standard input for this executable. This may be different
  /// from the ConInHandle in EFI_SYSTEM_TABLE.
  ///
  SHELL_FILE_HANDLE StdIn;

  ///
  /// The file handle for the standard output for this executable. This may be different
  /// from the ConOutHandle in EFI_SYSTEM_TABLE.
  ///
  SHELL_FILE_HANDLE StdOut;

  ///
  /// The file handle for the standard error output for this executable. This may be
  /// different from the StdErrHandle in EFI_SYSTEM_TABLE.
  ///
  SHELL_FILE_HANDLE StdErr;
} EFI_SHELL_PARAMETERS_PROTOCOL;

发现它除了ArgcArgv还有SHELL_FILE_HANDLE 类型的成员:

  • StdIn  标准输入句柄
  • StdOut 标准输出句柄
  • StdErr 标准错误句柄

五、总结

  Shell应用程序工程模块是在标准应用程序模块的基础之上利用Protocol做了封装,使我们可以很好地编写带命令行参数的UEFI程序。
  本部分demo是在已经安装了EDK2环境基础上编译运行的。如果还没有安装,可以参考:UEFI原理与编程(一):环境搭建。
  标准应用程序工程模块内容可以参考:UEFI原理与编程(二):UEFI工程模块文件-标准应用程序工程模块

参考资料

<1>《UEFI原理与编程》戴正华 著
<2> UEFI Spec2_6

你可能感兴趣的:(UEFI,原理与编程学习笔记)