Step to UEFI (224)编写自己的 Shell 命令(下)

在李安先生的《色戒》之前,还有宾·纳伦(PAN NALIN)导演的同名电影。当然这样也远不如直接说“钟丽缇的‘色戒’”。

色戒

所谓内事不决问百度,外事不决问谷歌,房事不决问和尚。其中男主角的师父就曾经点拨过他:要想将一滴水隐藏起来最好的办法就是把它放入大海。可惜男主角没有领悟师父的思想最后痛苦万分。

有些时候,我们编写的 EFI 程序不希望有人改动,这个时候就可以考虑将程序“藏”到 Shell 中。本文就实验将 RU.EFI 编译到 Shell 中。为了更好的理解本文的方案,最好先阅读理解前介绍过的如何将一个 EFI 程序包含在另外的 EFI中运行的方法【参考1】。具体方法是将下面的代码直接插入上一篇的 lzc.c 中:

      //
      // print it...
      //
      if (ShellStatus == SHELL_SUCCESS) {
        ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_LZC_OUTPUT),gShellLevel3HiiHandle);
        //Your code
        DP=FileDevicePath(NULL,L"fso:\\fake.efi");
        Print(L"%s\n",ConvertDevicePathToText(DP,TRUE,FALSE));
    
        //
        // Load the image with:
        // FALSE - not from boot manager and NULL, 0 being not already in memory
        //
        Status = gBS->LoadImage(
                        FALSE,
                        ImageHandle,
                        DP,
                        (VOID*)&Hello2_efi[0],
                        sizeof(Hello2_efi),
                        &NewHandle);     
        if (EFI_ERROR(Status)) {
                Print(L"Load image Error!  [%r]\n",Status);
                return 0;
        }
        //
        // now start the image, passing up exit data if the caller requested it
        //
        Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
        if (EFI_ERROR(Status)) {
                Print(L"\nError during StartImage [%X]\n",Status);
                return 0;
        }       
        
        Status = gBS->UnloadImage(NewHandle);                        
        if (EFI_ERROR(Status)) {
                Print(L"Un-Load image Error! %r\n",Status);
                return 0;
        }

但是,运行结果显示无法加载,原因是 Invalid Parameter。同样的代码单独编译为完全可以在 Shell 下运行。经过VS2015 动态调试发现,ImageHandle 参数为 0,就是说下面这个函数入口处的 ImageHandle 直接就是0.

/**
  Function for 'lzc' command.

  @param[in] ImageHandle  Handle to the Image (NULL if Internal).
  @param[in] SystemTable  Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunLzc (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )

经过研究,产生问题的原因应该是下面代码,因此在调用每一个命令的时候,入口上的 ImageHandle 直接就赋值为0.

edk202008\ShellPkg\Library\UefiShellCommandLib\UefiShellCommandLib.c

/**
  Checks if a command string has been registered for CommandString and if so it runs
  the previously registered handler for that command with the command line.

  If CommandString is NULL, then ASSERT().

  If Sections is specified, then each section name listed will be compared in a casesensitive
  manner, to the section names described in Appendix B UEFI Shell 2.0 spec. If the section exists,
  it will be appended to the returned help text. If the section does not exist, no
  information will be returned. If Sections is NULL, then all help text information
  available will be returned.

  @param[in]  CommandString          Pointer to the command name.  This is the name
                                     found on the command line in the shell.
  @param[in, out] RetVal             Pointer to the return vaule from the command handler.

  @param[in, out]  CanAffectLE       indicates whether this command's return value
                                     needs to be placed into LASTERROR environment variable.

  @retval RETURN_SUCCESS            The handler was run.
  @retval RETURN_NOT_FOUND          The CommandString did not match a registered
                                    command name.
  @sa SHELL_RUN_COMMAND
**/
RETURN_STATUS
EFIAPI
ShellCommandRunCommandHandler (
  IN CONST CHAR16               *CommandString,
  IN OUT SHELL_STATUS           *RetVal,
  IN OUT BOOLEAN                *CanAffectLE OPTIONAL
  )
………………..
      if (RetVal != NULL) {
        *RetVal = Node->CommandHandler(NULL, gST);
      } else {
        Node->CommandHandler(NULL, gST);
      }
      return (RETURN_SUCCESS);

搜索其他命令作为参考,都没有使用ImageHandle作为参数,但是有使用 gImageHandle。于是,就使用 gImageHandle 作为参数。最终代码如下:

/** @file
  Main file for LABZ Command Test shell level 3 function.

  (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved. <BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "UefiShellLevel3CommandsLib.h"
#include  <Library/DevicePathLib.h>
#include <Library/ShellLib.h>

#include  <ru.h>

/**
  Function for 'lzc' command.

  @param[in] ImageHandle  Handle to the Image (NULL if Internal).
  @param[in] SystemTable  Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunLzc (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS          Status;
  LIST_ENTRY          *Package;
  CHAR16              *ProblemParam;
  SHELL_STATUS        ShellStatus;

  EFI_DEVICE_PATH  *DP;
  EFI_HANDLE      NewHandle;
  UINTN           ExitDataSizePtr; 
        
  ProblemParam        = NULL;
  ShellStatus         = SHELL_SUCCESS;
  //CpuBreakpoint();
  //
  // initialize the shell lib (we must be in non-auto-init...)
  //
  Status = ShellInitialize();
  ASSERT_EFI_ERROR(Status);

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel3HiiHandle, L"lzc", ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    if (ShellCommandLineGetFlag(Package, L"-?")) {
      ASSERT(FALSE);
    } else if (ShellCommandLineGetRawValue(Package, 1) != NULL) {
      ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_MANY), gShellLevel3HiiHandle, L"lzc");
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      //
      // print it...
      //
      if (ShellStatus == SHELL_SUCCESS) {
        ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_LZC_OUTPUT),gShellLevel3HiiHandle);
        
        //Your code start
        DP=FileDevicePath(NULL,L"fso:\\fake.efi");
        Print(L"%s\n",ConvertDevicePathToText(DP,TRUE,FALSE));
    
        //
        // Load the image with:
        // FALSE - not from boot manager and NULL, 0 being not already in memory
        //
        Status = gBS->LoadImage(
                        FALSE,
                        gImageHandle,
                        DP,
                        (VOID*)&RU_efi[0],
                        sizeof(RU_efi),
                        &NewHandle);
        if (EFI_ERROR(Status)) {
                Print(L"Load image Error!  [%r]\n",Status);
                return 0;
        }

        //
        // now start the image, passing up exit data if the caller requested it
        //
        Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
        if (EFI_ERROR(Status)) {
                Print(L"\nError during StartImage [%X]\n",Status);
                return 0;
        }       
        
        Status = gBS->UnloadImage(NewHandle);                        
        if (EFI_ERROR(Status)) {
                Print(L"Un-Load image Error! %r\n",Status);
                return 0;
        } 
        //Your code end
        
      }
    }
    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  return (ShellStatus);
}

编译之后可以在 WinHost 下运行 lzc 命令(RU涉及到硬件的直接访问,界面闪现就会退出)。

模拟器里面的 Shell 运行自定义的 command

使用 build -a X64 -p ShellPkg\ShellPkg.dsc 命令后可以得到包含了 lzc 命令的 Shell ,放置到 U盘 \EFI\Boot\ 下,改名为  BootX64.efi 后即可从 U盘直接启, 有兴趣的朋友可以自己动手实验。

本文涉及到的代码可以在这里下载:

编译后的 Shell.EFI 可以在这里下载:

参考:

1. https://www.lab-z.com/stu165/  在Application 中调用包裹的 Application(上)

发表回复

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