Step to UEFI (28) —– Shell GetCurDir 补遗

上一篇给出了GetCurDir的方法,不过这样玩多多少少看起来让人觉得不够简洁。于是又在EDK2 ShellPkg中翻了一下,在\ShellPkg\Include\Library\ShellLib.h 中有定义 ShellGetCurrentDir 函数

/**
  Retreives the current directory path.

  If the DeviceName is NULL, it returns the current device's current directory
  name. If the DeviceName is not NULL, it returns the current directory name
  on specified drive.

  @param[in] DeviceName         The name of the file system to get directory on.

  @retval NULL                  The directory does not exist.
  @retval != NULL               The directory.
**/
CONST CHAR16*
EFIAPI
ShellGetCurrentDir (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  );

 

看一下具体实现在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

/**
  Retreives the current directory path

  If the DeviceName is NULL, it returns the current device's current directory
  name. If the DeviceName is not NULL, it returns the current directory name
  on specified drive.

  @param DeviceName             the name of the drive to get directory on

  @retval NULL                  the directory does not exist
  @return != NULL               the directory
**/
CONST CHAR16*
EFIAPI
ShellGetCurrentDir (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetCurDir(DeviceName));
  }

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

  return (NULL);
}

 

就是说它内部实际上是直接判断了当前哪个Protocol能用,就用对应的来实现取得当前目录的功能。于是,直接使用它就能避免我们需要特别选择 Protocol 的烦恼。尝试编写程序如下

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

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  CHAR16 *R=0;
  
  R=(CHAR16 *)ShellGetCurrentDir(NULL);
  
  Print(L"%s",ShellGetCurrentDir(0));
  return EFI_SUCCESS;
}

 

同时给出对应的 INF,如果你再编译时出现Link之类的错误,最大的可能就是头文件和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                      = getcurdir2
  FILE_GUID                      = 4ea97c46-7491-4dfd-0028-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  GetCurDir2.c

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


[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib
  
[Protocols]
	
[BuildOptions]

 

从功能上来说,和之前的完全一样

28

GetCurDir2

Step to UEFI (26) —– Shell下Reboot System

研究一下如何 RESETSYSTEM,直接使用 RUNTIMESERVICES 是很好的选择。首先是查看资料 UEFI 2.4 是不二的选择。

7.5.1 Reset System

具体参数解释:EFI_RESET_TYPE 有: EfiResetCold,EfiResetWarm, EfiResetShutdown,和EfiResetPlatformSpecific。Cold是系统级别的完全重启。 Warm也是系统级别的重启,但主要是CPU级别的重启。(具体的话,以前确实有Warm Reset和Cold Reset的差别,但是我感觉实际BIOS设计上,这两个并不是区分的特别清晰。传统上的CPU的Warm Reset有可能导致几百次重启之后系统挂掉,而测试人员根本不会听取你关于他们差别的解释,于是最好的办法仍然是都用Cold Reset来解决)。Shutdown的话,就是关机了。EfiResetPlatformSpecific从解释上来看好像是将一个EFI_GUID指定的Reset类型存放在ResetData中(类似记录重启原因吗?)

EfiResetPlatformSpecific 太复杂,这里只是研究一下简单的 Reset 最后编写程序如下

 

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

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

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

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  gRT -> ResetSystem(EfiResetCold,EFI_SUCCESS,0,NULL);
  return EFI_SUCCESS;
}

运行之后系统就重启了。

下载   MainRST

Step to UEFI (25) —– 命令行参数为什么是Unicode

之前的文章【参考1】,提出了一个问题:为什么 CLIB 下面收到的参数 IN char **Argv 实际上是一个 Unicode ?

为了回答这个问题,还要在代码中寻找答案。同样,追踪一下当我们使用 CLIB 的时候,编译过程中程序被添加了什么。分析方法和之前的类似,我们最终得到下面这个结果:

_ModuleEntryPoint:\MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c 入口还是他

框架没变

ProcessLibraryConstructorList (1)
ProcessModuleEntryPointList (2)
ProcessLibraryDestructorList (3)

(1) ProcessLibraryConstructorList:\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c

  Status = UefiRuntimeServicesTableLibConstructor (ImageHandle, SystemTable);  (1.1)
  Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);(1.2)
  Status = UefiLibConstructor (ImageHandle, SystemTable);	(1.3)
  Status = ShellLibConstructor (ImageHandle, SystemTable);	(1.4)
  Status = UefiHiiServicesLibConstructor (ImageHandle, SystemTable); 	(1.5)
  Status = __wchar_construct (ImageHandle, SystemTable);	(1.6)

 

(1.1) UefiRuntimeServicesTableLibConstructor :\MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c

(1.2) UefiBootServicesTableLibConstructor :\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c

(1.3) UefiLibConstructor :\MdePkg\Library\UefiLib\UefiLib.c

(1.4) ShellLibConstructor :\ShellPkg\Library\UefiShellLib\UefiShellLib.c

ShellLibConstructorWorker //加载一些 Shell Protocol

(1.5) UefiHiiServicesLibConstructor :\MdeModulePkg\Library\UefiHiiServicesLib\UefiHiiServicesLib.c

(1.6) __wchar_construct :\StdLib\LibC\Wchar\ConsDecons.c

(2) ProcessModuleEntryPointList :\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c

	return ShellCEntryLib (ImageHandle, SystemTable); (2.1)

 

(2.1) ShellCEntryLib : \ShellPkg\Library\UefiShellCEntryLib\UefiShellCEntryLib.c

ReturnFromMain = ShellAppMain (
                       EfiShellParametersProtocol->Argc,
                       EfiShellParametersProtocol->Argv
                      );  

 

(2.1.1) ShellAppMain : \StdLib\LibC\Main\Main.c

	ExitVal = (INTN)main( (int)Argc, (wchar_t **)Argv);

 

对照 map 文件可以看到这个main就是我们写的 ArgTest中的Main

0001:000007d0 _ShellGetEnvironmentVariable 00000a50 f UefiShellLib:UefiShellLib.obj
0001:000007eb _ShellIsFile 00000a6b f UefiShellLib:UefiShellLib.obj
0001:00000830 _UefiHiiServicesLibConstructor 00000ab0 f UefiHiiServicesLib:UefiHiiServicesLib.obj
0001:000008a8 _main 00000b28 f ArgTest:ArgTest.obj
0001:00000962 _GetPerformanceCounter 00000be2 f BaseTimerLibNullTemplate:TimerLibNull.obj
0001:00000967 _GetPerformanceCounterProperties 00000be7 f BaseTimerLibNullTemplate:TimerLibNull.obj

(3) ProcessLibraryDestructorList :\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c

	  Status = __wchar_deconstruct (ImageHandle, SystemTable); (3.1)
	  Status = ShellLibDestructor (ImageHandle, SystemTable);  (3.2)

 

(3.1) __wchar_deconstruct :\StdLib\LibC\Wchar\ConsDecons.c
(3.2) ShellLibDestructor :\ShellPkg\Library\UefiShellLib\UefiShellLib.c

上面的调用关系可以用下面的图来简单总结一下

UEFICLIBAPP

====================================分割线====================================

我们在运行期确定是下面的代码来取得参数的

    //
    // 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)) {
      //
      // use shell 1.0 interface
      //
      ReturnFromMain = ShellAppMain (
                         EfiShellInterface->Argc,
                         EfiShellInterface->Argv
                        );
    } else {
      ASSERT(FALSE);
    }

 

对于这个EfiShellInterface ,我们可以在 《EFI Shell Developer’s Guide》 找到。

25

因此,可以看到,取出来的Argc就是CHAR16.

参考:

1.http://www.lab-z.com/step-to-uefi-15%EF%BC%89-%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0-again/

Step to UEFI (24) —– Print的换行问题

前面一篇介绍了 ConOut 的换行,然后问题就来了:为什么 Print 的String不需要 \n \r 呢?

这里继续分析:

首先看一下ClsTest.map

 0001:0000006d       _DebugAssert               000002cd f   BaseDebugLibNull:DebugLib.obj
 0001:0000006e       _DebugAssertEnabled        000002ce f   BaseDebugLibNull:DebugLib.obj
 0001:00000071       _InternalPrint             000002d1 f   UefiLib:UefiLibPrint.obj
 0001:000000b1       _Print                     00000311 f   UefiLib:UefiLibPrint.obj
 0001:000000cc       _InternalAllocatePool      0000032c f   UefiMemoryAllocationLib:MemoryAllocationLib.obj
 0001:000000f3       _UnicodeVSPrint            00000353 f   BasePrintLib:PrintLib.obj
 0001:00000112       _BasePrintLibFillBuffer    00000372 f   BasePrintLib:PrintLibInternal.obj

就是说 Print 是来自 UefiLibPrint.Obj,接下来搜索 UefiLibPrint 能找到2个,用实验的方法确定我们需要的是在 \MdePkg\Library\UefiLib\UefiLibPrint.c

INTN
EFIAPI
Print (
  IN CONST CHAR16  *Format,
  ...
  )
{
  VA_LIST Marker;
  UINTN   Return;

  VA_START (Marker, Format);

  Return = InternalPrint (Format, gST->ConOut, Marker);

  VA_END (Marker);

  return Return;
}

继续追 InternalPrint 发现它调用下面的语句

Return = UnicodeVSPrint (Buffer, BufferSize, Format, Marker);

而这个函数在 \MdePkg\Library\BasePrintLib\PrintLib.c 中

UINTN
EFIAPI
UnicodeVSPrint (
  OUT CHAR16        *StartOfBuffer,
  IN  UINTN         BufferSize,
  IN  CONST CHAR16  *FormatString,
  IN  VA_LIST       Marker
  )
{

  ASSERT_UNICODE_BUFFER (StartOfBuffer);
  ASSERT_UNICODE_BUFFER (FormatString);
  return BasePrintLibSPrintMarker ((CHAR8 *)StartOfBuffer, BufferSize >> 1, FORMAT_UNICODE | OUTPUT_UNICODE, (CHAR8 *)FormatString, Marker, NULL);
}

继续追踪 BasePrintLibSPrintMarker 发现他在 \MdePkg\Library\BasePrintLib\PrintLibInternal.c

其中有一个程序段,如下

    case '\r':
      Format += BytesPerFormatCharacter;
      FormatCharacter = ((*Format & 0xff) | (*(Format + 1) << 8)) & FormatMask;
      if (FormatCharacter == '\n') {
        //
        // Translate '\r\n' to '\r\n'
        //
        ArgumentString = "\r\n";
      } else {
        //
        // Translate '\r' to '\r'
        //
        ArgumentString = "\r";
        Format   -= BytesPerFormatCharacter;
      }
      break;

    case '\n':
      //
      // Translate '\n' to '\r\n' and '\n\r' to '\r\n'
      //
      ArgumentString = "\r\n";
      Format += BytesPerFormatCharacter;
      FormatCharacter = ((*Format & 0xff) | (*(Format + 1) << 8)) & FormatMask;
      if (FormatCharacter != '\r') {
        Format   -= BytesPerFormatCharacter;
      }
      break;

就是说,实际上他在检查字符串是否有 \n 和 \r如果有,那么用 \n \r 替换之(文件中有2处干这个事情的,第一个是在分析 “%”,第二个才是我们想要的)。为了验证,我们将上面这段替换的代码删除,重新编译,运行结果如下:

24

上面一次运行结果是修改之前,下面是修改之后。可以看到,当我们去掉那段自己添加 \n \r做结尾的代码之后,同样会出现只换行不移动到行首的问题。

结论:Print 之所以 \n 直接就能换行移动到行首,是因为他代码中有特殊处理。

Step to UEFI (23) —– ConOut ->OutputString 的换行问题

前面的一篇文章遇到了奇怪的问题,字符串输出看起来很不规整。于是研究一下为什么。

首先,试试 Application 是否也会有这样的显示问题,修改程序如下

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Library/UefiApplicationEntryPoint.h>

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

//
// Entry point function 
//
EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"www.lab-z.com\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n");  
 
  return EFI_SUCCESS;
}

 

我们看到有同样的现象。

23a
为了查看汇编级别的程序,我们可以在 ClsTest.inf 加入下面的代码

[BuildOptions]
  MSFT:*_*_IA32_CC_FLAGS  = /Oi- /FAcs

 

真正有效的成分是 /FAcs,这让编译器在编译过程中生成C语言和汇编代码对应的中间文件。

再次编译之后我们可以在 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest 找到 ClsTest.cod

文件。这就是我们想要的。它的内容如下:

; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01 

	TITLE	c:\edk2\AppPkg\Applications\ClsTest\ClsTest.c
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB OLDNAMES

PUBLIC	??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ ; `string'
PUBLIC	??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ ; `string'
PUBLIC	??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ ; `string'
PUBLIC	??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ ; `string'
;	COMDAT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '2', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '1', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ DB 'w'
	DB	00H, 'w', 00H, 'w', 00H, '.', 00H, 'l', 00H, 'a', 00H, 'b', 00H
	DB	'-', 00H, 'z', 00H, '.', 00H, 'c', 00H, 'o', 00H, 'm', 00H, 0aH
	DB	00H, 00H, 00H				; `string'
PUBLIC	_UefiMain
; Function compile flags: /Ogspy
; File c:\edk2\apppkg\applications\clstest\clstest.c
;	COMDAT _UefiMain
_TEXT	SEGMENT
_UefiMain PROC						; COMDAT

; 22   :   Print(L"www.lab-z.com\n");

  00000	68 00 00 00 00	 push	 OFFSET ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@
  00005	e8 00 00 00 00	 call	 _Print

; 23   :   gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\n");

  0000a	a1 00 00 00 00	 mov	 eax, DWORD PTR _gST
  0000f	8b 40 2c	 mov	 eax, DWORD PTR [eax+44]
  00012	c7 04 24 00 00
	00 00		 mov	 DWORD PTR [esp], OFFSET ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@
  00019	50		 push	 eax
  0001a	ff 50 04	 call	 DWORD PTR [eax+4]

; 24   :   gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\n");

  0001d	a1 00 00 00 00	 mov	 eax, DWORD PTR _gST
  00022	8b 40 2c	 mov	 eax, DWORD PTR [eax+44]
  00025	68 00 00 00 00	 push	 OFFSET ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@
  0002a	50		 push	 eax
  0002b	ff 50 04	 call	 DWORD PTR [eax+4]

; 25   :   gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n");  

  0002e	a1 00 00 00 00	 mov	 eax, DWORD PTR _gST
  00033	8b 40 2c	 mov	 eax, DWORD PTR [eax+44]
  00036	68 00 00 00 00	 push	 OFFSET ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@
  0003b	50		 push	 eax
  0003c	ff 50 04	 call	 DWORD PTR [eax+4]
  0003f	83 c4 18	 add	 esp, 24			; 00000018H

; 26   :   
; 27   :   return EFI_SUCCESS;

  00042	33 c0		 xor	 eax, eax

; 28   : }

  00044	c3		 ret	 0
_UefiMain ENDP
END

 

特别注意到,编译后,我们定义的字符串汇编写成下面这样形式的Unicode字符串

CONST	SEGMENT
??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS

 

可以看到上面只有 0ah 这是换行的意思,并没有“换行然后切换到下行行首”的意思。

找到原因,我们可以加上切换到行首,就是下面这个样子

  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test4\n\r");    
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test5\n\r");   

 

再编译查看生成的 COD 文件

CONST	SEGMENT
??_C@_1BK@FBECEOIH@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA5?$AA?6?$AA?$AN?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '5', 00H, 0aH, 00H, 0dH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BK@JNOIEOBJ@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA4?$AA?6?$AA?$AN?$AA?$AA@
CONST	SEGMENT
??_C@_1BK@JNOIEOBJ@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA4?$AA?6?$AA?$AN?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '4', 00H, 0aH, 00H, 0dH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '2', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '1', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ DB 'w'
	DB	00H, 'w', 00H, 'w', 00H, '.', 00H, 'l', 00H, 'a', 00H, 'b', 00H
	DB	'-', 00H, 'z', 00H, '.', 00H, 'c', 00H, 'o', 00H, 'm', 00H, 0aH
	DB	00H, 00H, 00H				; `string'

 

运行结果:

23b

最后,提一个问题,如果程序写成这个样子

EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"www.lab-z.com\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\r");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\r");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n");  
  
  return EFI_SUCCESS;
}

 

输出结果应该是什么样的呢?

Step to UEFI (22) —– Application的入口分析

研究一下:UEFI APP 在编译的时候会加入什么头。

使用上一次的示例程序 ClrTest。稍微修改一下,去掉清屏的调用以便我们能看清结果:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Library/UefiApplicationEntryPoint.h>

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

//
// Entry point function 
//
EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
   Print(L"www.lab-z.com\n");
   return EFI_SUCCESS;
}

编译命令是 build -p AppPkg\AppPkg.dsc

首先查看生成的 Makefile

在 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\Makefile

下面的语句指定了入口函数

DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP
/ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /DLL
/ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO
/BASE:0 /DRIVER /DEBUG

同样的 Makefile中,给出入口函数

IMAGE_ENTRY_POINT = _ModuleEntryPoint

顺便看一眼 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\OUTPUT\ClsTest.map

__ModuleEntryPoint 00000260 f UefiApplicationEntryPoint:ApplicationEntryPoint.obj

就是说 ModuleEntryPoint 是从 ApplicationEntryPoint.obj 中链接进来的

这个函数的头文件在 \MdePkg\Include\Library\UefiApplicationEntryPoint.h

再进一步查找 \MdePkg\Library\UefiApplicationEntryPoint\UefiApplicationEntryPoint.inf 其中给出了对应的函数体的位置

[Sources]
ApplicationEntryPoint.c

打开看看 \MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c

EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
...............
//
// 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);

其中调用了4个函数,下面分别按图索骥

  1. ProcessLibraryConstructorList 函数,他在

\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c

追进去,看一下

VOID
EFIAPI
ProcessLibraryConstructorList (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);
ASSERT_EFI_ERROR (Status);
Status = UefiRuntimeServicesTableLibConstructor (ImageHandle,SystemTable);
ASSERT_EFI_ERROR (Status);
Status = UefiLibConstructor (ImageHandle, SystemTable);
ASSERT_EFI_ERROR (Status);
}

 

1.1 追一下UefiBootServicesTableLibConstructor  发现它在\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c

EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Cache the Image Handle
//
gImageHandle = ImageHandle;  //看到这里也能明白之前文章中Extern 的gImageHandle哪里来了
ASSERT (gImageHandle != NULL);
//
// Cache pointer to the EFI System Table
//
gST = SystemTable;
ASSERT (gST != NULL);

//
// Cache pointer to the EFI Boot Services Table
//
gBS = SystemTable->BootServices;
ASSERT (gBS != NULL);

return EFI_SUCCESS;
}

 

1.2   再看看UefiRuntimeServicesTableLibConstructor 在 \MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c

EFI_STATUS
EFIAPI
UefiRuntimeServicesTableLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  //
  // Cache pointer to the EFI Runtime Services Table
  //
  gRT = SystemTable->RuntimeServices;
  ASSERT (gRT != NULL);
  return EFI_SUCCESS;
}

 

1.3   UefiLibConstructor  在\MdePkg\Library\UefiLib\UefiLib.c

EFI_STATUS
EFIAPI
UefiLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EFI_SUCCESS;
}

 

2. ProcessModuleEntryPointList在

\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c

EFI_STATUS
EFIAPI
ProcessModuleEntryPointList (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return UefiMain (ImageHandle, SystemTable); //至此,马上进入到了我们写的函数中
}

 

3.继续追ProcessLibraryDestructorList 这里应该是收尾的一些工作了

\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c

VOID
EFIAPI
ProcessLibraryDestructorList (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{

}

 

上面很罗嗦的说了很多,总结一下可以用下面的图表示调用过程

AppEntry

 

当然,说了很多如果没有实验不能保证正确性

验证的办法是在上述提到的过程里面插入下面的语句

SystemTable->ConOut->OutputString(SystemTable->ConOut,L"LABZ Test X.X\n");

如果输出结果能够正常输出全部插入的,那么表示找到的点是正确的。

结果

appentry2

============================================================

2024年10月9日

还可以在 UefiApplicationEntryPoint.inf文件中加入如下编译指令:


[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /FAsc /Od
  

之后,可以在对应的 Application 的 Build 目录下看到生成的 .COD 文件中出现的代码。

Step to UEFI (21) —– 清屏

想实现一个清屏的功能,刚开始在CLIB中翻了半天没找到,用工具直接搜索了一下 clrscr (应该在 conio.h 中)压根儿没找到。估计是 CLIB没有支持,只好换个方法。想起来Syetem Table中有 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 拿着SPEC翻了【参考1】一下,发现可以使用它的 ClearScreen 函数。

ClearScreen

写一个简单的程序验证之:

//
// ClearScreen
//
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Library/UefiApplicationEntryPoint.h>

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

//
// Entry point function 
//
EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
   INTN	i;

  for (i=0;i<1000;i++)
    {
	Print(L".");
    }
  Print(L".\n");

  gST -> ConOut -> SetAttribute(gST->ConOut,0x1);
  gST -> ConOut -> ClearScreen(gST->ConOut);
 
  //SystemTable ->ConOut ->ClearScreen(SystemTable ->ConOut);
  //Print(L"%lX\n",SystemTable ->ConOut ->ClearScreen);
  //Print(L"%lX\n",gST -> ConOut -> ClearScreen);
  //Print(L"%lX\n",gST->ConOut);
  return EFI_SUCCESS;
}

 

工作正常,能够清屏。

程序下载 ClsTest

参考:

1.UEFI Spec 2.4 P459

2.http://biosren.com/viewthread.php?tid=3050&highlight=clearscreen 关于SHELL下面修改(前)背景色

Step to UEFI (20) —– 再论“ CLib 获得 ImageHandle”的问题

在翻看之前写的 《Step to UEFI (16) —– CLIB下获得 SystemTable》 【参考1】的时候偶然注意到:

加入头文件 #include Library/UefiBootServicesTableLib.h ,这个头文件的内容是

#ifndef __UEFI_BOOT_SERVICES_TABLE_LIB_H__
#define __UEFI_BOOT_SERVICES_TABLE_LIB_H__

///
/// Cache the Image Handle
///
extern EFI_HANDLE gImageHandle;

///
/// Cache pointer to the EFI System Table
///
extern EFI_SYSTEM_TABLE *gST;

///
/// Cache pointer to the EFI Boot Services Table
///
extern EFI_BOOT_SERVICES *gBS;

#endif

除了之前关注到的各种Table,居然还有一个 gImageHandle !!!

马上动手写了一个程序验证:

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

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

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

//SimpleTextInputEx.harderr
//#define	EFI_SHIFT_STATE_VALID		0x80000000
//#define EFI_LEFT_CONTROL_PRESSED	0x00000002

EFI_STATUS
EFIAPI
NotificationFunction(
	IN EFI_KEY_DATA	*KeyData
)
{
	printf("This is a test from www.lab-z.com \n");		
	return(EFI_SUCCESS);
}

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
	EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleEx;
	EFI_KEY_DATA	KeyData;
	EFI_STATUS		Status;
	EFI_HANDLE		CtrlCNotifyHandle = NULL;
	INTN			i;

 	Status = gBS -> OpenProtocol(
		gST -> ConsoleInHandle,
		&gEfiSimpleTextInputExProtocolGuid,
		(VOID**)&SimpleEx,
		gImageHandle,
		NULL,
		EFI_OPEN_PROTOCOL_GET_PROTOCOL);

	if (EFI_ERROR(Status)) {
		printf("OpenProtocol ERROR!!\n");
	}		

	KeyData.KeyState.KeyToggleState = 0;
	KeyData.Key.ScanCode			= 0;
	KeyData.KeyState.KeyShiftState 	= EFI_SHIFT_STATE_VALID | EFI_LEFT_CONTROL_PRESSED;
	KeyData.Key.UnicodeChar			= L's';

	Status = SimpleEx -> RegisterKeyNotify (
			SimpleEx,
			&KeyData,
			NotificationFunction,
			&CtrlCNotifyHandle);

	for (i=0;i<200;i++)
	 {
		printf("Test\n");		
		gBS -> Stall (5000);
      }

	Status = SimpleEx -> UnregisterKeyNotify(SimpleEx, CtrlCNotifyHandle);

  return EFI_SUCCESS;
}

运行结果和之前文章一样,就是说这个方法是OK的!

mainh2

 

程序下载 Mainh2

==============================================================================

这样的话,之前提到的方法完全就是“脱了裤子放屁—-多此一举”。

另外,再说点好玩的事情,前几天朋友圈里有人转了一篇文章,吹嘘他的“重要”发明“竖版世界地图”。文章的观点基本上是:中国最大的敌人是美国。美国和中国有多远?正常人看普通的世界地图会被误导,以为要横跨太平洋。但是实际上中国和美国如果从北极的方向走才是最近的。结论:看普通的世界地图会导致如此不堪的错误,用我发明的竖版世界地图就万事大吉。文章还在暗示设计北斗导航系统,差点因为看普通的地图而导致大错。

这里放一个“竖版的世界地图”【来自红网,Baidu搜出来的结果】

U9383P704DT20140623085032

怎么说呢,看着是挺别扭的,地球仿佛将菊花对准了你…………

我想看国家疆域,你把一个水球给我干嘛?

上小学和初中时学过一点地理知识。对于地图来说,基本上就是准确的看着不舒服,舒服看着不准确(投影决定的),根本原因是地球不是立方体,再仔细追究丫也不是标准的球体。

又想起来看过一篇文章,经常出国的人看每个国家的地图都觉得很别扭“为毛中国不是在中间?”

除了习惯的力量,还有这种竖版的地图,上面是北还是南?另外,莫非搞卫星的人不知道世界上还有一种“用空间中到定点的距离小于或等于定长的所有点组成图形的结构”结合“通用标记地面地理特性的方法”贴图在一起的通常我们称之为“地球仪”的东西吗?

说到这里,我也想有一个“伟大”的发明,那就是按照地球表面的凸凹形状做一个地球仪—-已经有了?没关系,那我就按照表面G的不同绘制吧。我也编一个故事:NK发射火箭没人考虑G点差别,一直无法满意。后来偶尔最高统帅看到我的地球仪,茅塞顿开………现在我的地球仪对NK是禁运的………

建议将 Google Earth 禁掉,我相信我的发明能更加“伟大”。

参考:

1.http://www.lab-z.com/step-to-uefi-16%EF%BC%89-clib%E4%B8%8B%E8%8E%B7%E5%BE%97-systemtable/ Step to UEFI (16) —– CLIB下获得 SystemTable

Step to UEFI (19) —– Shell Command Stall 命令的分析

随手翻了一下 UEFI Shell 的 Source ,看了一下 Stall 命令觉得挺有意思,研究了一下。

这个命令的介绍如下,实现了一个很简单的功能:暂停 xxx 微妙 (MicroSeconds 百万分之一秒)

stallcmd

对应的代码在 \Shell\stall\stall.c 可以看到

关键部分的代码是:

  Microseconds = Atoi (ChkPck.VarList->VarStr);  //要暂停的时间 UINTN Microseconds;
  PrintToken (STRING_TOKEN (STR_STALL_FOR), HiiStallHandle, Microseconds);
  Remaining = Microseconds % 100000;
  Status    = RT->GetTime (&TmpTime, NULL);      //取当前时间 
  if (EFI_ERROR (Status)) {
    Status = EFI_ABORTED;
    goto Done;
  }

  MilliSeconds1 = (TmpTime.Hour * 3600 + TmpTime.Minute * 60 + TmpTime.Second) * 1000; //当前时间转化为毫秒
  while (Microseconds - Remaining) {
    //
    // Break the execution?
    //
    if (GetExecutionBreak ()) {  //允许打断
      goto Done;
    }

    BS->Stall (100000);          //暂停 1秒
    Status = RT->GetTime (&TmpTime, NULL);
    if (EFI_ERROR (Status)) {
      Status = EFI_ABORTED;
      goto Done;
    }

    MilliSeconds2 = (TmpTime.Hour * 3600 + TmpTime.Minute * 60 + TmpTime.Second) * 1000;
    if (MilliSeconds2 < MilliSeconds1) { //如果发生跨天的情况,算了一下 0xFFFFFFFF 个微秒最多只能跨一天
      MilliSeconds2 += (24 * 3600 * 1000);
    }

    if ((Microseconds - Remaining) / 1000 <= (MilliSeconds2 - MilliSeconds1)) {
      break;
    }
  } // while (Microseconds - Remaining) 结束

  BS->Stall (Remaining);

 

看起来整个过程就是:使用 BS 的 Stall 不断做短时间的暂停,直到完成预订的Stall 时间。不过想不太明白,这样做同直接 BS -> Stall 指定的时间相比能获得什么好处?莫非是给一个能够用 Ctrl-Break 的机会么?

参考:
1. Shell Stall 命令的介绍,来自 UEFI Shell Specification Rev 2.1
2. BS->Stall 的介绍,来自 UEFI Spec 2.4
bsstall

小问题:UEFI 下,Print 怎样显示十进制的64位的无符号数呢?

有人【参考1】,提了一个问题:UEFI 下,Print 怎样显示十进制的64位的无符号数呢?

首先查了一下资料【参考2】

printfll

于是,试试 %llu 看看, ll 表示 long long, u表示十进制无符号数,代码

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{

  printf("%llu\n",0xffffffffL);  

  return EFI_SUCCESS;
}

 

执行结果

quest1

参考:

1.http://biosren.com/viewthread.php?tid=7419&highlight= UEFI 下,Print 怎样显示十进制的64位的无符号数呢?

2.http://www.cplusplus.com/reference/cstdio/printf/