之前文章中提到过,用LoadImage和StartImage无法加载CLIB build出来的 Application。这次认真研究一下这个问题。
首先,准备实验的材料: 两个简单的小程序 Hello1 和 Hello2 。前者是 CLIB 编出来的,后者是普通的EFI 程序。此外还有一个加载器程序 exec4.efi 。
1. 单独执行编译出来的 Hello1.efi 和Hello2.efi都没问题。实验 exec4 ,加载 hello1.efi 会出错,虚拟机会重启到 Setup中,加载 hello2.efi 正常;
2. 对 Hello1 进行分析,分析的方法是加入【参考1】提到的那种按键Pause。
2.1 在Build\NT32IA32\DEBUG_MYTOOLS\IA32\AppPkg\Applications\Hello1\Hello1\Makefile文件中可以看到,入口定义:
IMAGE_ENTRY_POINT = _ModuleEntryPoint
2.2 我们再根据编译过程生成的MAP文件,确定 _ModuleEntryPoint 是在 ApplicationEntryPoint.c 中。同样【参考2】可以给我们提供很多经验,相比普通的EFI程序,增加的CLib只是在整个架构中插入了多函数,并不会改变整体的架构。
/** Entry point to UEFI Application. This function is the entry point for a UEFI Application. This function must call ProcessLibraryConstructorList(), ProcessModuleEntryPointList(), and ProcessLibraryDestructorList(). The return value from ProcessModuleEntryPointList() is returned. If _gUefiDriverRevision is not zero and SystemTable->Hdr.Revision is less than _gUefiDriverRevison, then return EFI_INCOMPATIBLE_VERSION. @param ImageHandle The image handle of the UEFI Application. @param SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The UEFI Application exited normally. @retval EFI_INCOMPATIBLE_VERSION _gUefiDriverRevision is greater than SystemTable->Hdr.Revision. @retval Other Return value from ProcessModuleEntryPointList(). **/ EFI_STATUS EFIAPI _ModuleEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; if (_gUefiDriverRevision != 0) { // // Make sure that the EFI/UEFI spec revision of the platform is >= EFI/UEFI spec revision of the application. // if (SystemTable->Hdr.Revision < _gUefiDriverRevision) { return EFI_INCOMPATIBLE_VERSION; } } // // Call constructor for all libraries. // ProcessLibraryConstructorList (ImageHandle, SystemTable); // // Call the module's entry point // Status = ProcessModuleEntryPointList (ImageHandle, SystemTable); // // Process destructor for all libraries. // ProcessLibraryDestructorList (ImageHandle, SystemTable); // // Return the return status code from the driver entry point // return Status; }
首先追到的是 ProcessLibraryConstructorList 我们在其中插入Debug信息。特别注意,插入的位置在 \Build\NT32IA32\DEBUG_MYTOOLS\IA32\AppPkg\Applications\Hello1\Hello1\DEBUG\AutoGen.c
因为这个文件是编译过程中生成的,所以我们不可以重新 Build AppPkg,而要在目录中(\Build\NT32IA32\DEBUG_MYTOOLS\IA32\AppPkg\Applications\Hello1\Hello1\) 直接运行 NMake来编译;
2.3 插入Debug信息后,NMAKE 编译通过,直接运行 Hello1.efi 一次,确保没问题,再用 exec4 加载 hello1.efi 。同样有错误,这说明问题不是发生在ProcessLibraryConstructorList 中;下面是插入后的代码式样:
VOID EFIAPI ProcessLibraryConstructorList ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_INPUT_KEY Key; SystemTable->ConOut->OutputString(SystemTable->ConOut,L"UefiRuntimeServicesTableLibConstructor\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status = UefiRuntimeServicesTableLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); SystemTable->ConOut->OutputString(SystemTable->ConOut,L"UefiBootServicesTableLibConstructor\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); SystemTable->ConOut->OutputString(SystemTable->ConOut,L"UefiLibConstructor\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status = UefiLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); SystemTable->ConOut->OutputString(SystemTable->ConOut,L"__wchar_construct\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status = __wchar_construct (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); SystemTable->ConOut->OutputString(SystemTable->ConOut,L"ShellLibConstructor \n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status = ShellLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); SystemTable->ConOut->OutputString(SystemTable->ConOut,L"UefiHiiServicesLibConstructor \n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status = UefiHiiServicesLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); }
直接运行程序会不断暂停等待按键才继续:
2.4 接下来在ProcessModuleEntryPointList中像上面一样插入Debug,
// // Call the module's entry point // Status = ProcessModuleEntryPointList (ImageHandle, SystemTable); EFI_STATUS EFIAPI ProcessModuleEntryPointList ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_INPUT_KEY Key; SystemTable->ConOut->OutputString(SystemTable->ConOut,L"ShellCEntryLib \n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} Status=ShellCEntryLib (ImageHandle, SystemTable); SystemTable->ConOut->OutputString(SystemTable->ConOut,L"ShellCEntryLib Exit \n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_UP!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Waiting for DOWN_KEY\n\r"); Key.ScanCode=SCAN_NULL; while (SCAN_DOWN!=Key.ScanCode) {Status= SystemTable -> ConIn -> ReadKeyStroke(SystemTable->ConIn,&Key);} return EFI_SUCCESS; }
再次实验 Exec4 加载发现,现象消失了。仔细琢磨一下,应该是我最后 return EFI_SUCCESS 导致的。所以问题就应该发生在进入 ShellCEntryLib 那里。
2.5 继续调试直接在 ShellCEntryLib 加入 Debug 信息
/** UEFI entry point for an application that will in turn call the ShellAppMain function which has parameters similar to a standard C main function. An application that uses UefiShellCEntryLib must have a ShellAppMain function as prototyped in Include/Library/ShellCEntryLib.h. Note that the Shell uses POSITIVE integers for error values, while UEFI uses NEGATIVE values. If the application is to be used within a script, it needs to return one of the SHELL_STATUS values defined in ShellBase.h. @param ImageHandle The image handle of the UEFI Application. @param SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The application exited normally. @retval Other An error occurred. **/ EFI_STATUS EFIAPI ShellCEntryLib ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { INTN ReturnFromMain; EFI_SHELL_PARAMETERS_PROTOCOL *EfiShellParametersProtocol; EFI_SHELL_INTERFACE *EfiShellInterface; EFI_STATUS Status; ReturnFromMain = -1; EfiShellParametersProtocol = NULL; EfiShellInterface = NULL; Status = SystemTable->BootServices->OpenProtocol(ImageHandle, &gEfiShellParametersProtocolGuid, (VOID **)&EfiShellParametersProtocol, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (!EFI_ERROR(Status)) { SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Shell2\n\r"); // // use shell 2.0 interface // ReturnFromMain = ShellAppMain ( EfiShellParametersProtocol->Argc, EfiShellParametersProtocol->Argv ); } else { SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Shell1\n\r"); // // try to get shell 1.0 interface instead. // Status = SystemTable->BootServices->OpenProtocol(ImageHandle, &gEfiShellInterfaceGuid, (VOID **)&EfiShellInterface, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (!EFI_ERROR(Status)) { SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Shell1.1\n\r"); // // use shell 1.0 interface // ReturnFromMain = ShellAppMain ( EfiShellInterface->Argc, EfiShellInterface->Argv ); } else { SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Shell fail\n\r"); ASSERT(FALSE); } } return ReturnFromMain; }
用exec4加载之后输出如下:
可以看到,两种方式下,运行路径是不同的。
最后的结论:产生问题的原因是,当我们用 StartImage 运行一个 CLib程序的时候,Clib带入的函数找不到 Efi Shell Interface (要用这个Interface 的原因是希望取命令行参数传给被调用者)。找不到的时候就报错,报告一个加载不成功。
本文提到的 hello1 hello2 exec4 的源代码下载:
exec4
Hello2
Hello1
参考:
1.http://www.lab-z.com/utpk/ UEFI Tips 用按键做Pause
2.http://www.lab-z.com/22applicationentry/ Application的入口分析