说明
关于Shell,懂的人都懂,不懂的人就不是很好说明。
总之,在Windows下,它是这样的:
在Linux(Ubuntu系统)下是这样的:
在UEFI下是这样的:
在日本动画里面是这样的:
当然这个梗不是很好笑,划掉。
总之,它是一个UI,看上去虽然简陋,但是功能完善,易操作,对于程序员聚焦在开发上很有效。
实现
UEFI下的Shell其实是一个UEFI应用,通常情况下,UEFI只是用来启动系统的,所以Shell不会提供给用户,但是对于UEFI开发来说,它在调试问题时非常有用。
UEFI Shell应用是一个开源的项目,现在已经包含在edk中:
edk2/ShellPkg at master · tianocore/edk2 · GitHub
对应的目录如下:
它可以独立编译成一个efi应用(代码参考vUDK2017: https://github.com/tianocore/edk2.git Tag 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的简单介绍。