41. DEBUG 출력문 내부 구조와 DEBUG 문 제어를 위한 PCD 분석, 그리고 OVMF 부트 로그 가져오기

이번 장에서는 EDKII 코드 베이스에 존재하는 DEBUG 매크로와 관련된 정보를 학습한다. 다음은 그 예시이다.

DEBUG ((EFI_D_ERROR, "Hello Debug! Check this variable: %d\n", MyVar));

일반적인 출력 형식과 유사하지만 다른 기능이 존재한다.

  • 로그 레벨을 이용하여 로그 메세지를 분류할 수 있다. PCD를 통한 구성으로 type별 디버그 메세지를 쉽게 켜고 끌 수 있다.

  • 서로 다른 UEFI 단계 및 모듈은 서로 다른 DEBUG 구현을 가지기 때문에 여러 DEBUG 기능 구현이 존재한다.

사전에 정의된 로그 메세지의 범주는 아래와 같다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DebugLib.h

#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  // Network Io 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

하지만 일반적으로 다음 별칭의 값이 사용된다.

DEBUG 매크로 자체 또한 DebugLib에 정의되어 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DebugLib.h

모든 전처리 과정을 분할하여 살펴보면 일반적으로 다음과 같다.

DEBUG 매크로 인터페이스는 DebugLib 헤더에 정의되어 있지만 실제 구현을 위해서는 사용되는 특정 라이브러리의 인터페이스를 살펴봐야 한다https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkgX64.dsc 를 살펴보자

  • DebugLib 은 각 UEFI 단계에서 각기 다른 구현을 가지고 있다.

  • DebugLibDEBUG_ON_SERAIL_PORT 정의에 따라 다른 구현이 있다.

기본적으로 DEBUG_ON_SERIAL_PORT가 정의되어 있지 않은 경우 PlatformDebugLibIoPort 가 기본 라이브러리이다. https://github.com/tianocore/edk2/tree/master/OvmfPkg/Library/PlatformDebugLibIoPort

DEBUG 매크로는 다음과 같이 번역된다.

아래를 통해DebugPrintEnabled,DebugPrintLevelEnabled ,DebugPrint 정보를 살펴보자. https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/PlatformDebugLibIoPort/DebugLib.c

DebugPrintEnabled

DEBUG 매크로는 먼저 DebugPrintEnabled 결과를 확인한다. 이 함수는 PCD의 PcdDebugPropertyMaskDEBUG_PROPERTY_DEBUG_PRINT_ENABLED가 설정되어 있는지 확인한다.

DebugPrintLevelEnabled

그리고 DebugPrintLevelEnabled 함수는 전달된 ErrorLevel 이 PCD의 PcdFixedDebugPrintErrorLevel 에 있는지 확인한다.

DebugPrint

그런 다음 DebugPrint 함수가 실행되며 단순히 제어를 DebugVPrint로 전달한다.

그리고 DebugVPrintDebugPrintMarker로 제어를 전달한다.

DebugPrintMarker 는 주요 디버그 기능으로, 전달된 ErrorLevelGetDebugPrintErrorLevel() 출력에 있는지 확인한다. 최종적으로는 PCD의 PcdDebugIoPort 에 의해 정의된 입출력 포트에 디버그 문자열 쓰기를 수행한다.

GetDebugPrintErrorLevel()DebugPrintErrorLevelLib 라이브러리에 정의된 함수로 OvmfPkgX64.dsc 에서도 그 구현을 확인할 수 있다.

소스를 보면 단순히 다른 PCD를 확인하는 것을 볼 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.c

요약

DEBUG (( ErrorLevel, String, ... )) 와 같은 형태의 코드를 정리하자면 다음과 같다.

  1. PcdDebugPropertyMaskDEBUG_PROPERTY_DEBUG_PRINT_ENABLED가 있는지 확인한다.

  2. 전달된 ErrorLevelPcdFixedDebugPrintErrorLevel 에 설정되어 있는지 확인한다.

  3. 전달된 ErrorLevelPcdDebugPrintErrorLevel 에 설정되어 있는지 확인한다.

  4. 지정된 형식 문자열을 PcdDebugIoPort 에 정의된 입출력 포트에 작성한다.

Check if PcdDebugPropertyMask has DEBUG_PROPERTY_DEBUG_PRINT_ENABLED

OVMF의 DSC 파일에서는 PCD PcdDebugPropertyMask 를 다음과 같이 정의한다. https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkgX64.dsc

그리고 이 PCD는 Shell용으로 재정의 된다.

이 PCD에 대한 비트 정의는 다음에서 확인할 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec

SOURCE_DEBUG_ENABLE 의 설정 값

  • TRUE(17h): Debug Assert(1h)/Debug Print(2h)/Debug Code(4h)/BreakPoint(10h) 활성화

  • FALSE(2Fh): Debug Assert(1h)/Debug Print(2h)/Debug Code(4h)/Clear Memory(8h)/DeadLoop(20h) 활성화

DEBUG 매크로에서 이 PCDDEBUG_PROPERTY_DEBUG_PRINT_ENABLED 와 비교해보자. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DebugLib.h

보다시피 해당 검사는 SOURCE_DEBUG_ENABLE 와 관계없이 통과된다.

Checks if passed ErrorLevel is set in PcdFixedDebugPrintErrorLevel

해당 PCD는 MdePkg.dsc 에 정의되었다. https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec

이 PCD 정보는 OVMF의 DSC 파일에서 재정의 되지 않기 때문에 빌드에 포함되는 값이다. 0xFFFFFFFF 는 디버그 메세지가 EFI_D_* 출력 레벨에 관계없이 이 검사를 통과함을 의미한다.

Checks if passed ErrorLevel is set in PcdDebugPrintErrorLevel

해당 PCD는 MdePkg.dec 에 정의되었다. https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec

기본적으로 MdePkg.decMdePkg.dsc 에서 0x80000000 으로 정의된다. https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dsc

0x80000000 이라는 값은 오류 메시지(EFI_D_ERROR) 만 출력함을 의미한다.

빌드하는 OVMF의 DSC 파일에서는 해당 PCD를 재정의하고 있다.

0x8000004F - EFI_D_ERROR | EFI_D_INFO | EFI_D_FS | EFI_D_LOAD | EFI_D_WARN | EFI_D_INIT

만약 DEBUG 가 활성화된다면 위 범주의 메세지가 출력된다.

Writes formatted String to I/O port defined by PcdDebugIoPort

PcdDebugIoPort 에 대한 정의는 OvmfPkg.dec 에 존재한다. https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkg.dec

해당 PCD는 PlatformDebugLibIoPort 의 디버그 메세지에 대한 대상 입출력 포트를 제어한다.

DEBUG 테스트

디버그 메세지를 보기 위해서는 디버그 모드에서 OVMF를 다시 컴파일 해야 한다.

먼저 다음과 같이 DEBUG 모드로 빌드를 진행한다.

결과에 대한 경로는Build/OvmfX64/DEBUG_GCC5 이다.

QEMU를 다음과 같은 옵션으로 실행한다.

이렇게 하면 OVMF의 모든 DEBUG 로그 메세지가 debug.log 파일에 포함되어 생성된다. 해당 파일은 매 실행마다 생성된다.

로그파일에서 GUID 치환하기

debug.log 파일을 확인해보면 다음과 같은 GUID 값을 확인할 수 있다.

edk2 빌드 시스템이 GUID의 이름을 가진 파일을 만들기 때문에 이를 이용하면 GUID 값에 대응하는 텍스트로 대체시켜 가독성을 높일 수 있다.

다음과 같은 파이썬 코드를 작성하자.

결과물은 debug_parsed.log 에 저장되며 추가적인GUID 값이 존재하는 경우 EXTRA_GUIDS_FILE_PATH 경로에 지정하여 추가할 수 있다.

추가적으로 해당 스크립트를 범용적으로 이용하기 위해서 아래와 같이 작성해볼 수 있다.

DEBUG 메세지 출력길이 제한

DEBUg 문으로 출력할 수 있는 최대 문자열의 길이가 제한되어 있다. https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/PlatformDebugLibIoPort/DebugLib.c

정의에 따라 0x100보다 긴 출력이 발생할 수 있으므로 해당 값을 변경하여 빌드함으로써 그 제한을 늘릴 수 있다.

\

Last updated