BIOS/UEFI基础——DEBUG
来源:互联网 发布:测亩仪软件下载 编辑:程序博客网 时间:2024/05/29 17:11
简述
在UEFI开发中,非常重要的一个部分就是添加串口调试信息打印,这个通过DEBUG宏来完成。
在UEFI的代码中可以看到非常多的DEBUG代码,比如:
DEBUG ((EFI_D_INFO, "PlatformBootManagerBeforeConsole\n"));需要注意的有几点:
1. 括号是双重的,下面以第一个参数,第二个参数来分别称呼EFI_D_xx和字符串,但实际上稍微有点问题;
2. 第二个参数是一个字符串,这个字符串里面也可以带格式化标志,下面是DEBUG常用的标志:
%a:表示ASCII字符串;
%c:表示ASCII字符;
%d:表示十进制,可以有%02d这样的用法;
%g:表示GUID;
%p:表示指针;
%r:表示函数的返回状态字符串,类型是EFI_STATUS;
%x:表示十六进制,可以有%016lx这样的用法;
%s/%S:表示宽字符串,就是类似L""的字符串,类型是CHAR16;
3. 第一个参数表示打印级别,下面是说有的打印级别:
//// 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宏的位置是在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版本中通常会定义它来确定调试的打印,这也是可以理解的。
2. 除了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中的实现:
BOOLEANEFIAPIDebugPrintEnabled ( 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。
3. 之后需要关注的是_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.**/BOOLEANEFIAPIDebugPrintLevelEnabled ( 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宏的第一个参数在这里并没有派上用处。
4.1. 之后就涉及到了真正的函数DebugPrint(),它也定义在DebugLib中,还是以BaseDebugLibSerialPort.inf为例:
VOIDEFIAPIDebugPrint ( 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能够打印的信息是有大小限制的;
2)GetDebugPrintErrorLevel()函数获取PCD变量,并与DEBUG的第一个参数进行比较,来确定是否需要输出打印;
这里使用的PCD变量是PcdDebugPrintErrorLevel,它的值在dsc文件中定义:
# DEBUG_INIT 0x00000001 // Initialization # DEBUG_WARN 0x00000002 // Warnings # DEBUG_LOAD 0x00000004 // Load events # DEBUG_FS 0x00000008 // EFI File system # DEBUG_POOL 0x00000010 // Alloc & Free (pool) # DEBUG_PAGE 0x00000020 // Alloc & Free (page) # DEBUG_INFO 0x00000040 // Informational debug messages # DEBUG_DISPATCH 0x00000080 // PEI/DXE/SMM Dispatchers # DEBUG_VARIABLE 0x00000100 // Variable # DEBUG_BM 0x00000400 // Boot Manager # DEBUG_BLKIO 0x00001000 // BlkIo Driver # DEBUG_NET 0x00004000 // SNP Driver # DEBUG_UNDI 0x00010000 // UNDI Driver # DEBUG_LOADFILE 0x00020000 // LoadFile # DEBUG_EVENT 0x00080000 // Event messages # DEBUG_GCD 0x00100000 // Global Coherency Database changes # DEBUG_CACHE 0x00200000 // Memory range cachability changes # DEBUG_VERBOSE 0x00400000 // Detailed debug messages that may # // significantly impact boot performance # DEBUG_ERROR 0x80000000 // Error # jw_debug, change 0x8000004F to 0x80000040 gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000040
5.1. 之后是SerialPortWrite()函数,它是具体输出到某个介质的实现了。
4.2. 除了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.**/BOOLEANEFIAPIReportDebugCodeEnabled ( 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 );5.2. 之后就涉及到函数ReportStatusCodeEx()了。
它的实现非常多,PEI阶段、DXE阶段、SMM模块,RUNTIME模块等等都有不同的实现。
下面以DXE阶段为例,它的实现位于MdeModulePkg/Library/DxeReportStatusCodeLib/DxeReportStatusCodeLib.inf(对于Coreboot来说)。
它的实现并不复杂,不过要注意其中有优先级的变化,这在实际应用中可能导致一些问题。
ReportStatusCodeEx()函数又调用了InternalReportStatusCode()函数,而后者是通过gEfiStatusCodeRuntimeProtocolGuid对应的Protocol来进行输出的。
6. 对于gEfiStatusCodeRuntimeProtocolGuid,它是在IntelFrameworkModulePkg/Universal/StatusCode/RuntimeDxe/StatusCodeRuntimeDxe.inf中安装的(对于Coreboot来说),该Protocol只包含一个函数:ReportDispatcher,在InternalReportStatusCode()函数中就是调用了该函数实现。
7. 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,它最终对应的代码是:
VOIDEFIAPIDebugAssert ( 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 (FileHandle != NULL);需要特别注意,这里()中的条件是我们希望的,所以ASSERT(FALSE)才是真正错误的情况。
其实还有一种类型的ASSERT:ASSERT_EFI_ERROR (Status);
就是说当Status是错误的返回值时就ASSERT。
- BIOS/UEFI基础——DEBUG
- BIOS/UEFI基础——UEFI网络框架之概述
- BIOS/UEFI基础——UEFI网络框架之UNDI
- BIOS/UEFI基础——UEFI网络框架之SNP
- BIOS/UEFI基础——UEFI网络框架之MNP
- BIOS/UEFI基础——UEFI网络框架之ARP
- BIOS/UEFI基础——UEFI网络框架之TCP4
- BIOS/UEFI基础——UEFI网络框架之MNP2
- BIOS/UEFI基础——UEFI网络框架之IP4
- BIOS/UEFI基础——基础知识
- BIOS/UEFI基础——EDK
- BIOS/UEFI基础——EFI_HANDLE
- BIOS/UEFI基础——变量
- BIOS/UEFI基础——定时器
- BIOS/UEFI基础——Device Path
- BIOS/UEFI基础——x86架构中断基础介绍
- BIOS/UEFI基础——第一条指令
- BIOS/UEFI基础——System Table和Architecture Protocols
- 查看Oracle数据库表的大小
- solidworks无法装入solidworks DLL sldshellutils
- poj 1061 青蛙的约会 扩展gcd
- 修改oracle用户密码永不过期
- 在 Windows Forms 和 WPF 应用中使用 FontAwesome 图标
- BIOS/UEFI基础——DEBUG
- SDNU_ACM_ICPC_2017_拓扑排序
- 查看MySQL数据库表的大小
- 英语总结
- hdu 6012 Lotus and Horticulture 思维+前缀和处理
- A way to learn
- CodeChef XRQRS - Xor Queries
- 再学重写和重载
- 量子时代的技术机遇