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

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

Step to UEFI (80) —–取得汉字的字形

EFI 在设计之初就考虑了多语言的支持,使用HII可以轻松的实现汉字的显示。本篇文章介绍获得汉字字形的其他方法,掌握这种方法之后可以在没有HII支持的情况下显示汉字。当然,程序只是为了演示原理,介绍如何读取16×16的汉字字形信息,没有转为图形。
比如:“宋”字查询到的区位码是4346 【参考3】,意思是区码为43,位码是46。计算这个字在字库中的方法是:((43-1)*94+(46-1))*32=6828。之后,在字库文件的 6828偏移处连续读取32个字节即可。

shz

代码如下:

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

#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_BOOT_SERVICES *gBS;

#define FONT_SIZE (16)
#define HZ_INDEX(hz) ((hz[0] - 1) * 94 + (hz[1] -1))*32
#define DOTS_BYTES (FONT_SIZE * FONT_SIZE / 8)

int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_FILE_HANDLE FileHandle;
RETURN_STATUS Status;
EFI_FILE_INFO *FileInfo = NULL;
EFI_HANDLE *HandleBuffer=NULL;
UINTN ReadSize;
UINTN i,j;
UINT8 HZChar[2] = {43,46};
CHAR8 *c;
CHAR8 k;

//Open the file given by the parameter
Status = ShellOpenFileByName(L"HZK16K.BIN",
(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);

//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);
if(Status != RETURN_SUCCESS) {
Print(L"ReadFile failed!\n");
return EFI_SUCCESS;
}

for (i=0;i<DOTS_BYTES;i++)
{
c=((UINT8*)HandleBuffer)+HZ_INDEX(HZChar)+i;
k=*c;
for (j=0;j<8;j++)
{
if (0 == (k & 0x80))
{
Print(L" ");
}
else
{
Print(L"OO");
}
k=k<<1;
}
if ((i+1)%2==0) {Print(L"\n");}
}

FreePool(HandleBuffer);
ShellCloseFile((SHELL_FILE_HANDLE *)&FileHandle);

}
 

 

运行结果(特别注意要把字库文件放在Fsnt0:这样的目录下):

image002

更换一下区位码,我们还可以取得“我”的字形。
image004

完整的代码下载:
HZ

最后,关于【参考1】的代码多说两句。其中有unsigned char word[3] = “我”; 这样直接的定义,这是因为很久很久之前,为了编便于 PC处理汉字定义一个汉字由两个大于127的ASCII码组成。组成的规则是:区码+A0,位码+A0。比如,我在中文环境下定义一个“宋”,
image008

然后切换到英文环境下打开,看到的是2个ASCII码,

image010

如果再切换到十六进制编辑,会看到 CB CE (前提是保存为 ANSI格式,如果你存为unicode,看到的又是另外的东西)

image012

时代已经变了,对于 Windows 编程来说上述的知识都已经过时,如果你需要搞嵌入式开发,还是值得认真学习和理解。
另外,PC刚开始流行的时候,很长一段时间都有汉字不适合PC处理等等的言论,对于普通用户来说,汉字的输入也是很大的困扰。而最终的解决,我认为是人们强烈的交流的需求使得这样的问题很快被克服掉了。时至今日,我仍然能记得同一个寝室的胖子在他的 Nokia手机上,在十几个按键上运指如飞和各种MM聊得火热。很快,没人再认为汉字在PC的普及上是一个问题。

参考 :
1.https://blog.twofei.com/embedded/hzk.html HZK16汉字16*16点阵字库的使用及示例程序
2.http://blog.csdn.net/turingo/article/details/8191712 图灵狗的专栏
3.http://www.jscj.com/index/gb2312.php 汉字区位码查询系统 (具体)

==============================================================
2018年12月30日 补充: 在【参考1】的文章中提供了一个取得字模的代码,我在 Win10 下实验过,很好用:

代码如下:

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

int main(void)
{
	FILE* fphzk = NULL;
	int i, j, k, offset;
	int flag;
	unsigned char buffer[32];
	unsigned char word[5];
	unsigned char key[8] = {
		0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
	};
	fphzk = fopen("hzk16", "rb");
	if(fphzk == NULL){
		fprintf(stderr, "error hzk16\n");
		return 1;
	}
	while(1){
		printf("输入要生成字模的汉字(多个):");
		for(;;){
			fgets((char*)word, 3, stdin);
			if(*word == '\n') 
				break;
			offset = (94*(unsigned int)(word[0]-0xa0-1)+(word[1]-0xa0-1))*32;
			fseek(fphzk, offset, SEEK_SET);
			fread(buffer, 1, 32, fphzk);
			for(k=0; k<16; k++){
				for(j=0; j<2; j++){
					for(i=0; i<8; i++){
						flag = buffer[k*2+j]&key[i];
						printf("%s", flag?"●":"○");
					}
				}
				printf("\n");
			}
			printf("uchar code key[32] = {");
			for(k=0; k<31; k++){
				printf("0x%02X,", buffer[k]);
			}
			printf("0x%02X};\n", buffer[31]);
			printf("\n");
		}
	}
	fclose(fphzk);
	fphzk = NULL;
	return 0;
}

 

如果你是英文的OS,需要先切换内码为 CP936

Step to UEFI (79) —–取得字形

我们可以通过EFI_HII_FONT_PROTOCOL【参考1】 中的 GetGlyph来取得一些字符的字形定义。

gly1

GetGlyph 的原型可以在 \MdePkg\Include\Protocol\HiiFont.h 中找到:

/**

  Convert the glyph for a single character into a bitmap.

  @param This       A pointer to the EFI_HII_FONT_PROTOCOL instance.

  @param Char       The character to retrieve.

  @param StringInfo Points to the string font and color
                    information or NULL if the string should use
                    the default system font and color.

  @param Blt        This must point to a NULL on entry. A buffer will
                    be allocated to hold the output and the pointer
                    updated on exit. It is the caller's responsibility
                    to free this buffer.

  @param Baseline   The number of pixels from the bottom of the bitmap
                    to the baseline.


  @retval EFI_SUCCESS             The glyph bitmap created.

  @retval EFI_OUT_OF_RESOURCES    Unable to allocate the output buffer Blt.

  @retval EFI_WARN_UNKNOWN_GLYPH  The glyph was unknown and was
                                  replaced with the glyph for
                                  Unicode character code 0xFFFD.

  @retval EFI_INVALID_PARAMETER   Blt is NULL, or Width is NULL, or
                                  Height is NULL


**/
typedef
EFI_STATUS
(EFIAPI *EFI_HII_GET_GLYPH)(
  IN CONST  EFI_HII_FONT_PROTOCOL *This,
  IN CONST  CHAR16                Char,
  IN CONST  EFI_FONT_DISPLAY_INFO *StringInfo,
  OUT       EFI_IMAGE_OUTPUT      **Blt,
  OUT       UINTN                 *Baseline OPTIONAL
);

 

其中 Char 是你要取对应字形的文字(特别注意是单个的CHAR16),StringInfo 为 NULL时取得的是系统默认的字体,输出结果在 Blt 中。最后一项含义我不清楚……
再看一下输出结果是 EFI_IMAGE_OUTPUT。它的定义在 \MdePkg\Include\Protocol\HiiImage.h中。其中含有一个Union的定义,在我们这里使用时,会按照 EFI_GRAPHICS_OUTPUT_BLT_PIXEL 给出。

最后写一个简单的测试程序如下,功能是取得“z”字符的字形。

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

#include <Protocol/HiiFont.h>

extern EFI_BOOT_SERVICES         *gBS;
 
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS	Status = 0;
	UINTN		BaseLine;
	UINTN		i,j;	
    EFI_HII_FONT_PROTOCOL	*HiiFont = 0;
	EFI_IMAGE_OUTPUT		*Blt=NULL;
	CHAR8		*c,*p;
	
	Status = gBS->LocateProtocol (&gEfiHiiFontProtocolGuid, NULL, (VOID **) &HiiFont);
	if (Status!=EFI_SUCCESS) {
		Print(L"Error when LocateProtocol gEfiHiiFontProtocolGuid. Code[%r]\n",Status);
		return EFI_SUCCESS;
	}
   Status = HiiFont->GetGlyph (
				HiiFont,	//Protocol instance
				L'Z',		//Show char 'Z'
				NULL,		//Use the defualt system font and color
				&Blt,		//GLYPH information
				&BaseLine); //I don't know
				
	Print(L"This is [%d]x[%d]\n",Blt->Width,Blt->Height);
	
	c=(CHAR8*) Blt->Image.Bitmap;			
	for (j=0;j<Blt->Height;j++)
		{
			p=c;
			for (i=0;i<Blt->Width;i++)
				{
					Print(L"%2X",(*c)&0xFF);
					c=c+4;
				}
			Print(L"   ");	
			c=p+1;	
			for (i=0;i<Blt->Width;i++)
				{
					Print(L"%2X",(*c)&0xFF);
					c=c+4;
				}
			Print(L"   ");					
			c=p+2;	
			for (i=0;i<Blt->Width;i++)
				{
					Print(L"%2X",(*c)&0xFF);
					c=c+4;
				}
			c=p+(Blt->Width*4);
			Print(L"\n");	
		}	
		
	c=(CHAR8*) Blt->Image.Bitmap;			
	for (j=0;j<Blt->Height;j++)
		{
			for (i=0;i<Blt->Width;i++)
				{
					if (*c!=0) {
						Print(L"*");
					}
					else
					  {
						Print(L" ");
					  }	
					c=c+4;
				}
			Print(L"\n");	
		}			
		
	return EFI_SUCCESS;
}

 

运行结果,首先输出的是 R G B 数组,隐隐约约能看到其中有一个形状

gly2

程序后面有一个判断,直接输出星号和空格,结果如下,这样就看到非常清楚了。

gly3

完整的代码下载
GetGlyph

唯一的问题是:我还不知道取得这个东西能有什么用途…….

参考:
1.UEFI spec 2.4 P1711

Step to UEFI —– TIPs

最近调试程序的时候遇到一个奇怪的 Warning ,查了一会才找到原因:

c:\edk\AppPkg\Applications\C4066\C4066.c(17) : warning C4066: characters beyond first in wide-character constant ignored

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  Print(L'[%d]',2015);
  return EFI_SUCCESS;
}

 

错误产生的原因是:把双引号写成了单引号,编译器以为你要定义一个 CHAR16 的字符,所以要忽略一些东西。修改的方法是,改成双引号即可。

参考:

1.https://msdn.microsoft.com/en-us/library/aa748819(v=vs.60).aspx
Compiler Warning (level 3) C4066

Visual Studio 6.0
characters beyond first in wide-character constant ignored
The compiler processes only the first character of a wide-character constant.

Step to UEFI (77) —–改造 Stall和 MV

我们在 EDK2 的代码中能看到 Shell下部分命令的代码,这里介绍如何把这样的代码提取出来做成能够独立编译和运行的程序。简单起见,以 Stall 命令和 MV 命令为例。

经过试验,这些命令中使用到的大部分函数都可以在 ShellLib.h 中找到,我们要做的只是把这个文件copy一份到我们程序下面。
最后修改之后的程序如下:

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

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] = L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] = L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]= L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_PROBLEM_VAL[] = L"Error. The argument '%s' has incorrect value.\r\n";
CONST CHAR16 STR_STALL_FAILED[]    = L"Error. BootService Stall() failed with %r.\r\n";

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS          Status;
	LIST_ENTRY          *Package;
	CHAR16              *ProblemParam;
    SHELL_STATUS        ShellStatus;
	UINT64              Intermediate;
  
	//
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM,ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  }  else {
    if (ShellCommandLineGetRawValue(Package, 2) != NULL) {
      Print(STR_GEN_TOO_MANY);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else if (ShellCommandLineGetRawValue(Package, 1) == NULL) {
      Print(STR_GEN_TOO_FEW);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      Status = ShellConvertStringToUint64(
				ShellCommandLineGetRawValue(Package, 1), &Intermediate, FALSE, FALSE);
      if (EFI_ERROR(Status) || ((UINT64)(UINTN)(Intermediate)) != Intermediate) {
        Print(STR_GEN_PROBLEM_VAL, ShellCommandLineGetRawValue(Package, 1));
        ShellStatus = SHELL_INVALID_PARAMETER;
      } else {
        Status = gBS->Stall((UINTN)Intermediate);
        if (EFI_ERROR(Status)) {
          Print(STR_STALL_FAILED, Status);
          ShellStatus = SHELL_DEVICE_ERROR;
        }
      }
    }
    ShellCommandLineFreeVarList (Package);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zstall

完整代码下载(特别注意,涉及到时钟的程序在NT32模拟环境中和实际环境中存在很大差别,不要在实际环境中使用为模拟环境编译的EFI文件)。
zStall

总结一下,如果想把一个命令改造为独立的程序,需要做下面的事情:

1. 增加 #define ASSERT(Expression) 这个定义,上面代码为了简单,我只是定义它为空
2. 拷贝 ShellLib.h 到你的代码目录下,然后用 “”直接使用
3. 改造程序中定义的字符串,这些字符串都是定义在 UNI 文件中。如果你没有多语言的需要,可以像我这样在代码中重新定义一次
4. 将所有的 ShellPrintHiiEx 都修改为 Print

下面再用 MV 命令的代码练习一下

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

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UnicodeCollation.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] =    L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]=    L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] =    L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_NO_CWD[]  =    L"Error. No current directory is specified.\r\n";
CONST CHAR16 STR_GEN_FILE_NF[] =    L"Error. File '%s' was not found.\r\n";
CONST CHAR16 STR_GEN_ERR_FILE[]=    L"Error. File '%s' error: %r\r\n";
CONST CHAR16 STR_MV_INV_SUB[]  = L"Error. Cannot move a directory into itself or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_RO[]   = L"Error. Cannot move a read-only File or Directory.\r\n";
CONST CHAR16 STR_MV_INV_CWD[]  = L"Error. Cannot move current working directory or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_FS[]   = L"Error. Cannot move between file systems.\r\n";
CONST CHAR16 STR_GEN_NO_MEM[]  = L"Error. Memory is not available.\r\n";
CONST CHAR16 STR_GEN_ERR_UK[]  = L"Error: %r\r\n";
CONST CHAR16 STR_MV_OUTPUT[]   = L"Moving %s -> %s\r\n";
CONST CHAR16 STR_GEN_RES_OK[]  = L"- [ok]\r\n";
CONST CHAR16 STR_GEN_MARG_ERROR[] = L"Error. The destination '%s' is ambigious.\r\n";
CONST CHAR16 STR_GEN_FILE_ERROR[] = L"Error. The destination is an existant file '%s'.\r\n";
      
//copy from \ShellPkg\Library\BasePathLib\BasePathLib.c
/**
  Removes the last directory or file entry in a path by changing the last
  L'\' to a CHAR_NULL.

  @param[in, out] Path    The pointer to the path to modify.

  @retval FALSE     Nothing was found to remove.
  @retval TRUE      A directory or file was removed.
**/
BOOLEAN
EFIAPI
PathRemoveLastItem(
  IN OUT CHAR16 *Path
  )
{
  CHAR16        *Walker;
  CHAR16        *LastSlash;
  //
  // get directory name from path... ('chop' off extra)
  //
  for ( Walker = Path, LastSlash = NULL
      ; Walker != NULL && *Walker != CHAR_NULL
      ; Walker++
     ){
    if (*Walker == L'\\' && *(Walker + 1) != CHAR_NULL) {
      LastSlash = Walker+1;
    }
  }
  if (LastSlash != NULL) {
    *LastSlash = CHAR_NULL;
    return (TRUE);
  }
  return (FALSE);
}

/**
  Function to take a destination path that might contain wildcards and verify
  that there is only a single possible target (IE we cant have wildcards that
  have 2 possible destination).

  if the result is sucessful the caller must free *DestPathPointer.

  @param[in] DestDir               The original path to the destination.
  @param[in, out] DestPathPointer  A pointer to the callee allocated final path.
  @param[in] Cwd                   A pointer to the current working directory.

  @retval SHELL_INVALID_PARAMETER  The DestDir could not be resolved to a location.
  @retval SHELL_INVALID_PARAMETER  The DestDir could be resolved to more than 1 location.
  @retval SHELL_INVALID_PARAMETER  Cwd is required and is NULL.
  @retval SHELL_SUCCESS            The operation was sucessful.
**/
SHELL_STATUS
EFIAPI
GetDestinationLocation(
  IN CONST CHAR16               *DestDir,
  IN OUT CHAR16                 **DestPathPointer,
  IN CONST CHAR16               *Cwd
  )
{
  EFI_SHELL_FILE_INFO       *DestList;
  EFI_SHELL_FILE_INFO       *Node;
  CHAR16                    *DestPath;
  UINTN                     NewSize;
  UINTN                     CurrentSize;

  DestList = NULL;
  DestPath = NULL;

  if (StrStr(DestDir, L"\\") == DestDir) {
    if (Cwd == NULL) {
      return SHELL_INVALID_PARAMETER;
    }
    DestPath = AllocateZeroPool(StrSize(Cwd));
    if (DestPath == NULL) {
      return (SHELL_OUT_OF_RESOURCES);
    }
    StrCpy(DestPath, Cwd);
    while (PathRemoveLastItem(DestPath)) ;

    //
    // Append DestDir beyond '\' which may be present
    //
    CurrentSize = StrSize(DestPath);
    StrnCatGrow(&DestPath, &CurrentSize, &DestDir[1], 0);

    *DestPathPointer =  DestPath;
    return (SHELL_SUCCESS);
  }
  //
  // get the destination path
  //
  ShellOpenFileMetaArg((CHAR16*)DestDir, EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ|EFI_FILE_MODE_CREATE, &DestList);
  if (DestList == NULL || IsListEmpty(&DestList->Link)) {
    //
    // Not existing... must be renaming
    //
    if (StrStr(DestDir, L":") == NULL) {
      if (Cwd == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_INVALID_PARAMETER);
      }
      NewSize = StrSize(Cwd);
      NewSize += StrSize(DestDir);
      DestPath = AllocateZeroPool(NewSize);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Cwd);
      if (DestPath[StrLen(DestPath)-1] != L'\\' && DestDir[0] != L'\\') {
        StrCat(DestPath, L"\\");
      } else if (DestPath[StrLen(DestPath)-1] == L'\\' && DestDir[0] == L'\\') {
        ((CHAR16*)DestPath)[StrLen(DestPath)-1] = CHAR_NULL;
      }
      StrCat(DestPath, DestDir);
    } else {
      ASSERT(DestPath == NULL);
      DestPath = StrnCatGrow(&DestPath, NULL, DestDir, 0);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
    }
  } else {
    Node = (EFI_SHELL_FILE_INFO*)GetFirstNode(&DestList->Link);
    //
    // Make sure there is only 1 node in the list.
    //
    if (!IsNodeAtEnd(&DestList->Link, &Node->Link)) {
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_MARG_ERROR, DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
    if (ShellIsDirectory(Node->FullName)==EFI_SUCCESS) {
      DestPath = AllocateZeroPool(StrSize(Node->FullName)+sizeof(CHAR16));
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Node->FullName);
      StrCat(DestPath, L"\\");
    } else {
      //
      // cant move onto another file.
      //
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_FILE_ERROR,DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
  }

  *DestPathPointer =  DestPath;
  ShellCloseFileMetaArg(&DestList);

  return (SHELL_SUCCESS);
}


/**
  Function to clean up paths.  
  
  - Single periods in the path are removed.
  - Double periods in the path are removed along with a single parent directory.
  - Forward slashes L'/' are converted to backward slashes L'\'.

  This will be done inline and the existing buffer may be larger than required 
  upon completion.

  @param[in] Path       The pointer to the string containing the path.

  @retval NULL          An error occured.
  @return Path in all other instances.
**/
CHAR16*
EFIAPI
PathCleanUpDirectories(
  IN CHAR16 *Path
  )
{
  CHAR16  *TempString;
  UINTN   TempSize;
  if (Path==NULL) {
    return(NULL);
  }

  //
  // Fix up the '/' vs '\'
  //
  for (TempString = Path ; TempString != NULL && *TempString != CHAR_NULL ; TempString++) {
    if (*TempString == L'/') {
      *TempString = L'\\';
    }
  }

  //
  // Fix up the ..
  //
  while ((TempString = StrStr(Path, L"\\..\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 4;
    PathRemoveLastItem(Path);
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\..")) != NULL && *(TempString + 3) == CHAR_NULL) {
    *TempString = CHAR_NULL;
    PathRemoveLastItem(Path);
  }

  //
  // Fix up the .
  //
  while ((TempString = StrStr(Path, L"\\.\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 2;
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\.")) != NULL && *(TempString + 2) == CHAR_NULL) {
    *(TempString + 1) = CHAR_NULL;
  }



  return (Path);
}

STATIC EFI_UNICODE_COLLATION_PROTOCOL   *mUnicodeCollation = NULL;

/**
  Function to compare 2 strings without regard to case of the characters.

  @param[in] Buffer1            Pointer to String to compare.
  @param[in] Buffer2            Pointer to second String to compare.

  @retval 0                     Buffer1 equal to Buffer2.
  @return < 0                   Buffer1 is less than Buffer2.
  @return > 0                   Buffer1 is greater than Buffer2.
**/
INTN
EFIAPI
StringNoCaseCompare (
  IN  CONST VOID             *Buffer1,
  IN  CONST VOID             *Buffer2
  )
{
  EFI_STATUS                Status;
  if (mUnicodeCollation == NULL) {
    Status = gBS->LocateProtocol(
      &gEfiUnicodeCollation2ProtocolGuid,
      NULL,
      (VOID**)&mUnicodeCollation);

    ASSERT(Status);
  }

  return (mUnicodeCollation->StriColl(
    mUnicodeCollation,
    *(CHAR16**)Buffer1,
    *(CHAR16**)Buffer2));
}

/**
  Function to validate that moving a specific file (FileName) to a specific
  location (DestPath) is valid.

  This function will verify that the destination is not a subdirectory of
  FullName, that the Current working Directory is not being moved, and that
  the directory is not read only.

  if the move is invalid this function will report the error to StdOut.

  @param FullName [in]    The name of the file to move.
  @param Cwd      [in]    The current working directory
  @param DestPath [in]    The target location to move to
  @param Attribute[in]    The Attribute of the file

  @retval TRUE        The move is valid
  @retval FALSE       The move is not
**/
BOOLEAN
EFIAPI
IsValidMove(
  IN CONST CHAR16   *FullName,
  IN CONST CHAR16   *Cwd,
  IN CONST CHAR16   *DestPath,
  IN CONST UINT64   Attribute
  )
{
  CHAR16  *Test;
  CHAR16  *Test1;
  CHAR16  *TestWalker;
  INTN    Result;
  UINTN   TempLen;
  if (Cwd != NULL && StrCmp(FullName, Cwd) == 0) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_CWD);
    return (FALSE);
  }
  Test = NULL;
  Test = StrnCatGrow(&Test, NULL, DestPath, 0);
  TestWalker = Test;
  ASSERT(TestWalker != NULL);
  while(*TestWalker == L'\\') {
    TestWalker++;
  }
  while(TestWalker != NULL && TestWalker[StrLen(TestWalker)-1] == L'\\') {
    TestWalker[StrLen(TestWalker)-1] = CHAR_NULL;
  }
  ASSERT(TestWalker != NULL);
  ASSERT(FullName   != NULL);
  if (StrStr(FullName, TestWalker) != 0) {
    TempLen = StrLen(FullName);
    if (StrStr(FullName, TestWalker) != FullName                    // not the first items... (could below it)
      && TempLen <= (StrLen(TestWalker) + 1)
      && StrStr(FullName+StrLen(TestWalker) + 1, L"\\") == NULL) {
      //
      // Invalid move
      //
      Print(STR_MV_INV_SUB);
      FreePool(Test);
      return (FALSE);
    }
  }
  FreePool(Test);
  if (StrStr(DestPath, FullName) != 0 && StrStr(DestPath, FullName) != DestPath) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_SUB);
    return (FALSE);
  }
  if ((Attribute & EFI_FILE_READ_ONLY) != 0) {
    //
    // invalid to move read only
    //
    Print(STR_MV_INV_RO);
    return (FALSE);
  }
  Test  = StrStr(FullName, L":");
  Test1 = StrStr(DestPath, L":");
  if (Test1 != NULL && Test  != NULL) {
    *Test  = CHAR_NULL;
    *Test1 = CHAR_NULL;
    Result = StringNoCaseCompare(&FullName, &DestPath);
    *Test  = L':';
    *Test1 = L':';
    if (Result != 0) {
      Print(STR_MV_INV_FS);
      return (FALSE);
    }
  }
  return (TRUE);
}

/**
  function to take a list of files to move and a destination location and do
  the verification and moving of those files to that location.  This function
  will report any errors to the user and continue to move the rest of the files.

  @param[in] FileList           A LIST_ENTRY* based list of files to move
  @param[out] Resp              pointer to response from question.  Pass back on looped calling
  @param[in] DestDir            the destination location

  @retval SHELL_SUCCESS             the files were all moved.
  @retval SHELL_INVALID_PARAMETER   a parameter was invalid
  @retval SHELL_SECURITY_VIOLATION  a security violation ocurred
  @retval SHELL_WRITE_PROTECTED     the destination was write protected
  @retval SHELL_OUT_OF_RESOURCES    a memory allocation failed
**/
SHELL_STATUS
EFIAPI
ValidateAndMoveFiles(
  IN CONST EFI_SHELL_FILE_INFO  *FileList,
  OUT VOID                      **Resp,
  IN CONST CHAR16               *DestDir
  )
{
  EFI_STATUS                Status;
  CHAR16                    *DestPath;
  CONST CHAR16              *Cwd;
  SHELL_STATUS              ShellStatus;
  CONST EFI_SHELL_FILE_INFO *Node;
  EFI_FILE_INFO             *NewFileInfo;
  CHAR16                    *TempLocation;
  UINTN                     NewSize;
  UINTN                     Length;
  VOID                      *Response;
  SHELL_FILE_HANDLE         DestHandle;
  CHAR16 STR_GEN_DEST_EXIST_OVR[] = L"Destination file already exists.  Overwrite? Yes, No, All, Cancel ";
  
  ASSERT(FileList != NULL);
  ASSERT(DestDir  != NULL);

  DestPath = NULL;
  Cwd      = ShellGetCurrentDir(NULL);
  Response = *Resp;

  //
  // Get and validate the destination location
  //
  ShellStatus = GetDestinationLocation(DestDir, &DestPath, Cwd);
  if (ShellStatus != SHELL_SUCCESS) {
    return (ShellStatus);
  }
  DestPath = PathCleanUpDirectories(DestPath);

  //
  // Go through the list of files and directories to move...
  //
  for (Node = (EFI_SHELL_FILE_INFO *)GetFirstNode(&FileList->Link)
    ;  !IsNull(&FileList->Link, &Node->Link)
    ;  Node = (EFI_SHELL_FILE_INFO *)GetNextNode(&FileList->Link, &Node->Link)
   ){
    if (ShellGetExecutionBreakFlag()) {
      break;
    }
    ASSERT(Node->FileName != NULL);
    ASSERT(Node->FullName != NULL);

    //
    // skip the directory traversing stuff...
    //
    if (StrCmp(Node->FileName, L".") == 0 || StrCmp(Node->FileName, L"..") == 0) {
      continue;
    }

    //
    // Validate that the move is valid
    //
    if (!IsValidMove(Node->FullName, Cwd, DestPath, Node->Info->Attribute)) {
      ShellStatus = SHELL_INVALID_PARAMETER;
      continue;
    }

    //
    // Chop off map info from "DestPath"
    //
    if ((TempLocation = StrStr(DestPath, L":")) != NULL) {
      CopyMem(DestPath, TempLocation+1, StrSize(TempLocation+1));
    }

    //
    // construct the new file info block
    //
    NewSize = StrSize(DestPath);
    NewSize += StrSize(Node->FileName) + SIZE_OF_EFI_FILE_INFO + sizeof(CHAR16);
    NewFileInfo = AllocateZeroPool(NewSize);
    if (NewFileInfo == NULL) {
      Print(STR_GEN_NO_MEM);
      ShellStatus = SHELL_OUT_OF_RESOURCES;
    } else {
      CopyMem(NewFileInfo, Node->Info, SIZE_OF_EFI_FILE_INFO);
      if (DestPath[0] != L'\\') {
        StrCpy(NewFileInfo->FileName, L"\\");
        StrCat(NewFileInfo->FileName, DestPath);
      } else {
        StrCpy(NewFileInfo->FileName, DestPath);
      }
      Length = StrLen(NewFileInfo->FileName);
      if (Length > 0) {
        Length--;
      }
      if (NewFileInfo->FileName[Length] == L'\\') {
        if (Node->FileName[0] == L'\\') {
          //
          // Don't allow for double slashes. Eliminate one of them.
          //
          NewFileInfo->FileName[Length] = CHAR_NULL;
        }
        StrCat(NewFileInfo->FileName, Node->FileName);
      }
      NewFileInfo->Size = SIZE_OF_EFI_FILE_INFO + StrSize(NewFileInfo->FileName);
      Print(STR_MV_OUTPUT, Node->FullName, NewFileInfo->FileName);

      if (!EFI_ERROR(ShellFileExists(NewFileInfo->FileName))) {
        if (Response == NULL) {
          ShellPromptForResponse(ShellPromptResponseTypeYesNoAllCancel, STR_GEN_DEST_EXIST_OVR,  &Response);
        }
        switch (*(SHELL_PROMPT_RESPONSE*)Response) {
          case ShellPromptResponseNo:
            FreePool(NewFileInfo);
            continue;
          case ShellPromptResponseCancel:
            *Resp = Response;
            //
            // indicate to stop everything
            //
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return (SHELL_ABORTED);
          case ShellPromptResponseAll:
            *Resp = Response;
            break;
          case ShellPromptResponseYes:
            FreePool(Response);
            break;
          default:
            FreePool(Response);
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return SHELL_ABORTED;
        }
        Status = ShellOpenFileByName(NewFileInfo->FileName, &DestHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0);
        ShellDeleteFile(&DestHandle);
      }


      //
      // Perform the move operation
      //
      Status = ShellSetFileInfo(Node->Handle, NewFileInfo);

      //
      // Free the info object we used...
      //
      FreePool(NewFileInfo);

      //
      // Check our result
      //
      if (EFI_ERROR(Status)) {
        Print(STR_GEN_ERR_UK, Status);
        ShellStatus = SHELL_INVALID_PARAMETER;
        if (Status == EFI_SECURITY_VIOLATION) {
          ShellStatus = SHELL_SECURITY_VIOLATION;
        } else if (Status == EFI_WRITE_PROTECTED) {
          ShellStatus = SHELL_WRITE_PROTECTED;
        } else if (Status == EFI_OUT_OF_RESOURCES) {
          ShellStatus = SHELL_OUT_OF_RESOURCES;
        } else if (Status == EFI_DEVICE_ERROR) {
          ShellStatus = SHELL_DEVICE_ERROR;
        } else if (Status == EFI_ACCESS_DENIED) {
          ShellStatus = SHELL_ACCESS_DENIED;
        }
      } else {
        Print( L"%s", STR_GEN_RES_OK);
      }
    }
  } // for loop

  FreePool(DestPath);
  return (ShellStatus);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS          Status;
  LIST_ENTRY          *Package;
  CHAR16              *ProblemParam;
  SHELL_STATUS        ShellStatus;
  UINTN               ParamCount;
  UINTN               LoopCounter;
  EFI_SHELL_FILE_INFO *FileList;
  VOID                *Response;

  ProblemParam        = NULL;
  ShellStatus         = SHELL_SUCCESS;
  ParamCount          = 0;
  FileList            = NULL;
  Response            = NULL;

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM , ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    if (ShellCommandLineGetFlag(Package, L"-?")) {
      ASSERT(FALSE);
    }

    switch (ParamCount = ShellCommandLineGetCount(Package)) {
      case 0:
      case 1:
        //
        // we have insufficient parameters
        //
        Print(STR_GEN_TOO_FEW);
        ShellStatus = SHELL_INVALID_PARAMETER;
        break;
      case 2:
        //
        // must have valid CWD for single parameter...
        //
        if (ShellGetCurrentDir(NULL) == NULL){
          Print(STR_GEN_NO_CWD);
          ShellStatus = SHELL_INVALID_PARAMETER;
        } else {
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, 1), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, 1));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            //
            ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellGetCurrentDir(NULL));
          }
        }

        break;
      default:
        ///@todo make sure this works with error half way through and continues...
        for (ParamCount--, LoopCounter = 1 ; LoopCounter < ParamCount ; LoopCounter++) {
          if (ShellGetExecutionBreakFlag()) {
            break;
          }
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, LoopCounter), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, LoopCounter));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            // Only change ShellStatus if it's sucessful
            //
            if (ShellStatus == SHELL_SUCCESS) {
              ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            } else {
              ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            }
          }
          if (FileList != NULL && !IsListEmpty(&FileList->Link)) {
            Status = ShellCloseFileMetaArg(&FileList);
            if (EFI_ERROR(Status) && ShellStatus == SHELL_SUCCESS) {
              ShellStatus = SHELL_ACCESS_DENIED;
              Print(STR_GEN_ERR_FILE , ShellCommandLineGetRawValue(Package, 1), ShellStatus|MAX_BIT);
            }
          }
        }
        break;
    } // switch on parameter count

    if (FileList != NULL) {
      ShellCloseFileMetaArg(&FileList);
    }

    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  SHELL_FREE_NON_NULL(Response);

  if (ShellGetExecutionBreakFlag()) {
    return (SHELL_ABORTED);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zmv1

完整代码下载:

zMv