說明
關於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的簡單介紹。