经过之前多篇文章的积累,目前终于具备了搭建EFI的自有shell的各种条件。完成这种初步的hello world是后续打印各种协议信息的基础。
需要使用的东西是UefiMain.h所包含的表、SimpleText.h所包含的输入输出协议。
具体的实现思路是:
1、编写EFI_Main的主函数,引用其系统表
2、根据系统表调用ConIn、ConOut两个主要函数
3、添加ConIn中的阻滞函数于while循环中,在接收到键盘事件后,进行具体的动作
4、具体动作初步可以编写为一个string的直白打印
Shell.efi文件参考:
效果是非常的好啊,照片不好看就不发了。
编译杂项
正式进入EFI开发后,很多EDK2项目可以提供的东西最好还是用起来。
有关EFI主要的系统表、入口函数等内容,基本都定义在/edk2/MdePkg/Include/这个目录下面,具体来说,是主要集中在如下三个头文件上:
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>相应的,需要在gcc进行编译时(链接时可以不用头文件),以-I命令把头文件所在的目录加上,由于X86的要求,-I命令除了基本的根目录外还要再指定一个X86的目录。因此,需要再makefile中以参数的形式规定一些头文件检索路径,这里是一个参考:
## 这是makefile中的内容
## 新增参数用以代表检索路径
#######################################################################
# 项目名称
ProjectName = Shell
# 库文件路径参数
LIBFLAGS = -I /edk2/MdePkg/Include/ -I /edk2/MdePkg/Include/AArch64/
# EFI的入口函数
EFIMain = UefiMain
#######################################################################此外,在引用来自EDK2项目的头文件时,由于其内部根据参数的不同在实例化具体变量结构时会变化,所以这个-I参数必须要对。
此外,EDK2项目本体已经对gST、gBS等尝试做了实例化,但不一定能用,实际最好还是直接以UefiSpec.h这个头文件中比较接地气的直接实例化为准展开变量解析。
有关makefile具体怎么写,这在之前的文章中已经详细谈过,这里不再赘述。那么,解决了最后的工具链问题后,开启真正的shell代码编写吧。
主函数开发
使用了来自MdePkg的头文件后,可以直接复用头文件中的struct。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleTextIn.h>
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_INPUT_KEY Key;
BOOLEAN Running = TRUE;
UINTN Index;
EFI_SYSTEM_TABLE *gST = SystemTable;
EFI_BOOT_SERVICES *gBS = gST->BootServices;
CHAR16 *HelloString = L"Hello, EFI World!\r\n";
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut = gST->ConOut;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn = gST->ConIn;
CHAR16 OutputStr[2];
if (ConOut == NULL) {
return EFI_UNSUPPORTED;
}
Status = ConOut->Reset(ConOut,FALSE);
if (EFI_ERROR(Status)) {
return Status;
}
// 打印欢迎界面
Status = ConOut->OutputString(ConOut,HelloString);
if (EFI_ERROR(Status)) {
return Status;
}
// 开启光标可见
ConOut->EnableCursor(ConOut, TRUE);
while (Running) {
// WaitForEvent:
Status = gBS->WaitForEvent(1, &ConIn->WaitForKey, &Index);
if (EFI_ERROR(Status)) {
return Status;
}
// ReadKeyStroke: 读取按键数据
Status = ConIn->ReadKeyStroke(ConIn, &Key);
if (EFI_ERROR(Status)) {
ConOut->OutputString(ConOut,L"\nError reading key\n");
return Status;
}
// 如果 UnicodeChar 不为0,说明是可打印字符或普通ASCII字符
if (Key.UnicodeChar != 0) {
// 处理特殊控制字符,如回车换行
if (Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
ConOut->OutputString(ConOut,L"\r\n");
} else if (Key.UnicodeChar == CHAR_BACKSPACE) {
// 简单的退格处理:打印空格覆盖再退格,或者直接忽略,这里选择直接打印以便看到效果
// 在真实终端中可能需要更复杂的缓冲处理,这里仅做简单回显
ConOut->OutputString(ConOut,L"\b \b");
} else {
// 打印输入的字符
OutputStr[0] = Key.UnicodeChar;
OutputStr[1] = L'\0';
ConOut->OutputString(ConOut,OutputStr);
}
} else {
// 处理功能键(如F1-F12, Arrow Keys等),显示其扫描码
// ScanCode 为 0 表示普通字符,非0表示功能键
if (Key.ScanCode != 0) {
ConOut->OutputString(ConOut,L"\nScanCode");
}
}
}
return EFI_SUCCESS;
}
使用makefile进行编译就可以了。