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 (27) —– Shell GetCurDir

翻看 Shell Specification【参考 1】,发现上面介绍了一个Shell下取得当前目录的函数

getcurdir

看起来这个函数很简单,于是动手写程序。注意到 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中有声明,于是直接 extern EFI_SHELL_PROTOCOL *gEfiShellProtocol; 过来用。之所以能这样做,是因为这个位置是 Shell 程序在编译过程中特别加入的头,具体请参考之前的文章。

编译完成之后,进入虚拟环境实验,非常奇怪,每次运行都会出现错误提示,然后NT32就会崩溃掉。

getcurdirerror

真相到底是什么呢?一检查,gEfiShellProtocol == 0 。还要追根溯源,到定义他的地方去查看。

代码在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

为了Debug,在关键的地方添加上的一点代码,输出信息

  Status = gBS->OpenProtocol(
    ImageHandle,
    &gEfiShellParametersProtocolGuid,
    (VOID **)&gEfiShellParametersProtocol,
    ImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
   );
  if (EFI_ERROR(Status)) {
    gEfiShellParametersProtocol = NULL;
  }
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestA\n\r"); //LabZDebug
  if (gEfiShellParametersProtocol == NULL || gEfiShellProtocol == NULL) {
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestB\n\r"); //LabZDebug
    //
    // Moved to seperate function due to complexity
    //
    Status = ShellFindSE2(ImageHandle);

    if (EFI_ERROR(Status)) {
      DEBUG((DEBUG_ERROR, "Status: 0x%08x\r\n", Status));
      mEfiShellEnvironment2 = NULL;
    }
    Status = gBS->OpenProtocol(ImageHandle,
                               &gEfiShellInterfaceGuid,
                               (VOID **)&mEfiShellInterface,
                               ImageHandle,
                               NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL
                              );
    if (EFI_ERROR(Status)) {
      mEfiShellInterface = NULL;
    }
  }
if (0==gEfiShellProtocol) { 
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestE\n\r"); //LabZDebug
  }

SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestC\n\r"); //LabZDebug

 

输出的结果是: TestA TestB TestE 和 TestC。 就是说这段代码尝试取 Shell Protocol 但是没有取到。再仔细看一下代码,这里没有取得到。因此,此路不通。再向下看发现初始化了EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2,再看了一下他的定义,在\ShellPkg\Include\Protocol\EfiShellEnvironment2.h 中

/// EFI_SHELL_ENVIRONMENT2 protocol structure.
typedef struct {
  SHELLENV_EXECUTE                        Execute;
  SHELLENV_GET_ENV                        GetEnv;
  SHELLENV_GET_MAP                        GetMap;
  SHELLENV_ADD_CMD                        AddCmd;
  SHELLENV_ADD_PROT                       AddProt;
  SHELLENV_GET_PROT                       GetProt;
  SHELLENV_CUR_DIR                        CurDir;
  SHELLENV_FILE_META_ARG                  FileMetaArg;
  SHELLENV_FREE_FILE_LIST                 FreeFileList;

  //
  // The following services are only used by the shell itself.
  //
  SHELLENV_NEW_SHELL                      NewShell;
  SHELLENV_BATCH_IS_ACTIVE                BatchIsActive;

  SHELLENV_FREE_RESOURCES                 FreeResources;

  //
  // GUID to differentiate ShellEnvironment2 from ShellEnvironment.
  //
  EFI_GUID                                SESGuid;
  //
  // Major Version grows if shell environment interface has been changes.
  //
  UINT32                                  MajorVersion;
  UINT32                                  MinorVersion;
  SHELLENV_ENABLE_PAGE_BREAK              EnablePageBreak;
  SHELLENV_DISABLE_PAGE_BREAK             DisablePageBreak;
  SHELLENV_GET_PAGE_BREAK                 GetPageBreak;

  SHELLENV_SET_KEY_FILTER                 SetKeyFilter;
  SHELLENV_GET_KEY_FILTER                 GetKeyFilter;

  SHELLENV_GET_EXECUTION_BREAK            GetExecutionBreak;
  SHELLENV_INCREMENT_SHELL_NESTING_LEVEL  IncrementShellNestingLevel;
  SHELLENV_DECREMENT_SHELL_NESTING_LEVEL  DecrementShellNestingLevel;
  SHELLENV_IS_ROOT_SHELL                  IsRootShell;

  SHELLENV_CLOSE_CONSOLE_PROXY            CloseConsoleProxy;
  HANDLE_ENUMERATOR                       HandleEnumerator;
  PROTOCOL_INFO_ENUMERATOR                ProtocolInfoEnumerator;
  GET_DEVICE_NAME                         GetDeviceName;
  GET_SHELL_MODE                          GetShellMode;
  SHELLENV_NAME_TO_PATH                   NameToPath;
  SHELLENV_GET_FS_NAME                    GetFsName;
  SHELLENV_FILE_META_ARG_NO_WILDCARD      FileMetaArgNoWildCard;
  SHELLENV_DEL_DUP_FILE                   DelDupFileArg;
  SHELLENV_GET_FS_DEVICE_PATH             GetFsDevicePath;
} EFI_SHELL_ENVIRONMENT2;

因此,尝试一下SHELLENV_CUR_DIR. 这个函数头文件也在同一个文件中

/**
  This function returns a string array containing the current directory on
  a given device.

  If DeviceName is specified, then return the current shell directory on that
  device.  If DeviceName is NULL, then return the current directory on the
  current device.  The caller us responsible to free the returned string when
  no longer required.

  @param[in] DeviceName         The name of the device to get the current
                                directory on, or NULL for current device.

  @return String array with the current directory on the current or specified device.

**/
typedef
CHAR16*
(EFIAPI *SHELLENV_CUR_DIR) (
  IN CHAR16 *DeviceName OPTIONAL
  );

 

最后的 C 代码如下

#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_ENVIRONMENT2        *mEfiShellEnvironment2;

/***
  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
  )
{
  Print(L"%s",mEfiShellEnvironment2->CurDir(0));
  return EFI_SUCCESS;
}

 

对应的 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                      = getcurdir
  FILE_GUID                      = 4ea97c46-7491-4dfd-0027-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  GetCurDir.c

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

[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib

[Protocols]

[BuildOptions]

 

运行结果

getcurdir1

 

说明工作正常。

完整下载

GetCurDir

参考:

1.UEFI Shell Specification July 2, 2014 EFI_SHELL_PROTOCOL.GetCurDir() P24

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

 

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

DHT11 测试

DHT11 是一款温度湿度传感器。具体可以看【参考1】。参考中使用的是单独的元件,而我使用的是做好的模块,因此不需要额外的电阻。

dht11

硬件方面,只有三根线,GND VCC 和OUT。 对照参考中的程序,很容易上手。

dht11r

下面的代码也是参考中的代码,只是修改了一下波特率为 9600,这样方便我们测试之后,直接打开串口监视。

#include "dht11.h"

dht11 DHT11;
#define DHT11PIN 3 //DHT11 PIN 3 连接UNO 3
 
void setup()
{
  Serial.begin(9600);
  Serial.println("DHT11 TEST PROGRAM ");
  Serial.print("LIBRARY VERSION: ");
  Serial.println(DHT11LIB_VERSION);
  Serial.println();
}
 
void loop()
{
  Serial.println("\n");
 
  int chk = DHT11.read(DHT11PIN);
 
  Serial.print("Read sensor: ");
  switch (chk)
  {
    case DHTLIB_OK: 
                Serial.println("OK"); 
                break;
    case DHTLIB_ERROR_CHECKSUM: 
                Serial.println("Checksum error"); 
                break;
    case DHTLIB_ERROR_TIMEOUT: 
                Serial.println("Time out error"); 
                break;
    default: 
                Serial.println("Unknown error"); 
                break;
  }
  Serial.print("Humidity (%): ");
  Serial.println((float)DHT11.humidity, 2);
  Serial.print("Temperature (oC): ");
  Serial.println((float)DHT11.temperature-2, 2);
  delay(2000);
}

 

简单测试一下,对着传感器吹一口气,数值会有变化

dht11test

完整代码下载

DHT11Test
参考:

1.http://www.geek-workshop.com/forum.php?mod=viewthread&tid=997&extra=&highlight=dht11&page=1 DHT11 测试

pmason_rose 改造的遥控小车

两年前,再次学习单片机,此次入手的单片机比它的爹妈强多了。不仅把rs232接口用usb硬连接到pc而且直接写其rom!(本人不是硬件专业人士先进东西见的少)。琢磨着做个啥当学习目标呢,做个pc控制的小车吧。首先得解决mcu对数据的处理,恩,定义个包头aa55加个数据长度字0xh加个命令字0xh,再加上数据字。差资料显示状态机方式处理最好。写完后写上位程序。嘿嘿masm32是我的最爱,不过3天调试成功!为了增加可玩性,哈加入tcp方式传输命令流。一切调试ok,接上小车,呵呵大告成功!上传视频共同分享快乐!

pmason_rose 联系方式 pmason_rose@qq.com(332779423)