Step to UEFI (98)Shell加载EFI的分析

继续阅读《UEFI 原理与编程》,其中的第三章介绍了一个 EFI Application如何在 Shell下加载运行的。ShellProtocol.c中的InternalShellExecuteDevicePath 是具体实现这个功能的函数。
函数头:

/**
  internal worker function to load and run an image via device path.

  @param ParentImageHandle      A handle of the image that is executing the specified
                                command line.
  @param DevicePath             device path of the file to execute
  @param CommandLine            Points to the NULL-terminated UCS-2 encoded string
                                containing the command line. If NULL then the command-
                                line will be empty.
  @param Environment            Points to a NULL-terminated array of environment
                                variables with the format 'x=y', where x is the
                                environment variable name and y is the value. If this
                                is NULL, then the current shell environment is used.
                            
  @param[out] StartImageStatus  Returned status from gBS->StartImage.

  @retval EFI_SUCCESS       The command executed successfully. The  status code
                            returned by the command is pointed to by StatusCode.
  @retval EFI_INVALID_PARAMETER The parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES Out of resources.
  @retval EFI_UNSUPPORTED   Nested shell invocations are not allowed.
**/
EFI_STATUS
EFIAPI
InternalShellExecuteDevicePath(
  IN CONST EFI_HANDLE               *ParentImageHandle,
  IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath,
  IN CONST CHAR16                   *CommandLine OPTIONAL,
  IN CONST CHAR16                   **Environment OPTIONAL,
  OUT EFI_STATUS                    *StartImageStatus OPTIONAL
  )

 

第一步,先将 EFI Application加载到内存中,生成Image对象。取得这个对象的句柄为 NewHandle。

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    *ParentImageHandle,
    (EFI_DEVICE_PATH_PROTOCOL*)DevicePath,
    NULL,
    0,
    &NewHandle);

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
    return (Status);
  }

第二步,取得命令行参数,将命令行的参数交给 NewHandle

 Status = gBS->OpenProtocol(
    NewHandle,
    &gEfiLoadedImageProtocolGuid,
    (VOID**)&LoadedImage,
    gImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL);

  if (!EFI_ERROR(Status)) {
    ASSERT(LoadedImage->LoadOptionsSize == 0);
    if (NewCmdLine != NULL) {
      LoadedImage->LoadOptionsSize  = (UINT32)StrSize(NewCmdLine);
      LoadedImage->LoadOptions      = (VOID*)NewCmdLine;
    }

 

看到这里我有一个问题:前面加载之后,为什么第二步就能够在被加载的Image上找到“EFI_Loaded_Image_Protocol”? 带着这样的问题追踪了代码。首先要找到 gBS->LoadImage 的真正实现代码,在\MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c有定义,

//
// DXE Core Module Variables
//
EFI_BOOT_SERVICES mBootServices = {
  {
.................
.................
  (EFI_IMAGE_LOAD)                              CoreLoadImage,                            // LoadImage
.................
.................
}

 

再追CoreLoadImage代码在 \MdeModulePkg\Core\Dxe\Image\Image.c

/**
  Loads an EFI image into memory and returns a handle to the image.

  @param  BootPolicy              If TRUE, indicates that the request originates
                                  from the boot manager, and that the boot
                                  manager is attempting to load FilePath as a
                                  boot selection.
  @param  ParentImageHandle       The caller's image handle.
  @param  FilePath                The specific file path from which the image is
                                  loaded.
  @param  SourceBuffer            If not NULL, a pointer to the memory location
                                  containing a copy of the image to be loaded.
  @param  SourceSize              The size in bytes of SourceBuffer.
  @param  ImageHandle             Pointer to the returned image handle that is
                                  created when the image is successfully loaded.

  @retval EFI_SUCCESS             The image was loaded into memory.
  @retval EFI_NOT_FOUND           The FilePath was not found.
  @retval EFI_INVALID_PARAMETER   One of the parameters has an invalid value.
  @retval EFI_UNSUPPORTED         The image type is not supported, or the device
                                  path cannot be parsed to locate the proper
                                  protocol for loading the file.
  @retval EFI_OUT_OF_RESOURCES    Image was not loaded due to insufficient
                                  resources.
  @retval EFI_LOAD_ERROR          Image was not loaded because the image format was corrupt or not
                                  understood.
  @retval EFI_DEVICE_ERROR        Image was not loaded because the device returned a read error.
  @retval EFI_ACCESS_DENIED       Image was not loaded because the platform policy prohibits the 
                                  image from being loaded. NULL is returned in *ImageHandle.
  @retval EFI_SECURITY_VIOLATION  Image was loaded and an ImageHandle was created with a 
                                  valid EFI_LOADED_IMAGE_PROTOCOL. However, the current 
                                  platform policy specifies that the image should not be started.

**/
EFI_STATUS
EFIAPI
CoreLoadImage (
  IN BOOLEAN                    BootPolicy,
  IN EFI_HANDLE                 ParentImageHandle,
  IN EFI_DEVICE_PATH_PROTOCOL   *FilePath,
  IN VOID                       *SourceBuffer   OPTIONAL,
  IN UINTN                      SourceSize,
  OUT EFI_HANDLE                *ImageHandle
  )
{
  EFI_STATUS    Status;
  UINT64        Tick;
  EFI_HANDLE    Handle;

  Tick = 0;
  PERF_CODE (
    Tick = GetPerformanceCounter ();
  );

  Status = CoreLoadImageCommon (
             BootPolicy,
             ParentImageHandle,
             FilePath,
             SourceBuffer,
             SourceSize,
             (EFI_PHYSICAL_ADDRESS) (UINTN) NULL,
             NULL,
             ImageHandle,
             NULL,
             EFI_LOAD_PE_IMAGE_ATTRIBUTE_RUNTIME_REGISTRATION | EFI_LOAD_PE_IMAGE_ATTRIBUTE_DEBUG_IMAGE_INFO_TABLE_REGISTRATION
             );

  Handle = NULL; 
  if (!EFI_ERROR (Status)) {
    //
    // ImageHandle will be valid only Status is success. 
    //
    Handle = *ImageHandle;
  }

  PERF_START (Handle, "LoadImage:", NULL, Tick);
  PERF_END (Handle, "LoadImage:", NULL, 0);

  return Status;
}
在CoreLoadImageCommon 中可以找到加载安装EFI_Loaded_Image_Protocol 的代码。
  //
  //Reinstall loaded image protocol to fire any notifications
  //
  Status = CoreReinstallProtocolInterface (
             Image->Handle,
             &gEfiLoadedImageProtocolGuid,
             &Image->Info,
             &Image->Info
             );
  if (EFI_ERROR (Status)) {
    goto Done;
  }

 

就是说,在加载过程中,还要对Image 安装EFI_Loaded_Image_Protocol。因此,Load 的动作并不是简单的读取到内存中。

继续回到InternalShellExecuteDevicePath 代码中。

    //第三步,将修改好的ShellParamsProtocol 再安装到 Image上。
    //
    // Initialize and install a shell parameters protocol on the image.
    //
    ShellParamsProtocol.StdIn   = ShellInfoObject.NewShellParametersProtocol->StdIn;
    ShellParamsProtocol.StdOut  = ShellInfoObject.NewShellParametersProtocol->StdOut;
    ShellParamsProtocol.StdErr  = ShellInfoObject.NewShellParametersProtocol->StdErr;
    Status = UpdateArgcArgv(&ShellParamsProtocol, NewCmdLine, NULL, NULL);
    ASSERT_EFI_ERROR(Status);
    //
    // Replace Argv[0] with the full path of the binary we're executing:
    // If the command line was "foo", the binary might be called "foo.efi".
    // "The first entry in [Argv] is always the full file path of the
    //  executable" - UEFI Shell Spec section 2.3
    //
    ImagePath = EfiShellGetFilePathFromDevicePath (DevicePath);
    // The image we're executing isn't necessarily in a filesystem - it might
    // be memory mapped. In this case EfiShellGetFilePathFromDevicePath will
    // return NULL, and we'll leave Argv[0] as UpdateArgcArgv set it.
    if (ImagePath != NULL) {
      if (ShellParamsProtocol.Argv == NULL) {
        // Command line was empty or null.
        // (UpdateArgcArgv sets Argv to NULL when CommandLine is "" or NULL)
        ShellParamsProtocol.Argv = AllocatePool (sizeof (CHAR16 *));
        if (ShellParamsProtocol.Argv == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          goto UnloadImage;
        }
        ShellParamsProtocol.Argc = 1;
      } else {
        // Free the string UpdateArgcArgv put in Argv[0];
        FreePool (ShellParamsProtocol.Argv[0]);
      }
      ShellParamsProtocol.Argv[0] = ImagePath;
    }

    Status = gBS->InstallProtocolInterface(&NewHandle, &gEfiShellParametersProtocolGuid, EFI_NATIVE_INTERFACE, &ShellParamsProtocol);
    ASSERT_EFI_ERROR(Status);
//第四步,用 gBS->StartImage执行Image。
    //
    // now start the image and if the caller wanted the return code pass it to them...
    //
    if (!EFI_ERROR(Status)) {
      StartStatus      = gBS->StartImage(
                          NewHandle,
                          0,
                          NULL
                          );
      if (StartImageStatus != NULL) {
        *StartImageStatus = StartStatus;
      }

 

Step to UEFI (92)关于 ConOut 的奇怪实验

UEFI System Table 中的 ConOut-> OutputString 能够让我们直接在屏幕上输出字符串。这里介绍一种方法,能够让我们截获并且修改这个函数输出的字符串。

首先看一下 System Table 的定义在 \MdePkg\Include\Uefi\UefiSpec.h

///

/// EFI System Table

///

typedef struct {

///

/// The table header for the EFI System Table.

///

EFI_TABLE_HEADER                  Hdr;

///

/// A pointer to a null terminated string that identifies the vendor

/// that produces the system firmware for the platform.

///

CHAR16                            *FirmwareVendor;

///

/// A firmware vendor specific value that identifies the revision

/// of the system firmware for the platform.

///

UINT32                            FirmwareRevision;

///

/// The handle for the active console input device. This handle must support

/// EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL.

///

EFI_HANDLE                        ConsoleInHandle;

///

/// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is

/// associated with ConsoleInHandle.

///

EFI_SIMPLE_TEXT_INPUT_PROTOCOL    *ConIn;

///

/// The handle for the active console output device.

///

EFI_HANDLE                        ConsoleOutHandle;

///

/// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface

/// that is associated with ConsoleOutHandle.

///

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *ConOut;

///

/// The handle for the active standard error console device.

/// This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.

///

EFI_HANDLE                        StandardErrorHandle;

///

/// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface

/// that is associated with StandardErrorHandle.

///

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *StdErr;

///

/// A pointer to the EFI Runtime Services Table.

///

EFI_RUNTIME_SERVICES              *RuntimeServices;

///

/// A pointer to the EFI Boot Services Table.

///

EFI_BOOT_SERVICES                 *BootServices;

///

/// The number of system configuration tables in the buffer ConfigurationTable.

///

UINTN                             NumberOfTableEntries;

///

/// A pointer to the system configuration tables.

/// The number of entries in the table is NumberOfTableEntries.

///

EFI_CONFIGURATION_TABLE           *ConfigurationTable;

} EFI_SYSTEM_TABLE;

 

其中的ConOut 是指向  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  的指针

具体定义可以在 \MdePkg\Include\Protocol\SimpleTextOut.h 查到

///

/// The SIMPLE_TEXT_OUTPUT protocol is used to control text-based output devices.

/// It is the minimum required protocol for any handle supplied as the ConsoleOut

/// or StandardError device. In addition, the minimum supported text mode of such

/// devices is at least 80 x 25 characters.

///

struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {

EFI_TEXT_RESET                Reset;

EFI_TEXT_STRING               OutputString;

EFI_TEXT_TEST_STRING          TestString;

EFI_TEXT_QUERY_MODE           QueryMode;

EFI_TEXT_SET_MODE             SetMode;

EFI_TEXT_SET_ATTRIBUTE        SetAttribute;

EFI_TEXT_CLEAR_SCREEN         ClearScreen;

EFI_TEXT_SET_CURSOR_POSITION  SetCursorPosition;

EFI_TEXT_ENABLE_CURSOR        EnableCursor;

///

/// Pointer to SIMPLE_TEXT_OUTPUT_MODE data.

///

EFI_SIMPLE_TEXT_OUTPUT_MODE   *Mode;

};

我们需要的是将  EFI_TEXT_STRING OutputString; 替换为我们自己的函数。

最终的代码如下

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>


extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		  *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

EFI_SYSTEM_TABLE	myST;
EFI_SYSTEM_TABLE	*pmyST=&myST;

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL	myConOut;

EFI_STATUS
myOut (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL        *This,
  IN CHAR16                                 *String
  )
{
	//Just a experiment, add a String to the output
	CHAR16 R[40]=L"LAB-Z:";
	StrCat(R,String);
	gST->ConOut->OutputString(This,R);
	
	return EFI_SUCCESS;
}
  
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{	
    //Create a fake EFI_SYSTEM_TABLE named myST
	memcpy(&myST,gST,sizeof(EFI_SYSTEM_TABLE));	
	//Test this EFI_SYSTEM_TABLE
	  gST->ConOut->OutputString(  gST->ConOut,L"Test of gSt 1\n\r");
	pmyST->ConOut->OutputString(pmyST->ConOut,L"Test of pmyST 2\n\r");
	
	//Create a fake EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
	memcpy(&myConOut,gST->ConOut,sizeof(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL));	
    //Test the fake ConOut
	pmyST->ConOut=&myConOut;
	
	//If we use pmyST->ConOut it will be an error
	pmyST->ConOut->OutputString(gST->ConOut,L"Test of myConOut 3\n\r");
	
	//Replace OutputString function with our function
	pmyST->ConOut->OutputString=&myOut;
	pmyST->ConOut->OutputString(gST->ConOut,L"Test of myConOut 4\n\r");
	
	return EFI_SUCCESS;
}

 

运行结果

image001

特别需要注意的地方,如果我们写成

pmyST->ConOut->OutputString(pmyST ->ConOut,L"Test of myConOut 3\n\r");

那么会碰到下面这个错误

image003

产生问题的代码在  \ShellPkg\Application\Shell\ConsoleLogger.c 中,看起来是向 Handle 安装另外一个 Protocol的时候出现报错信息。

  Status = gBS->InstallProtocolInterface(&gImageHandle, &gEfiSimpleTextOutProtocolGuid, EFI_NATIVE_INTERFACE, (VOID*)&((*ConsoleInfo)->OurConOut));
  if (EFI_ERROR(Status)) {
    SHELL_FREE_NON_NULL((*ConsoleInfo)->Buffer);
    SHELL_FREE_NON_NULL((*ConsoleInfo)->Attributes);
    SHELL_FREE_NON_NULL((*ConsoleInfo));
    *ConsoleInfo = NULL;
    return (Status);
  }

  gST->ConsoleOutHandle = gImageHandle;
  gST->ConOut           = &(*ConsoleInfo)->OurConOut;

  return (Status);

 

完整的代码下载:
MySt

Step to UEFI (91) Shell下的串口测试软件

这里【参考1】提供了一个Shell下调用 SerialIO Protocol 进行通讯的 Application。下面介绍一下如何重新编译和使用这个程序。

这里我使用UDK2014下面的 EADK作为编译环境:
1. 将代码目录copy到EADK的AppPkg\Application 下面
2. 需要在 AppPkg.dsc中加入下面的代码
[LibraryClasses] 下面加入
UefiHandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
3. 同样使用 build –a IA32 –p AppPkg\AppPkg.dsc 进行编译
4. 编译之后即生成了 Serial-Test.efi

我选择在 VirtualBox 中测试这个 Application。把它放在一个 ISO 之中,挂接启动到 UEFI虚拟机中,然后在 FS0: 下面即可看到这个 Application。

image001

同样,虚拟机中需要打开串口,我是采用 pipe通讯的方法在虚拟机中模拟出来com1。

image002
之后再打开putty,设置如下:
image003
双方连接之后即可进行通讯。
image004

image005

可以看到双方能够进行正常的通讯。
image006

本文提到的代码下载:
SerialTest
制作好的 ISO下载
test

特别提醒:VirtualBox 的BIOS有一些问题(至少5.0.20 r106931依然如此),无法彻底关闭Redirection功能,所以如果你要用它来实验一些串口相关内容时,需要特别注意,显示在虚拟机EFI Shell下面的东西还会发送一份到串口上。
参考:
1. https://github.com/tianocore/edk2/tree/master/OptionRomPkg/Bus/Usb/FtdiUsbSerialDxe

Step to UEFI (90) 给Shell 加入一个 command

上一次【参考1】介绍的屏幕旋转项目中还带有 Shell Command的内容。就是说可以把他调用驱动的Application”包”到Shell 中。这次介绍一下具体的实现。
源代码放置的位置目录和上一次实验相同。之后进行下面的步骤:
1.在 C:\EDK\ShellPkg\ShellPkg.dsc的 增加内容:

  ShellPkg/Application/Shell/Shell.inf {
    <LibraryClasses>
      NULL|ShellPkg/Library/UefiShellLevel2CommandsLib/UefiShellLevel2CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellLevel3CommandsLib/UefiShellLevel3CommandsLib.inf
!ifndef $(NO_SHELL_PROFILES)
      NULL|ShellPkg/Library/UefiShellDriver1CommandsLib/UefiShellDriver1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellInstall1CommandsLib/UefiShellInstall1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellDebug1CommandsLib/UefiShellDebug1CommandsLib.inf
      NULL|ShellPkg/Library/UefiShellNetwork1CommandsLib/UefiShellNetwork1CommandsLib.inf
!ifdef $(INCLUDE_DP)
      NULL|ShellPkg/Library/UefiDpLib/UefiDpLib.inf
!endif #$(INCLUDE_DP)
!endif #$(NO_SHELL_PROFILES)
##LABZDEBUG_Start
      NULL|GopRotatePkg/Library/GopRotateShellCommandLib/GopRotateShellCommandLib.inf
##LABZDEBUG_End	  
  }

 

2. 使用编译命令 重新编译 Shell。具体方法还可以在【参考2】看到

build by build -a IA32 -p ShellPkg\ShellPkg.dsc -b RELEASE

3.正常编译之后shell.efi 可以在这个目录中找到 C:\EDK\Build\Shell\RELEASE_MYTOOLS\IA32

4.从C:\EDK\Nt32Pkg\Nt32Pkg.fdf) 可以看到,NT32Pkg 用的是 FullShell

5.用生成的Shell .efi 替换C:\EDK\EdkShellBinPkg\FullShell\Ia32中的 Shell_Full.efi

6.用 Build 重新编译Nt32 项目,然后再用 Build run 运行模拟器

7.在模拟器中先加载Driver Load GopRotate.efi

8.枚举一下当前Shell中有 GraphicsOutput Protocol支持的 Device Handle。模拟器中有两个设备,分别对应2个窗口
image002

9.设置其中一个旋转,效果如下
image003

10.再旋转另外一个
image005

下面是重新编译通过的 shell 有兴趣的朋友可以直接使用

Shell_Full

参考:

1. http://www.lab-z.com/stu88/ Step to UEFI (88) 一个转屏驱动
2. http://www.lab-z.com/how2buildshell/ Step to UEFI (35) —– How to build Shell.efi

Step to UEFI (89) 内存访问

初学 Watcom C的时候遇到一个问题“如何访问指定的内存”。当时为了这个问题花费了不少功夫,最后才发现直接用指针就可以进行访问,因为过于简单以至于网上都没有人问过…….
最近看 UEFI 编程,同样也遇到了这个问题,我去查找了 mm命令的source code,看到了它使用了PCI Root Bridge I/O Protocol 这个Protocol,然后就去研究之。同样越研究越迷糊,最终发现虽然这个Protocol提供了内存访问函数,但是本质上依然使用指针来直接访问。出于保护模式下的内存,任何其他方法都有“脱了裤子放屁-----多此一举”之嫌。
参考 mm 源程序,很快写出代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include <Library/MemoryAllocationLib.h>
#include  <Protocol/DeviceIo.h>

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

typedef enum {
  EfiPciWidthUint8,
  EfiPciWidthUint16,
  EfiPciWidthUint32,
  EfiPciWidthUint64
} DUMMY;

VOID
ReadMem (
  EFI_IO_WIDTH  Width,
  UINT64        Address,
  UINTN         Size,
  VOID          *Buffer
  )
{
  do {
    if (Width == EfiPciWidthUint8) {
      *(UINT8 *) Buffer = *(UINT8 *) (UINTN) Address;
      Address -= 1;
    } else if (Width == EfiPciWidthUint16) {
      *(UINT16 *) Buffer = *(UINT16 *) (UINTN) Address;
      Address -= 2;
    } else if (Width == EfiPciWidthUint32) {
      *(UINT32 *) Buffer = *(UINT32 *) (UINTN) Address;
      Address -= 4;
    } else if (Width == EfiPciWidthUint64) {
      *(UINT64 *) Buffer = *(UINT64 *) (UINTN) Address;
      Address -= 8;
    } else {
      Print(L"Can't read memory at %X",Width);
      break;
    }
    //
    //
    //
    Size--;
  } while (Size > 0);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR8		*Mem1;
	UINT32		Buffer;

	Mem1=AllocatePool(4);
	*(Mem1+0)='L'; 
	*(Mem1+1)='A';
	*(Mem1+2)='B';
	*(Mem1+3)='Z';
	Print(L"Memory Address: %X\n",Mem1);
	Print(L"%X\n",*Mem1);
	Print(L"%X\n",*(Mem1+1));
	Print(L"%X\n",*(Mem1+2));
	Print(L"%X\n",*(Mem1+3));
	
	ReadMem(EfiPciWidthUint32, (UINT64)Mem1, 1, &Buffer);
	
	Print(L"Read[%X]=%X\n",Mem1,Buffer);
	
	FreePool(Mem1);
	
  return EFI_SUCCESS;
}

 

在NT32模拟器中实验时发现,模拟环境中不是所有的内存空间都是可以直接访问的。稍有不慎就会得到错误信息,模拟器也会随之崩溃。于是,代码是创建一个4 Bytes长的内存空间,写入一些字符,然后再 ReadMem 读取出来,这样做能够保证访问的内存是可以被正常操作的。

运行结果:

ma

参考的 mm.c 来自EfiShell 1.06\Shell\mm\mm.c。
mm

本文提到的完整代码下载
ReadMEM

Step to UEFI (88) 一个转屏驱动

这次介绍一个通过驱动程序旋转屏幕的项目,地址是https://github.com/apop2/GopRotate 。项目的简介是“A EDK2 Package that supplies a UEFI driver that will bind on top of Graphics Output Devices and rotate any BLT operations by 0, 90, 180 or 270 degrees.”。

本文并不打算做原理上的分析,只是介绍如何编译和实验。

实验环境是 UDK2014
1.在 C:\EDK\Nt32Pkg\Nt32Pkg.dsc 文件的 [Components] 段中添加下面的内容

  MdeModulePkg/Application/VariableInfo/VariableInfo.inf
  MdeModulePkg/Universal/PlatformDriOverrideDxe/PlatformDriOverrideDxe.inf
##LABZDebug_Start  
  GopRotatePkg/GopRotate/GopRotate.inf
##LABZDebug_End
###################################################################################################
#
# BuildOptions Section - Define the module specific tool chain flags that should be used as
#                        the default flags for a module. These flags are appended to any 

 

2.将 GopRotatePkg 目录拷贝到你UDK 的根目录下 例如: C:\EDK\
3.使用 Build 命令编译 NT32
4.使用 build run 运行模拟器
至此,驱动程序已经编译完成。下面要编译使用这个驱动的 Application。
5.将GopRot 按照一个普通的Application编译
编译完成后可以进行实验了。
6.使用 load goprotate.efi 加载驱动
image001

7.输入 goprot.efi 2 进行测试。
运行之前的屏幕是这样的:
image003
运行之后屏幕就变成这样了
image005

完整的代码下载

前面提到的驱动项目完整代码
GopRotatePkg

调用驱动的应用程序代码
GopRot

Step to UEFI (87) EFI_UNICODE_COLLATION_PROTOCOL

查看UEFI下的大小写转换函数的时候,偶然发现了EFI_UNICODE_COLLATION_PROTOCOL【参考1】提供了几个有意思的函数。

u1

具体的头文件定义在 \MdePkg\Include\Protocol\UnicodeCollation.h

///
/// The EFI_UNICODE_COLLATION_PROTOCOL is used to perform case-insensitive 
/// comparisons of strings. 
///
struct _EFI_UNICODE_COLLATION_PROTOCOL {
  EFI_UNICODE_COLLATION_STRICOLL    StriColl;   
  EFI_UNICODE_COLLATION_METAIMATCH  MetaiMatch;
  EFI_UNICODE_COLLATION_STRLWR      StrLwr;
  EFI_UNICODE_COLLATION_STRUPR      StrUpr;

  //
  // for supporting fat volumes
  //
  EFI_UNICODE_COLLATION_FATTOSTR    FatToStr;
  EFI_UNICODE_COLLATION_STRTOFAT    StrToFat;
  
  ///
  /// A Null-terminated ASCII string array that contains one or more language codes.
  /// When this field is used for UnicodeCollation2, it is specified in RFC 4646 format.
  /// When it is used for UnicodeCollation, it is specified in ISO 639-2 format.
  ///
  CHAR8                             *SupportedLanguages;
};

 

根据介绍,大概的介绍一些功能(如果你发现有错误,欢迎eMail指出)
EFI_UNICODE_COLLATION_STRICOLL StriColl; //大小写不敏感的比较函数
EFI_UNICODE_COLLATION_METAIMATCH MetaiMatch; //正则表达式匹配
EFI_UNICODE_COLLATION_STRLWR StrLwr; //字符串转小写
EFI_UNICODE_COLLATION_STRUPR StrUpr; //字符串转大写
EFI_UNICODE_COLLATION_FATTOSTR FatToStr; //8.3格式的OEM定义字符文件名转String
EFI_UNICODE_COLLATION_STRTOFAT StrToFat; //String转8.3格式的OEM定义字符
CHAR8 *SupportedLanguages; //列出当前系统支持的语言代码

之后,根据上面的介绍,编写一个测试例子:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include <Protocol/UnicodeCollation.h>

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_STATUS	Status;
	EFI_UNICODE_COLLATION_PROTOCOL *mUnicodeCollation;
	CHAR16	*TestStr=L"wWw.LaB-z.cOm";
	CHAR16  *Pattern1=L"w*";
	CHAR16  *Pattern2=L"*z.c*";
	CHAR16  *Pattern3=L"c*";
	
	Status = gBS->LocateProtocol(
				&gEfiUnicodeCollation2ProtocolGuid, 
				NULL, 
				&mUnicodeCollation);
    if (EFI_ERROR (Status)) {
      Print(L"Can't Locate Protocol\n");
      return Status;
    }
	
	mUnicodeCollation->StrLwr(mUnicodeCollation,TestStr);
	Print(L"%s\n",TestStr);
	
	mUnicodeCollation->StrUpr(mUnicodeCollation,TestStr);
	Print(L"%s\n",TestStr);

	Print(L"%d\n",(mUnicodeCollation->
					MetaiMatch(mUnicodeCollation,
					   TestStr,
					   Pattern1)));

	Print(L"%d\n",(mUnicodeCollation->
					MetaiMatch(mUnicodeCollation,
					   TestStr,
					   Pattern2)));

	Print(L"%d\n",(mUnicodeCollation->
					MetaiMatch(mUnicodeCollation,
					   TestStr,
					   Pattern3)));					   
  return EFI_SUCCESS;
}

 

运行结果:
u2

其中测试了大小写转换不必细说,多说两句关于正则表达式的用法:

	CHAR16	*TestStr=L"wWw.LaB-z.cOm";
	CHAR16  *Pattern1=L"w*";
	CHAR16  *Pattern2=L"*z.c*";
	CHAR16  *Pattern3=L"c*";

 

其中 “*” 表示匹配一个或者任意多个字符, Pattern1 表示的是“以w开头的字符串”;Pattern2 表示的是“中间含有 z.c 字符的字符串”;Pattern3 表示的是“以c开头的字符串”。最终运行结果如下:

完整的代码下载:
UnicTest

参考:
1. UEFI 2.4 P592

Step to UEFI (86) StartImage 加载CLib程序的解决方法

前面【参考1】提到了 StartImage 加载 CLib 编写Application 出错的原因,这篇文章介绍如何解决这个问题。
根据原因来看是因为找不到提供 Parameters 的Protocol,那么我们在调用之前给被加载的Application 装上需要的Protocol即可。安装 Protocol 需要用到 InstallProtocollInterface,具体定义如下【参考2】:
image001

欲安装的 Protocol 实例则是从加载程序(Exec6)上面取下来的。

没有多少人愿意看大篇幅的代码,我这里列下最关键的部分:

首先,取出当前的 Shell Interface, 不同的环境下还可以使用 Shell Parameter Protocol , NT32 环境下只支持前者

//如果你在实体机上发现有问题,那么可以考虑这段代码的问题
  Status = gBS->OpenProtocol(gImageHandle,
                             &gEfiShellInterfaceGuid,
                             (VOID **)&EfiShellInterface,
                             gImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_GET_PROTOCOL
                            );
  if (EFI_ERROR(Status)) {
	Print(L"Shell Parameters Protocol not Found!\r\n",Status);	
    return (Status);	
  } 
//之后,将取下来的 Protocol 安装给被加载的 Application
  Status = gBS->InstallProtocolInterface (
            &NewHandle,
            &gEfiShellInterfaceGuid,
            EFI_NATIVE_INTERFACE,
            EfiShellInterface
           ); 
  if (EFI_ERROR(Status)) {
	Print(L"Protocol Interface Installed fail!\r\n",Status);
	return (Status);
  } 	

 

最后,安装之后不能忘记 Uninstall,还要调用一下,特别注意第一个参数传递的不是指针。

image003

运行结果,可以看出 Hello1和 Hello2都可以被正常加载运行:

image005

看到这里,这篇文章就可以结束了,下面列出 Exec6 的代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

extern EFI_BOOT_SERVICES           	 *gBS;
extern EFI_SYSTEM_TABLE				 *gST;
extern EFI_RUNTIME_SERVICES 		 *gRT;

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;

extern EFI_HANDLE					 gImageHandle;

typedef struct {
  UINTN                       Signature;
  /// Image handle
  EFI_HANDLE                  Handle;   
  /// Image type
  UINTN                       Type;           
  /// If entrypoint has been called
  BOOLEAN                     Started;        
  /// The image's entry point
  EFI_IMAGE_ENTRY_POINT       EntryPoint;     
  /// loaded image protocol
  EFI_LOADED_IMAGE_PROTOCOL   Info; 
  /// Location in memory
  EFI_PHYSICAL_ADDRESS        ImageBasePage;    
} LOADED_IMAGE_PRIVATE_DATA_TEMP;

#define _CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

#define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) \
          _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info)
		  
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  EFI_HANDLE	NewHandle;
  EFI_STATUS	Status;
  LOADED_IMAGE_PRIVATE_DATA_TEMP      *private = NULL;  
  UINTN			ExitDataSizePtr;
  EFI_LOADED_IMAGE_PROTOCOL	*ImageInfo = NULL;
    EFI_SHELL_INTERFACE           *EfiShellInterface=NULL;
	
  if (Argc!=2) {
		Print(L"Usage: Exec4 FileName\n");
		return EFI_SUCCESS;
  }
  
  Print(L"File [%s]\n",Argv[1]);

  DevicePath=ShellGetDevicePath(Argv[1]);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  Status = gBS -> HandleProtocol (
						NewHandle,
						&gEfiLoadedImageProtocolGuid,
						&ImageInfo
						);
						
  private = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(ImageInfo);  

  Print(L"ImageBase in EFI_LOADED_IMAGE_PROTOCOL      [%lX]\n",ImageInfo->ImageBase);
  Print(L"ImageBase in LOADED_IMAGE_PRIVATE_DATA_TEMP [%lX]\n",private->ImageBasePage);
  Print(L"Entry Point [%lX]\n",private->EntryPoint);
							  
  Status = gBS->OpenProtocol(gImageHandle,
                             &gEfiShellInterfaceGuid,
                             (VOID **)&EfiShellInterface,
                             gImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_GET_PROTOCOL
                            );
  if (EFI_ERROR(Status)) {
	Print(L"Shell Parameters Protocol not Found!\r\n",Status);	
    return (Status);	
  } 

  Status = gBS->InstallProtocolInterface (
            &NewHandle,
            &gEfiShellInterfaceGuid,
            EFI_NATIVE_INTERFACE,
            EfiShellInterface
           ); 
  if (EFI_ERROR(Status)) {
	Print(L"Protocol Interface Installed fail!\r\n",Status);
	return (Status);
  } 		   
 
  Print(L"================================RUN================================\r\n",Status);
 
  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\r\n",Status);
    return (Status);
  }

  Print(L"===============================EXIT================================\r\n",Status);

  Status = gBS->UninstallProtocolInterface (
            NewHandle,
            &gEfiShellInterfaceGuid,
            EfiShellInterface
           ); 
  if (EFI_ERROR(Status)) {
	Print(L"Protocol Interface Uninstalled fail!\r\n",Status);
	return (Status);
  } 
  
  gBS->UnloadImage (NewHandle);  

  Print(L"NewHandle [%lX]\n",NewHandle);  
  
  return EFI_SUCCESS;
}

 

完整代码下载:
exec6

至此,终于回答了 StartImage 执行Application 的问题,如果你发现本文有任何问题欢迎给我留言,或者你有什么其他问题,同样可以给我发 e-Mail。

就是这样。

参考:
1. http://www.lab-z.com/stu85/ StartImage CLib
2. Uefi Spec 2.4 P153

Step to UEFI (85) StartImage CLib

之前文章中提到过,用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);

}

 

直接运行程序会不断暂停等待按键才继续:

image001

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;
}

 

直接运行,输出如下:
image002

用exec4加载之后输出如下:

image003

可以看到,两种方式下,运行路径是不同的。

最后的结论:产生问题的原因是,当我们用 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的入口分析

UEFI Tips 用按键做Pause

很多年前,我去AMI学习,偶然间看到他们在代码中加入通过 60/61 Port来读取键盘按键信息实现一个按需Delay ,深以为意。今天偶然间想起来,在调试Application 的时候,配合屏幕输出也可以用这样的方式来进行Debug。

下面是一个例子:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>


extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

#define SCAN_NULL       0x0000
#define SCAN_UP         0x0001
#define SCAN_DOWN       0x0002
#define SCAN_ESC        0x0017

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_INPUT_KEY	Key;
  EFI_STATUS		Status;  
  gST->ConOut->OutputString(gST->ConOut,L"Test Starting.....\n\r");
  gST->ConOut->OutputString(gST->ConOut,L"Waiting for UP_KEY\n\r");
  
  Key.ScanCode=SCAN_NULL;
  while (SCAN_UP!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
	}
  
  gST->ConOut->OutputString(gST->ConOut,L"Waiting for DOWN_KEY\n\r");	
  Key.ScanCode=SCAN_NULL;
  while (SCAN_DOWN!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
	}
  return EFI_SUCCESS;
}

 

通过按光标向上和向下继续运行,运行结果:

keypasue

完整文件下载

KeyPause