UEFI基礎——UEFI Shell

說明

關於Shell,懂的人都懂,不懂的人就不是很好說明。

總之,在Windows下,它是這樣的:

在Linux(Ubuntu系統)下是這樣的:

在UEFI下是這樣的:

在日本動畫裏面是這樣的:

當然這個梗不是很好笑,劃掉。

總之,它是一個UI,看上去雖然簡陋,但是功能完善,易操作,對於程序員聚焦在開發上很有效。

 

實現

UEFI下的Shell其實是一個UEFI應用,通常情況下,UEFI只是用來啓動系統的,所以Shell不會提供給用戶,但是對於UEFI開發來說,它在調試問題時非常有用。

UEFI Shell應用是一個開源的項目,現在已經包含在edk中:

https://github.com/tianocore/edk2/tree/master/ShellPkg

對應的目錄如下:

它可以獨立編譯成一個efi應用(代碼參考https://gitee.com/jiangwei0512/vUDK2017,後面的代碼和實現都來自該代碼):

!ifndef $(USE_OLD_SHELL)
  ShellPkg/Application/Shell/Shell.inf {
    <LibraryClasses>
      ShellCommandLib|ShellPkg/Library/UefiShellCommandLib/UefiShellCommandLib.inf
      NULL|ShellPkg/Library/UefiShellLevel2CommandsLib/UefiShellLevel2CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellLevel3CommandsLib/UefiShellLevel3CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellDriver1CommandsLib/UefiShellDriver1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellDebug1CommandsLib/UefiShellDebug1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellInstall1CommandsLib/UefiShellInstall1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellNetwork1CommandsLib/UefiShellNetwork1CommandsLib.inf
!if $(NETWORK_IP6_ENABLE) == TRUE
      NULL|ShellPkg/Library/UefiShellNetwork2CommandsLib/UefiShellNetwork2CommandsLib.inf
!endif
      NULL|ShellPkg/Library/UefiShellTftpCommandLib/UefiShellTftpCommandLib.inf
# // jiangwei-20180614-AddBeniShellCommands-start>>
      NULL|ShellPkg/Library/UefiShellBeniCommandLib/UefiShellBeniCommandLib.inf
# // jiangwei-20180614-AddBeniShellCommands-end<<
      HandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
      ShellLib|ShellPkg/Library/UefiShellLib/UefiShellLib.inf
      FileHandleLib|MdePkg/Library/UefiFileHandleLib/UefiFileHandleLib.inf
      PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
#      SafeBlockIoLib|ShellPkg/Library/SafeBlockIoLib/SafeBlockIoLib.inf
#      SafeOpenProtocolLib|ShellPkg/Library/SafeOpenProtocolLib/SafeOpenProtocolLib.inf
      BcfgCommandLib|ShellPkg/Library/UefiShellBcfgCommandLib/UefiShellBcfgCommandLib.inf

    <PcdsFixedAtBuild>
      gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
      gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000
  }
!endif

然後被編譯出來:

最後Shell.efi會被集成到BIOS二進制文件中。

UEFI在啓動時會去找這個Shell.efi文件,並創建啓動項。

  //
  // Register UEFI Shell
  //
  PlatformRegisterFvBootOption (
    PcdGetPtr (PcdShellFile), L"EFI Internal Shell", LOAD_OPTION_ACTIVE
    );

實際上我們可以在BIOS的Boot Manager界面中找到這個啓動項:

它通常被放在最後,只有當不存在其它可用的啓動項時纔會進入(當然一般的發行版BIOS直接會將該啓動項刪除)。

對於Shell的代碼實現,它符合一般的UEFI應用,入口如下:

/**
  The entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )

之後的代碼因爲本身比較簡單,所以不在這裏詳細介紹,這裏只是簡單說明下。

它的主體是一個循環:

        //
        // begin the UI waiting loop
        //
        do {
          //
          // clean out all the memory allocated for CONST <something> * return values
          // between each shell prompt presentation
          //
          if (!IsListEmpty(&ShellInfoObject.BufferToFreeList.Link)){
            FreeBufferList(&ShellInfoObject.BufferToFreeList);
          }

          //
          // Reset page break back to default.
          //
          ShellInfoObject.PageBreakEnabled        = PcdGetBool(PcdShellPageBreakDefault);
          ASSERT (ShellInfoObject.ConsoleInfo != NULL);
          ShellInfoObject.ConsoleInfo->Enabled    = TRUE;
          ShellInfoObject.ConsoleInfo->RowCounter = 0;

          //
          // Reset the CTRL-C event (yes we ignore the return values)
          //
          Status = gBS->CheckEvent (ShellInfoObject.NewEfiShellProtocol->ExecutionBreak);

          //
          // Display Prompt
          //
          Status = DoShellPrompt();
        } while (!ShellCommandGetExit());

循環函數如下:

/**
  Function to perform the shell prompt looping.  It will do a single prompt,
  dispatch the result, and then return.  It is expected that the caller will
  call this function in a loop many times.

  @retval EFI_SUCCESS
  @retval RETURN_ABORTED
**/
EFI_STATUS
DoShellPrompt (
  VOID
  )

它讀取輸入並執行相關的操作:

  //
  // Read a line from the console
  //
  Status = ShellInfoObject.NewEfiShellProtocol->ReadFile(ShellInfoObject.NewShellParametersProtocol->StdIn, &BufferSize, CmdLine);

  //
  // Null terminate the string and parse it
  //
  if (!EFI_ERROR (Status)) {
    CmdLine[BufferSize / sizeof (CHAR16)] = CHAR_NULL;
    Status = RunCommand(CmdLine);
    }

操作大體有兩種:

    //
    // Depending on the first parameter we change the behavior
    //
    switch (Type = GetOperationType(FirstParameter)) {
      case   File_Sys_Change:
        Status = ChangeMappedDrive (FirstParameter);
        break;
      case   Internal_Command:
      case   Script_File_Name:
      case   Efi_Application:
        Status = SetupAndRunCommandOrFile(Type, CleanOriginal, FirstParameter, ShellInfoObject.NewShellParametersProtocol, CommandStatus);
        break;
      default:
        //
        // Whatever was typed, it was invalid.
        //
        ShellPrintHiiEx(-1, -1, NULL, STRING_TOKEN (STR_SHELL_NOT_FOUND), ShellInfoObject.HiiHandle, FirstParameter);
        SetLastError(SHELL_NOT_FOUND);
        break;
    }

第一種其實是更換當前目錄,第二種纔是具體的操作。

具體操作又分爲三種:內置命令,腳本和應用。

內置命令就是集成在Shell內部的操作,可以通過help查看:

UEFI實戰——Protocol和Handle的簡單調試中有介紹如何通過內置命令進行調試。

腳本有特定的格式,以.sh/.nsh等結尾,下面是一個例子:

##
#
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.php
#
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
##

echo -on
hello
echo -off

其實沒有什麼特別的,就是內部指令的一個集合。

應用也沒有特別好介紹的,因爲Shell本身就是一個應用,事實上在Shell裏面還可以執行Shell.efi來開啓一個新的Shell。另外一個比較常用的應用就是GRUB,它用來啓動操作系統。

 

以上就是關於UEFI Shell的簡單介紹。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章