ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

UEFI之Dubug

2021-06-06 21:00:14  阅读:598  来源: 互联网

标签:CODE Dubug ENABLED EFI UEFI DEBUG PROPERTY define


概要

在UEFI的调试中,我们必不可少的是打印Debug,通常用DEBUG宏来完成,
所有需要打印的模块中都需要包含一个Debug 头文件Library/DebugLib.h和一个Debug库DebugLib

比如:
DEBUG((DEBUG_INFO, "(Agent)SetLastAttemp - %r\n", VarStatus));

使用介绍

需要注意的有几点:

  1. 括号是双重的,下面以第一个参数,第二个参数来分别称呼EFI_D_xx和字符串;

  2. 第二个参数是一个字符串,这个字符串里面也可以带格式化标志,下面是DEBUG常用的标志,与标准C中略有不同:

  %a:表示ASCII字符串;
  %c:表示ASCII字符;
  %d:表示十进制,可以有%02d这样的用法;
  %g:表示GUID;
  %p:表示指针;
  %r:表示函数的返回状态字符串,类型是EFI_STATUS;
  %x:表示十六进制,可以有%016lx这样的用法,这样不足的前置位由0补充;
  %lx:表示64位的十六进制;
  %ld:表示64位的十进制;
  %s/%S:表示宽字符串,就是类似L""的字符串,类型是CHAR16;
  1. 第一个参数表示打印级别,下面是说有的打印级别:
//
// Declare bits for PcdDebugPrintErrorLevel and the ErrorLevel parameter of DebugPrint()
//
#define DEBUG_INIT      0x00000001  // Initialization
#define DEBUG_WARN      0x00000002  // Warnings
#define DEBUG_LOAD      0x00000004  // Load events
#define DEBUG_FS        0x00000008  // EFI File system
#define DEBUG_POOL      0x00000010  // Alloc & Free (pool)
#define DEBUG_PAGE      0x00000020  // Alloc & Free (page)
#define DEBUG_INFO      0x00000040  // Informational debug messages
#define DEBUG_DISPATCH  0x00000080  // PEI/DXE/SMM Dispatchers
#define DEBUG_VARIABLE  0x00000100  // Variable
#define DEBUG_BM        0x00000400  // Boot Manager
#define DEBUG_BLKIO     0x00001000  // BlkIo Driver
#define DEBUG_NET       0x00004000  // SNP Driver
#define DEBUG_UNDI      0x00010000  // UNDI Driver
#define DEBUG_LOADFILE  0x00020000  // LoadFile
#define DEBUG_EVENT     0x00080000  // Event messages
#define DEBUG_GCD       0x00100000  // Global Coherency Database changes
#define DEBUG_CACHE     0x00200000  // Memory range cachability changes
#define DEBUG_VERBOSE   0x00400000  // Detailed debug messages that may
                                    // significantly impact boot performance
#define DEBUG_ERROR     0x80000000  // Error
 
//
// Aliases of debug message mask bits
//
#define EFI_D_INIT      DEBUG_INIT
#define EFI_D_WARN      DEBUG_WARN
#define EFI_D_LOAD      DEBUG_LOAD
#define EFI_D_FS        DEBUG_FS
#define EFI_D_POOL      DEBUG_POOL
#define EFI_D_PAGE      DEBUG_PAGE
#define EFI_D_INFO      DEBUG_INFO
#define EFI_D_DISPATCH  DEBUG_DISPATCH
#define EFI_D_VARIABLE  DEBUG_VARIABLE
#define EFI_D_BM        DEBUG_BM
#define EFI_D_BLKIO     DEBUG_BLKIO
#define EFI_D_NET       DEBUG_NET
#define EFI_D_UNDI      DEBUG_UNDI
#define EFI_D_LOADFILE  DEBUG_LOADFILE
#define EFI_D_EVENT     DEBUG_EVENT
#define EFI_D_VERBOSE   DEBUG_VERBOSE
#define EFI_D_ERROR     DEBUG_ERROR

说明:

1)首先这里宏定义了两层,这是因为EDK和EDKII的兼容关系,一般还是用EFI开头的版本比较好;

2)有一个比较特别的是EFI_D_ERROR,它是最高位为1,表示的错误;

哪些级别会被打印出来取决于全局PCD变量的配置,这个在后面会讲到。

  1. DEBUG中打印字符的长度是有限制的,最多0x100个字符,后面会具体说明。

宏实现

  1. DEBUG宏的位置是在MdePkg\Include\Library\DebugLib.h文件中:
/**  
  Macro that calls DebugPrint().
  If MDEPKG_NDEBUG is not defined and the DEBUG_PROPERTY_DEBUG_PRINT_ENABLED 
  bit of PcdDebugProperyMask is set, then this macro passes Expression to 
  DebugPrint().
  @param  Expression  Expression containing an error level, a format string, 
                      and a variable argument list based on the format string.
  
**/
#if !defined(MDEPKG_NDEBUG)      
  #define DEBUG(Expression)        \
    do {                           \
      if (DebugPrintEnabled ()) {  \
        _DEBUG (Expression);       \
      }                            \
    } while (FALSE)
#else
  #define DEBUG(Expression)
#endif

需要注意这里的MDEPKG_NDEBUG宏,如果开启了这个宏,表示所有的DEBUG信息都不会有打印了,因为走了#else分支。

而MDEPKG_NDEBUG这个宏一般定义在dsc文件中,这里以OvmfPkgX64.dsc为例:

[BuildOptions]
  GCC:*_UNIXGCC_*_CC_FLAGS             = -DMDEPKG_NDEBUG
  GCC:RELEASE_*_*_CC_FLAGS             = -DMDEPKG_NDEBUG
  INTEL:RELEASE_*_*_CC_FLAGS           = /D MDEPKG_NDEBUG
  MSFT:RELEASE_*_*_CC_FLAGS            = /D MDEPKG_NDEBUG

可以看到在Release版本中通常会定义它来确定调试的打印,这也是可以理解的。

  1. 除了MDEPKG_NDEBUG这个宏,这里还有一个判断条件:DebugPrintEnabled ()

这个函数定义在DebugLib中,不同的Pkg可能会有不同的实现,还是以OvmfPkgX64.dsc为例:

!ifdef $(DEBUG_ON_SERIAL_PORT)
  DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
!else
  DebugLib|OvmfPkg/Library/PlatformDebugLibIoPort/PlatformDebugLibIoPort.inf
!endif

这里我们看下BaseDebugLibSerialPort.inf中的实现:

BOOLEAN
EFIAPI
DebugPrintEnabled (
  VOID
  )
{
  return (BOOLEAN) ((PcdGet8(PcdDebugPropertyMask) & DEBUG_PROPERTY_DEBUG_PRINT_ENABLED) != 0);
}

从中可以看到它是去判断PcdDebugPropertyMask这个PCD变量的值,而它定义在OvmfPkgX64.dsc中:

!ifdef $(SOURCE_DEBUG_ENABLE)
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x17
!else
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
!endif

另外一个宏DEBUG_PROPERTY_DEBUG_PRINT_ENABLED定义如下:

//
// Declare bits for PcdDebugPropertyMask
//
#define DEBUG_PROPERTY_DEBUG_ASSERT_ENABLED       0x01
#define DEBUG_PROPERTY_DEBUG_PRINT_ENABLED        0x02
#define DEBUG_PROPERTY_DEBUG_CODE_ENABLED         0x04
#define DEBUG_PROPERTY_CLEAR_MEMORY_ENABLED       0x08
#define DEBUG_PROPERTY_ASSERT_BREAKPOINT_ENABLED  0x10
#define DEBUG_PROPERTY_ASSERT_DEADLOOP_ENABLED    0x20

所以可以确定对于OvmfPkgX64.dsc,函数DebugPrintEnabled ()返回的是TRUE。

需要关注的是_DEBUG宏:

/**
  Internal worker macro that calls DebugPrint().
  This macro calls DebugPrint() passing in the debug error level, a format
  string, and a variable argument list.
  __VA_ARGS__ is not supported by EBC compiler, Microsoft Visual Studio .NET 2003
  and Microsoft Windows Server 2003 Driver Development Kit (Microsoft WINDDK) version 3790.1830.
  @param  Expression  Expression containing an error level, a format string,
                      and a variable argument list based on the format string.
**/
 
#if !defined(MDE_CPU_EBC) && (!defined (_MSC_VER) || _MSC_VER > 1400)
  #define _DEBUG_PRINT(PrintLevel, ...)              \
    do {                                             \
      if (DebugPrintLevelEnabled (PrintLevel)) {     \
        DebugPrint (PrintLevel, ##__VA_ARGS__);      \
      }                                              \
    } while (FALSE)
  #define _DEBUG(Expression)   _DEBUG_PRINT Expression
#else
#define _DEBUG(Expression)   DebugPrint Expression
#endif

这里又涉及到几个判断条件。

首先是编译器的支持情况,这个在注释中已经说明。

另外一个是DebugPrintLevelEnabled()函数,它有一个参数PrintLevel,它就是DEBUG函数的第一个参数。

这个函数也定义在DebugLib库中,我们同样使用BaseDebugLibSerialPort.inf中的实现:

/**
  Returns TRUE if any one of the bit is set both in ErrorLevel and PcdFixedDebugPrintErrorLevel.
  This function compares the bit mask of ErrorLevel and PcdFixedDebugPrintErrorLevel.
  @retval  TRUE    Current ErrorLevel is supported.
  @retval  FALSE   Current ErrorLevel is not supported.
**/
BOOLEAN
EFIAPI
DebugPrintLevelEnabled (
  IN  CONST UINTN        ErrorLevel
  )
{
  return (BOOLEAN) ((ErrorLevel & PcdGet32(PcdFixedDebugPrintErrorLevel)) != 0);
}

这里也涉及到一个PCD变量,它同样定义在MdePkg.dec文件中:

  ## This flag is used to control build time optimization based on debug print level.
  #  Its default value is 0xFFFFFFFF to expose all debug print level.
  #  BIT0  - Initialization message.<BR>
  #  BIT1  - Warning message.<BR>
  #  BIT2  - Load Event message.<BR>
  #  BIT3  - File System message.<BR>
  #  BIT4  - Allocate or Free Pool message.<BR>
  #  BIT5  - Allocate or Free Page message.<BR>
  #  BIT6  - Information message.<BR>
  #  BIT7  - Dispatcher message.<BR>
  #  BIT8  - Variable message.<BR>
  #  BIT10 - Boot Manager message.<BR>
  #  BIT12 - BlockIo Driver message.<BR>
  #  BIT14 - Network Driver message.<BR>
  #  BIT16 - UNDI Driver message.<BR>
  #  BIT17 - LoadFile message.<BR>
  #  BIT19 - Event message.<BR>
  #  BIT20 - Global Coherency Database changes message.<BR>
  #  BIT21 - Memory range cachability changes message.<BR>
  #  BIT22 - Detailed debug message.<BR>
  #  BIT31 - Error message.<BR>
  # @Prompt Fixed Debug Message Print Level.
  gEfiMdePkgTokenSpaceGuid.PcdFixedDebugPrintErrorLevel|0xFFFFFFFF|UINT32|0x30001016

它的值是全FF,所以这个函数必定返回TRUE,也因为这个DEBUG宏的第一个参数在这里并没有派上用处。

  1. 真正的函数DebugPrint(),它也定义在DebugLib中,以MdePkg/Library/BaseDebugLibSerialPort/DebugLib.c为例:
VOID
EFIAPI
DebugPrint (
  IN  UINTN        ErrorLevel,
  IN  CONST CHAR8  *Format,
  ...
  )
{
  CHAR8    Buffer[MAX_DEBUG_MESSAGE_LENGTH];
  VA_LIST  Marker;
 
  //
  // If Format is NULL, then ASSERT().
  //
  ASSERT (Format != NULL);
 
  //
  // Check driver debug mask value and global mask
  //
  if ((ErrorLevel & GetDebugPrintErrorLevel ()) == 0) {
    return;
  }
 
  //
  // Convert the DEBUG() message to an ASCII String
  //
  VA_START (Marker, Format);
  AsciiVSPrint (Buffer, sizeof (Buffer), Format, Marker);
  VA_END (Marker);
 
  //
  // Send the print string to a Serial Port 
  //
  SerialPortWrite ((UINT8 *)Buffer, AsciiStrLen (Buffer));
}

函数也比较简单,有几点说明:

1)Buffer是一个有大小的数组,这说明DEBUG能够打印的信息是有大小限制的#define MAX_DEBUG_MESSAGE_LENGTH 0x100

2)GetDebugPrintErrorLevel()函数获取PCD变量,并与DEBUG的第一个参数进行比较,来确定是否需要输出打印;

这里使用的PCD变量是PcdDebugPrintErrorLevel,它的值在dsc文件中定义:

SerialPortWrite()函数,它是具体输出到某个介质的实现了。

  1. 除了BaseDebugLibSerialPort.inf这一种实现方式外(OVMF使用了这种方式),还有一个更加普遍的实现:PeiDxeDebugLibReportStatusCode.inf,它的实现方式是:
  //
  // Send the DebugInfo record
  //
  REPORT_STATUS_CODE_EX (
    EFI_DEBUG_CODE,
    (EFI_SOFTWARE_DXE_BS_DRIVER | EFI_DC_UNSPECIFIED),
    0,
    NULL,
    &gEfiStatusCodeDataTypeDebugGuid,
    DebugInfo,
    TotalSize
    );

这里又使用了一个宏REPORT_STATUS_CODE_EX,它的实现如下:

#define REPORT_STATUS_CODE_EX(Type,Value,Instance,CallerId,ExtendedDataGuid,ExtendedData,ExtendedDataSize)  \
  (ReportProgressCodeEnabled() && ((Type) & EFI_STATUS_CODE_TYPE_MASK) == EFI_PROGRESS_CODE)             ?  \
  ReportStatusCodeEx(Type,Value,Instance,CallerId,ExtendedDataGuid,ExtendedData,ExtendedDataSize)        :  \
  (ReportErrorCodeEnabled() && ((Type) & EFI_STATUS_CODE_TYPE_MASK) == EFI_ERROR_CODE)                   ?  \
  ReportStatusCodeEx(Type,Value,Instance,CallerId,ExtendedDataGuid,ExtendedData,ExtendedDataSize)        :  \
  (ReportDebugCodeEnabled() && ((Type) & EFI_STATUS_CODE_TYPE_MASK) == EFI_DEBUG_CODE)                   ?  \
  ReportStatusCodeEx(Type,Value,Instance,CallerId,ExtendedDataGuid,ExtendedData,ExtendedDataSize)        :  \
  EFI_UNSUPPORTED

这里会根据xxxEnabled()和一个type参数来确定是否执行函数ReportStatusCodeEx()。

其中Enabled是根据dsc文件中的PCD变量来的,以ReportDebugCodeEnabled()为例:

/**
  Returns TRUE if status codes of type EFI_DEBUG_CODE are enabled
  This function returns TRUE if the REPORT_STATUS_CODE_PROPERTY_DEBUG_CODE_ENABLED
  bit of PcdReportStatusCodeProperyMask is set.  Otherwise FALSE is returned.
  @retval  TRUE   The REPORT_STATUS_CODE_PROPERTY_DEBUG_CODE_ENABLED bit of
                  PcdReportStatusCodeProperyMask is set.
  @retval  FALSE  The REPORT_STATUS_CODE_PROPERTY_DEBUG_CODE_ENABLED bit of
                  PcdReportStatusCodeProperyMask is clear.
**/
BOOLEAN
EFIAPI
ReportDebugCodeEnabled (
  VOID
  )
{
  return (BOOLEAN) ((PcdGet8 (PcdReportStatusCodePropertyMask) & REPORT_STATUS_CODE_PROPERTY_DEBUG_CODE_ENABLED) != 0);
}

PCD变量指的是PcdReportStatusCodePropertyMask,它的值可以是


//
// Declare bits for PcdReportStatusCodePropertyMask
//
#define REPORT_STATUS_CODE_PROPERTY_PROGRESS_CODE_ENABLED          0x00000001
#define REPORT_STATUS_CODE_PROPERTY_ERROR_CODE_ENABLED             0x00000002
#define REPORT_STATUS_CODE_PROPERTY_DEBUG_CODE_ENABLED             0x00000004

这里的3个值刚好和REPORT_STATUS_CODE_EX宏里面的一一对应。

关于REPORT_STATUS_CODE_EX的第一个参数type也对应的有3种:

///
/// Definition of code types. All other values masked by
/// EFI_STATUS_CODE_TYPE_MASK are reserved for use by
/// this specification.
///
///@{
#define EFI_PROGRESS_CODE             0x00000001
#define EFI_ERROR_CODE                0x00000002
#define EFI_DEBUG_CODE                0x00000003
///@}

对于我们的DEBUG宏来说,使用的是EFI_DEBUG_CODE这种类型。

事实上REPORT_STATUS_CODE_EX宏可以单独的拿出来用,并使用不同的type类型,比如说BdsEntry.c中就有:

      REPORT_STATUS_CODE_EX (
        EFI_ERROR_CODE,
        PcdGet32 (PcdErrorCodeSetVariable),
        0,
        NULL,
        &gEdkiiStatusCodeDataTypeVariableGuid,
        SetVariableStatus,
        sizeof (EDKII_SET_VARIABLE_STATUS) + NameSize + DataSize
        );
  1. 之后就涉及到函数ReportStatusCodeEx()了

它的实现非常多,PEI阶段、DXE阶段、SMM模块,RUNTIME模块等等都有不同的实现。

下面以DXE阶段为例,它的实现位于MdeModulePkg/Library/DxeReportStatusCodeLib/DxeReportStatusCodeLib.inf(对于Coreboot来说)。

它的实现并不复杂,不过要注意其中有优先级的变化,这在实际应用中可能导致一些问题。

ReportStatusCodeEx()函数又调用了InternalReportStatusCode()函数,而后者是通过gEfiStatusCodeRuntimeProtocolGuid对应的Protocol来进行输出的。

  1. 对于gEfiStatusCodeRuntimeProtocolGuid
    它是在IntelFrameworkModulePkg/Universal/StatusCode/RuntimeDxe/StatusCodeRuntimeDxe.inf中安装的(对于Coreboot来说),该Protocol只包含一个函数:ReportDispatcher,在InternalReportStatusCode()函数中就是调用了该函数实现。

  2. ReportDispatcher()函数需要关注的主要代码如下:

  if (FeaturePcdGet (PcdStatusCodeUseSerial)) {
    SerialStatusCodeReportWorker (
      CodeType,
      Value,
      Instance,
      CallerId,
      Data
      );
  }
  if (FeaturePcdGet (PcdStatusCodeUseMemory)) {
    RtMemoryStatusCodeReportWorker (
      CodeType,
      Value,
      Instance
      );
  }
  if (FeaturePcdGet (PcdStatusCodeUseDataHub)) {
    DataHubStatusCodeReportWorker (
      CodeType,
      Value,
      Instance,
      CallerId,
      Data
      );
  }
  if (FeaturePcdGet (PcdStatusCodeUseOEM)) {
    //
    // Call OEM hook status code library API to report status code to OEM device
    //
    OemHookStatusCodeReport (
      CodeType,
      Value,
      Instance,
      CallerId,
      Data
      );
  }

根据不同的PCD变量,可以选在不同的打印输出,甚至还有自定义的方式。

以SerialStatusCodeReportWorker()为例,它到最后也是调用了SerialPortWrite(),这跟OVMF中的实现就对应上了。

跟OVMF版本不同的是这里可以有更多的扩展。

以上就是DEBUG的实现。

另外再补充一个与DEBUG同一级别的调试代码ASSERT,它最终对应的代码是:

VOID
EFIAPI
DebugAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN        LineNumber,
  IN CONST CHAR8  *Description
  )

使用ASSERT的时候要特别注意,当ASSERT之后代码可以进入CPU Dead Loop,这个时候代码就无法继续执行下去。

通常在发行版的UEFI中不会让这种情况出现,这个使用就需要设置DSC(或者DEC)中的一个PCD:PcdDebugPropertyMask,它可以设置不同的值,每个BIT代表的意义如下:

//
// Declare bits for PcdDebugPropertyMask
//
#define DEBUG_PROPERTY_DEBUG_ASSERT_ENABLED       0x01
#define DEBUG_PROPERTY_DEBUG_PRINT_ENABLED        0x02
#define DEBUG_PROPERTY_DEBUG_CODE_ENABLED         0x04
#define DEBUG_PROPERTY_CLEAR_MEMORY_ENABLED       0x08
#define DEBUG_PROPERTY_ASSERT_BREAKPOINT_ENABLED  0x10
#define DEBUG_PROPERTY_ASSERT_DEADLOOP_ENABLED    0x20

当设置了DEBUG_PROPERTY_ASSERT_DEADLOOP_ENABLED就会进入Dead Loop:

  //
  // Generate a Breakpoint, DeadLoop, or NOP based on PCD settings
  //
  if ((PcdGet8(PcdDebugPropertyMask) & DEBUG_PROPERTY_ASSERT_BREAKPOINT_ENABLED) != 0) {
    CpuBreakpoint ();
  } else if ((PcdGet8(PcdDebugPropertyMask) & DEBUG_PROPERTY_ASSERT_DEADLOOP_ENABLED) != 0) {
    CpuDeadLoop ();
  }

还没有说ASSERT的使用,这里举个例子:

ASSERT (FileHandle != NULL);

需要特别注意,这里()中的条件是我们希望的,所以ASSERT(FALSE)才是真正错误的情况。

其实还有一种类型的ASSERT:ASSERT_EFI_ERROR (Status);

就是说当Status是错误的返回值时就ASSERT。

标签:CODE,Dubug,ENABLED,EFI,UEFI,DEBUG,PROPERTY,define
来源: https://blog.csdn.net/u011057409/article/details/117636203

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有