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 (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 (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的入口分析

Step to UEFI (84) ShellExecute 的使用

之前提到过,使用 LoadImage 和 StartImage 无法加载 CLib Build出来的 EFI Application。一种变通的方法是通过 ShellLib 下面的 ShellExcute 来调用其他的 EFI Application。

具体定义在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

**
  Cause the shell to parse and execute a command line.

  This function creates a nested instance of the shell and executes the specified
  command (CommandLine) with the specified environment (Environment). Upon return,
  the status code returned by the specified command is placed in StatusCode.
  If Environment is NULL, then the current environment is used and all changes made
  by the commands executed will be reflected in the current environment. If the
  Environment is non-NULL, then the changes made will be discarded.
  The CommandLine is executed from the current working directory on the current
  device.

  The EnvironmentVariables pararemeter is ignored in a pre-UEFI Shell 2.0
  environment.  The values pointed to by the parameters will be unchanged by the
  ShellExecute() function.  The Output parameter has no effect in a
  UEFI Shell 2.0 environment.

  @param[in] ParentHandle         The parent image starting the operation.
  @param[in] CommandLine          The pointer to a NULL terminated command line.
  @param[in] Output               True to display debug output.  False to hide it.
  @param[in] EnvironmentVariables Optional pointer to array of environment variables
                                  in the form "x=y".  If NULL, the current set is used.
  @param[out] Status              The status of the run command line.

  @retval EFI_SUCCESS             The operation completed sucessfully.  Status
                                  contains the status code returned.
  @retval EFI_INVALID_PARAMETER   A parameter contains an invalid value.
  @retval EFI_OUT_OF_RESOURCES    Out of resources.
  @retval EFI_UNSUPPORTED         The operation is not allowed.
**/
EFI_STATUS
EFIAPI
ShellExecute (
  IN EFI_HANDLE                 *ParentHandle,
  IN CHAR16                     *CommandLine OPTIONAL,
  IN BOOLEAN                    Output OPTIONAL,
  IN CHAR16                     **EnvironmentVariables OPTIONAL,
  OUT EFI_STATUS                *Status OPTIONAL
  )
{
  EFI_STATUS                CmdStatus;
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    //
    // Call UEFI Shell 2.0 version (not using Output parameter)
    //
    return (gEfiShellProtocol->Execute(ParentHandle,
                                      CommandLine,
                                      EnvironmentVariables,
                                      Status));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    //
    // Call EFI Shell version.
    // Due to oddity in the EFI shell we want to dereference the ParentHandle here
    //
    CmdStatus = (mEfiShellEnvironment2->Execute(*ParentHandle,
                                          CommandLine,
                                          Output));
    //
    // No Status output parameter so just use the returned status
    //
    if (Status != NULL) {
      *Status = CmdStatus;
    }
    //
    // If there was an error, we can't tell if it was from the command or from
    // the Execute() function, so we'll just assume the shell ran successfully
    // and the error came from the command.
    //
    return EFI_SUCCESS;
  }

  return (EFI_UNSUPPORTED);
}

 

调用参数如下:
ParentHandle 执行操作的父进程的Handle
CommandLine 要执行的命令行
Output 是否输出 Debug 信息(这里我没有搞明白,如果有清楚的朋友望不吝赐教)
EnvironmentVariables 环境变量

因为已经在头文件中定义过,所以我们可以直接调用。

比如用下面的方式可以执行 ls 命令:

Shell command
  CHAR16	  *S=L"ls";
  OpStat = ShellExecute( &MyHandle, S, FALSE, NULL, &CmdStat);

 

我们再编写一个简单的程序输出当前收到的命令行参数

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

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
	int	i;
	
	for (i=0;i<Argc; i++)	{
		Print(L"%S\n",Argv[i]);
	}
		
  return EFI_SUCCESS;
}

 

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Hello1
  FILE_GUID                      = 4ea97c46-7491-4dfd-0048-747010f3ce51
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  Hello1.c

[Packages]
  StdLib/StdLib.dec   
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 


[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib
  BaseLib
  BaseMemoryLib
  UefiLib

  
[Protocols]

  
[BuildOptions]

 

运行结果

se1

我们使用 ShellExecute 的代码

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.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;

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS  CmdStat;
  EFI_STATUS  OpStat;
  EFI_HANDLE  MyHandle = gImageHandle;
  CHAR16	  *S=L"hello1.efi a1 b2 c3";
  
  OpStat = ShellExecute( &MyHandle, S, FALSE, NULL, &CmdStat);
  
  return EFI_SUCCESS;
}

 

最后运行结果
se2

可以看到,能够调用hello1.efi 并且正确的传递了参数。

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

shellexecdemo

Step to UEFI (83) BlockIo Protocol

“因为硬盘是一种块设备,所以每个硬盘设备(硬盘设备包括分区设备)控制器都安装有一个 BlockIo 实例,一个 BlockIo2实例。BlockIo 提供了访问设备的阻塞函数,BlockIo2提供了访问设备的异步函数”【参考1】

blk2

blk1

这里提供一个枚举BlockIo,然后显示每一个 Media 属性的例子:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Protocol/BlockIo.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;
    UINTN					HandleCount,HandleIndex;
    EFI_HANDLE              *BlockControllerHandles = NULL;	
	EFI_BLOCK_IO_PROTOCOL   *BlockIo;
	
	//找到全部有 BlockIo Protocol 的Device
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiBlockIoProtocolGuid,
            NULL,
            &HandleCount,
            &BlockControllerHandles);  

   if (!EFI_ERROR(Status)) {
        //逐个打开 
        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
            /*打开EFI_BLOCK_IO_PROTOCOL  */ 
            Status = gBS->HandleProtocol(
                    BlockControllerHandles[HandleIndex],
                    &gEfiBlockIoProtocolGuid,
                    (VOID**)&BlockIo);
			//显示信息		
			Print(L"-->[Device]<--:%d\n",HandleIndex);
			Print(L"MediaId       :%0x\n",BlockIo->Media->MediaId);
    		Print(L"RemovableMedia:%0x\n",BlockIo->Media->RemovableMedia);
    		Print(L"MediaPresent  :%0x\n",BlockIo->Media->MediaPresent);
    		Print(L"ReadOnly      :%0x\n",BlockIo->Media->ReadOnly); 
    		Print(L"WriteCaching  :%0x\n",BlockIo->Media->WriteCaching);
    		Print(L"BlockSize     :%0x\n",BlockIo->Media->BlockSize);
    		Print(L"IoAlign       :%0x\n",BlockIo->Media->IoAlign);
    		Print(L"LastBlock     :%0x\n",BlockIo->Media->LastBlock);
    		Print(L"LogicalPartition :%0x\n",BlockIo->Media->LogicalPartition);
    		Print(L"LowestAlignedLba :%0x\n",BlockIo->Media->LowestAlignedLba);
    		Print(L"LogicalBlocksPerPhysicalBlock   : %0x\n",
			BlockIo->Media->LogicalBlocksPerPhysicalBlock);
    		Print(L"OptimalTransferLengthGranularity: %0x\n",
			BlockIo->Media->OptimalTransferLengthGranularity);
        }	//for (HandleIndex = 0;
		
        gBS->FreePool(BlockControllerHandles);
    }			
  return EFI_SUCCESS;
}

 

在 Nt32 虚拟机中运行的结果

blockioa

完整的代码和程序下载:

BIOTest

参考
1. UEFI 原理与编程 P139

Step to UEFI (82) NT32Pkg的Debug Message

最近在查看EDKII代码的时候忽然有一个奇怪的想法:在运行模拟器的时候(Build Run),我们可以在编译窗口看到很多输出的Debug 信息,那么我们是否可以在自己编写的Application中输出这样的信息?

最直接的想法是在 Application中调用 gWinNt ,但是如果要用这个东西,需要定义很多文件头,最麻烦的是这些头文件最后都要使用 Windows.h ,编译的时候总是无法通过。另外的方法是,在 Application 中调用诸如 WinNtThunkDxe 或者 WinNtSerialIoDxe 这样的Protocol,但是在编译使同样会遇到有上面的问题。我尝试了很多次都没有成功,最后只好放弃。

换一种思路,我们可以在NT32Pkg中留下可供调用的函数,然后在 Application 中Call这个函数。

最简单的功能就是用于系统重启的 gST->Reset 。经过查找,模拟器中实现这个功能的代码在 \Nt32Pkg\ResetRuntimeDxe\reset.c 。头定义如下

VOID
EFIAPI
WinNtResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
/*++

Routine Description:

  TODO: Add function description

Arguments:

  ResetType   - TODO: add argument description
  ResetStatus - TODO: add argument description
  DataSize    - TODO: add argument description
  ResetData   - TODO: add argument description

Returns:

  EFI_SUCCESS - TODO: Add description for return value

--*/

 

实现功能的代码只有一行 gWinNt->ExitProcess (0)。只要把这些语句注释掉,替换为我们的输出代码即可。参考 \Nt32Pkg\Library\PeiNt32OemHookStatusCodeLib\Nt32OemHookStatusCodeLib.c
,对编译窗口输出的语句如下:

 //
  // Callout to standard output.
  //
  mWinNt->WriteFile (
            mStdOut,
            Buffer,
            (DWORD)CharCount,
            (LPDWORD)&CharCount,
            NULL
            );

 

搬过来,写成下面的形式:

VOID
EFIAPI
WinNtResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
/*++

Routine Description:

  TODO: Add function description

Arguments:

  ResetType   - TODO: add argument description
  ResetStatus - TODO: add argument description
  DataSize    - TODO: add argument description
  ResetData   - TODO: add argument description

Returns:

  EFI_SUCCESS - TODO: Add description for return value

--*/
{
  CHAR8			 *R="www.lab-z.com \n\r";	
  UINTN           CharCount=AsciiStrLen(R);
  
  //
  // Cache of standard output handle .
  //
	HANDLE                      mStdOut;
  
  //
  // Cache standard output handle.
  //
  mStdOut = gWinNt->GetStdHandle (STD_OUTPUT_HANDLE);  
  
  //
  // Callout to standard output.
  //
  gWinNt->WriteFile (
            mStdOut,
            R,
            (DWORD)CharCount,
            (LPDWORD)&CharCount,
            NULL
            );
			
  //
  // BUGBUG Need to kill all console windows later
  //
  //
  // Discard ResetType, always return 0 as exit code
  //
  //gWinNt->ExitProcess (0);

  //
  // Should never go here
  //
  //ASSERT (FALSE);
}

 

特别注意:字符串是 Ascii 定义的,末尾必须是 \r\n ,否则不会立即显示。

最后运行结果,只要在模拟器中输入 reset 即可看到字符串,正常的动作应该是退出模拟器。

nt32a

nt32b

因为我修改掉了退出的方法,每次需要在编译窗口用 ctrl+c来结束模拟器了。

这里只是一个Demo,最好还是重新在EDKII代码中定义一个用来直接输出的接口。

Step to UEFI Tips

最近写程序遇到了一个非常奇怪的问题,化简问题如下:
1. 定义 CHAR8 大小的 Buffer[4]
2. 其中赋值为 128 82 0 0
3. 将他们通过下面的公式合成为一个 INT32
Buffer[0]+(Buffer[1]<<8) +(Buffer[2]<<16)+ (Buffer[3]<<24); 4. 我们期望得到 0x5280=21120, 但是给出来的结果却是 20864 编写程序如下,我们从命令行接收参数,然后转换为数值。

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

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR8	Buffer[4];
	UINTN	FileSize=0;

	Print(L"Argv [%s %s %s %s]\n",
			Argv[1],Argv[2],Argv[3],Argv[4]);	
			
	#pragma warning(disable:4244)
	Buffer[0]=StrDecimalToUintn(Argv[1]);
	Buffer[1]=StrDecimalToUintn(Argv[2]);
	Buffer[2]=StrDecimalToUintn(Argv[3]);
	Buffer[3]=StrDecimalToUintn(Argv[4]);

	Print(L"Buffer [%x %x %x %x]\n",
			Buffer[0],Buffer[1],Buffer[2],Buffer[3]);		
			
	FileSize=Buffer[0]+
		(Buffer[1]<<8)+
		(Buffer[2]<<16)+
		(Buffer[3]<<24);	

	Print(L"File Size = [%d]\n",FileSize);		
				
  return EFI_SUCCESS;
}

 

运行结果:

tipsa

为了Debug,需要打开生成汇编代码的功能,方法是在工程的 inf 文件 [BuildOptions] 下面加入如下语句:

[BuildOptions]
  MSFT:*_*_IA32_CC_FLAGS  = /FAsc

 

再次编译即可在Build目录下看到生成的 cod 文件

tipsb

关键代码部分:

; 28   : 
; 29   : 	Print(L"Buffer [%x %x %x %x]\n",
; 30   : 			Buffer[0],Buffer[1],Buffer[2],Buffer[3]);		

  00048	0f be 7c 24 3e	 movsx	 edi, BYTE PTR _Buffer$[esp+50]
  0004d	0f be 6c 24 3d	 movsx	 ebp, BYTE PTR _Buffer$[esp+49]
  00052	0f be f0	 movsx	 esi, al
  00055	56		 push	 esi
  00056	57		 push	 edi
  00057	0f be db	 movsx	 ebx, bl
  0005a	55		 push	 ebp
  0005b	53		 push	 ebx
  0005c	68 00 00 00 00	 push	 OFFSET ??_C@_1CM@LIBNEIP@?$AAB?$AAu?$AAf?$AAf?$AAe?$AAr?$AA?5?$AA?$FL?$AA?$CF?$AAx?$AA?5?$AA?$CF?$AAx?$AA?5?$AA?$CF?$AAx?$AA?5?$AA?$CF?$AAx?$AA?$FN?$AA?6?$AA?$AA@
  00061	e8 00 00 00 00	 call	 _Print

 

其中 push esi是 Buffer[3], push edi是 Buffer[2], push ebp 是 Buffer[1] , push ebx 是 Buffer[0],看起来一切正常。我们根据简单的结果进行计算0xFFFFFF80+0x5200+0x00+0x00=0x5180 就是说出现问题出在运行期的取值,和 Printf函数 没有关系。所以,关注点在为什么 1byte的0x80被定义成了 0xFFFFFF80。之后,我尝试化简程序,直接定义变量输出查看。我们定义三个变量 a b c用 printf 输出之:

	CHAR8	a=129;
	UINT8	b=130;
	unsigned char	c=131;

	Print(L"A= [%x]\n",a);					
	Print(L"B= [%x]\n",b);		
	Print(L"C= [%x]\n",c);	

 

运行结果:
tipsc

可以看到,如同我们的假设的, CHAR8 的 128被定义为 -127.
具体的 CHAR8定义可以在 MdePkg 中的 ProcessorBind.h 中找到:

  ///
  /// 1-byte Character.
  ///
  typedef char                CHAR8;

 

CHAR 本身是一种有符号的整数(所以才会有 unsigned char)。因此,这就是一个错误的定义数据类型而导致的问题,解决方法很简单,用 UINT8 做定义就好了(从ProcessorBind.h可以看出,UINT8是 unsigned char)。

最后,贴一下修改后的完整INF 文件以便参考:

## @file
#   A simple, basic, application showing how the Hello application could be
#   built using the "Standard C Libraries" from StdLib.
#
#  Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
#  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.
#
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = tipsa
  FILE_GUID                      = 4ea97c46-7491-4dfd-0064-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  tipsa.c

[Packages]
  StdLib/StdLib.dec   
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 


[LibraryClasses]
  LibC
  ShellCEntryLib   
  ShellLib
  
[Protocols]
	
[BuildOptions]
  MSFT:*_*_IA32_CC_FLAGS  = /FAsc
  

 

Step to UEFI (81) -----测试文件生成器

一些情况下,我们需要在 Shell 下面使用文件进行测试,这次编写一个工具,生成使用随机数填充的文件。为了校验方便,文件的末尾有一个 checksum,按照 32Bits 的 UINTN ,整个文件的和应该是 0 .

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

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

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

//copied from \StdLib\Include\stdlib.h
/** Expands to an integer constant expression that is the maximum value
    returned by the rand function.

    The value of the RAND_MAX macro shall be at least 32767.
**/
#define RAND_MAX  0x7fffffff

//Copied from \StdLib\LibC\StdLib\Rand.c
static UINT32 next = 1;

/** Compute a pseudo-random number.
  *
  * Compute x = (7^5 * x) mod (2^31 - 1)
  * without overflowing 31 bits:
  *      (2^31 - 1) = 127773 * (7^5) + 2836
  * From "Random number generators: good ones are hard to find",
  * Park and Miller, Communications of the ACM, vol. 31, no. 10,
  * October 1988, p. 1195.
**/
UINT32
rand()
{
  INT32 hi, lo, x;

  /* Can't be initialized with 0, so use another value. */
  if (next == 0)
    next = 123459876;
  hi = next / 127773;
  lo = next % 127773;
  x = 16807 * lo - 2836 * hi;
  if (x < 0)
    x += 0x7fffffff;
  return ((next = x) % ((UINT32)RAND_MAX + 1));
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	UINTN 							FileSize=0;
    EFI_STATUS          			Status;	
	EFI_FILE_PROTOCOL    			*Root;
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
	EFI_FILE_PROTOCOL 				*FileHandle=0;	
	UINTN		*HandleBuffer;
	UINT32		i,t,sum=0;

	if (Argc>1) {
		FileSize=StrDecimalToUintn(Argv[1])*1024;
		//Print(L"%d",FileSize);	
	}
	else
	{
		FileSize=1024*1024*10;
	}
	
	Status = gBS->LocateProtocol(
				&gEfiSimpleFileSystemProtocolGuid, 
				NULL,
				(VOID **)&SimpleFileSystem);
						
	if (EFI_ERROR(Status)) {
		Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
		return Status;	
	}

	Status = SimpleFileSystem->OpenVolume(SimpleFileSystem,&Root);
    if (EFI_ERROR(Status)) {
		    Print(L"OpenVolume error \r\n");
            return Status;	
	}
   
    Status = Root -> Open(Root,
			&FileHandle,
			(CHAR16 *) L"ztest.bin",
			EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
			0);
			
    if (EFI_ERROR(Status) || (FileHandle==0)) {
		    Print(L"Open error \r\n");
            return Status;	
	}	
	
	HandleBuffer = AllocateZeroPool(FileSize);
    if (HandleBuffer == NULL) {
		Print(L"Not enough memory!\n");
		return Status;
    }
	
	for (i=0;i<(FileSize-4)/4;i++)
		{
			t=rand();
			*(HandleBuffer+i)=t;
			sum=sum+t;
		}

	*(HandleBuffer+(FileSize-4)/4)=(UINT32)(0-sum);

	Status = FileHandle -> Write(FileHandle, &FileSize, HandleBuffer);
	
	Print(L"Write Done \r\n");	
	FreePool(HandleBuffer);
	Status  = FileHandle -> Close (FileHandle);
	
  return EFI_SUCCESS;
}

 

除了生成文件的工具,还有一个用来校验文件的工具,他的作用是将文件全部内容相加,查看结果是否为0.

#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>
#include <Library/MemoryAllocationLib.h>

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_FILE_HANDLE   FileHandle;
  RETURN_STATUS     Status;
  EFI_FILE_INFO     *FileInfo = NULL;
  UINT32	        *HandleBuffer=NULL;
  UINTN  			ReadSize;
  UINT32			i,sum=0;
  
  //Check if there is a parameter
  if (Argc == 1) {
	Print(L"Usage: crctest [filename]\n");
	return 0;
  }
  
  //Open the file given by the parameter
  Status = ShellOpenFileByName(Argv[1], (SHELL_FILE_HANDLE *)&FileHandle,
                               EFI_FILE_MODE_READ , 0);

  if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
      }			

  //Get file size	  
  FileInfo = ShellGetFileInfo( (SHELL_FILE_HANDLE)FileHandle);	

  //if the file size is not the multiple of 4, we don't check it!
  if ((UINTN) FileInfo-> FileSize % 4 !=0 ) {
	Print(L"We can't check this file as the filesize is wrong!\n");
  }
  
  //Allocate a memory buffer
  HandleBuffer = AllocateZeroPool((UINTN) FileInfo-> FileSize);
  if (HandleBuffer == NULL) {
      return (SHELL_OUT_OF_RESOURCES);   }

  ReadSize=(UINTN) FileInfo-> FileSize;
  
  //Load the whole file to the buffer
  Status = ShellReadFile(FileHandle,&ReadSize,HandleBuffer);
  
  for (i=0;i< FileInfo-> FileSize / 4;i++)
	{
		sum=sum+*(HandleBuffer+i);
	}
  if (sum==0) {
	Print(L"Pass!\n");
  }
  else
	{
		Print(L"Fail!\n");
	}
  
  FreePool(HandleBuffer);	
  FileHandle -> Close (FileHandle);  
  
  return EFI_SUCCESS;
}

 

完整的代码和 EFI 文件下载:
TestFileGen
Chker