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
하지만 일반적으로 다음 별칭의 값이 사용된다.
//
// 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
DEBUG 매크로 자체 또한 DebugLib
에 정의되어 있다.
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DebugLib.h
모든 전처리 과정을 분할하여 살펴보면 일반적으로 다음과 같다.
if (DebugPrintEnabled ()) {
if (DebugPrintLevelEnabled (PrintLevel)) {
DebugPrint (PrintLevel, ##__VA_ARGS__);
}
}
DEBUG 매크로 인터페이스는 DebugLib
헤더에 정의되어 있지만 실제 구현을 위해서는 사용되는 특정 라이브러리의 인터페이스를 살펴봐야 한다https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkgX64.dsc 를 살펴보자
DebugLib
은 각 UEFI 단계에서 각기 다른 구현을 가지고 있다.DebugLib
은DEBUG_ON_SERAIL_PORT
정의에 따라 다른 구현이 있다.
[LibraryClasses.common.SEC]
!ifdef $(DEBUG_ON_SERIAL_PORT)
DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
!else
DebugLib|OvmfPkg/Library/PlatformDebugLibIoPort/PlatformRomDebugLibIoPort.inf
!endif
[LibraryClasses.common.PEI_CORE]
# [LibraryClasses.common.PEIM]
# [LibraryClasses.common.DXE_CORE]
# [LibraryClasses.common.DXE_RUNTIME_DRIVER]
# [LibraryClasses.common.UEFI_DRIVER]
# [LibraryClasses.common.DXE_DRIVER]
# [LibraryClasses.common.UEFI_APPLICATION]
# [LibraryClasses.common.DXE_SMM_DRIVER]
# [LibraryClasses.common.SMM_CORE]
!ifdef $(DEBUG_ON_SERIAL_PORT)
DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
!else
DebugLib|OvmfPkg/Library/PlatformDebugLibIoPort/PlatformDebugLibIoPort.inf
!endif
기본적으로 DEBUG_ON_SERIAL_PORT
가 정의되어 있지 않은 경우 PlatformDebugLibIoPort
가 기본 라이브러리이다.
https://github.com/tianocore/edk2/tree/master/OvmfPkg/Library/PlatformDebugLibIoPort
DebugLib|OvmfPkg/Library/PlatformDebugLibIoPort/PlatformDebugLibIoPort.inf
DEBUG
매크로는 다음과 같이 번역된다.
if (DebugPrintEnabled ()) {
if (DebugPrintLevelEnabled (PrintLevel)) {
DebugPrint (PrintLevel, ##__VA_ARGS__);
}
}
아래를 통해DebugPrintEnabled
,DebugPrintLevelEnabled
,DebugPrint
정보를 살펴보자. https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/PlatformDebugLibIoPort/DebugLib.c
DebugPrintEnabled
DEBUG
매크로는 먼저 DebugPrintEnabled
결과를 확인한다. 이 함수는 PCD의 PcdDebugPropertyMask
에 DEBUG_PROPERTY_DEBUG_PRINT_ENABLED
가 설정되어 있는지 확인한다.
BOOLEAN
EFIAPI
DebugPrintEnabled (
VOID
)
{
return (BOOLEAN) ((PcdGet8(PcdDebugPropertyMask) & DEBUG_PROPERTY_DEBUG_PRINT_ENABLED) != 0);
}
DebugPrintLevelEnabled
그리고 DebugPrintLevelEnabled
함수는 전달된 ErrorLevel
이 PCD의 PcdFixedDebugPrintErrorLevel
에 있는지 확인한다.
BOOLEAN
EFIAPI
DebugPrintLevelEnabled (
IN CONST UINTN ErrorLevel
)
{
return (BOOLEAN) ((ErrorLevel & PcdGet32(PcdFixedDebugPrintErrorLevel)) != 0);
}
DebugPrint
그런 다음 DebugPrint
함수가 실행되며 단순히 제어를 DebugVPrint
로 전달한다.
VOID
EFIAPI
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
VA_START (Marker, Format);
DebugVPrint (ErrorLevel, Format, Marker);
VA_END (Marker);
}
그리고 DebugVPrint
는 DebugPrintMarker
로 제어를 전달한다.
VOID
EFIAPI
DebugVPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
IN VA_LIST VaListMarker
)
{
DebugPrintMarker (ErrorLevel, Format, VaListMarker, NULL);
}
DebugPrintMarker
는 주요 디버그 기능으로, 전달된 ErrorLevel
이 GetDebugPrintErrorLevel()
출력에 있는지 확인한다. 최종적으로는 PCD의 PcdDebugIoPort
에 의해 정의된 입출력 포트에 디버그 문자열 쓰기를 수행한다.
VOID
DebugPrintMarker (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
IN VA_LIST VaListMarker,
IN BASE_LIST BaseListMarker
)
{
CHAR8 Buffer[MAX_DEBUG_MESSAGE_LENGTH];
UINTN Length;
//
// If Format is NULL, then ASSERT().
//
ASSERT (Format != NULL);
//
// Check if the global mask disables this message or the device is inactive
//
if ((ErrorLevel & GetDebugPrintErrorLevel ()) == 0 ||
!PlatformDebugLibIoPortFound ()) {
return;
}
//
// Convert the DEBUG() message to an ASCII String
//
if (BaseListMarker == NULL) {
Length = AsciiVSPrint (Buffer, sizeof (Buffer), Format, VaListMarker);
} else {
Length = AsciiBSPrint (Buffer, sizeof (Buffer), Format, BaseListMarker);
}
//
// Send the print string to the debug I/O port
//
IoWriteFifo8 (PcdGet16 (PcdDebugIoPort), Length, Buffer);
}
GetDebugPrintErrorLevel()
는 DebugPrintErrorLevelLib
라이브러리에 정의된 함수로 OvmfPkgX64.dsc
에서도 그 구현을 확인할 수 있다.
[LibraryClasses]
...
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
소스를 보면 단순히 다른 PCD를 확인하는 것을 볼 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.c
UINT32
EFIAPI
GetDebugPrintErrorLevel (
VOID
)
{
//
// Retrieve the current debug print error level mask from PcdDebugPrintErrorLevel.
//
return PcdGet32 (PcdDebugPrintErrorLevel);
}
요약
DEBUG (( ErrorLevel, String, ... ))
와 같은 형태의 코드를 정리하자면 다음과 같다.
PcdDebugPropertyMask
에DEBUG_PROPERTY_DEBUG_PRINT_ENABLED
가 있는지 확인한다.전달된
ErrorLevel
이PcdFixedDebugPrintErrorLevel
에 설정되어 있는지 확인한다.전달된
ErrorLevel
이PcdDebugPrintErrorLevel
에 설정되어 있는지 확인한다.지정된 형식 문자열을
PcdDebugIoPort
에 정의된 입출력 포트에 작성한다.
Check if PcdDebugPropertyMask
has DEBUG_PROPERTY_DEBUG_PRINT_ENABLED
PcdDebugPropertyMask
has DEBUG_PROPERTY_DEBUG_PRINT_ENABLED
OVMF의 DSC 파일에서는 PCD PcdDebugPropertyMask
를 다음과 같이 정의한다.
https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkgX64.dsc
[PcdsFixedAtBuild]
!if $(SOURCE_DEBUG_ENABLE) == TRUE
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x17
!else
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
!endif
그리고 이 PCD는 Shell용으로 재정의 된다.
[Components]
ShellPkg/Application/Shell/Shell.inf {
...
<PcdsFixedAtBuild>
...
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
}
이 PCD에 대한 비트 정의는 다음에서 확인할 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec
## The mask is used to control DebugLib behavior.<BR><BR>
# BIT0 - Enable Debug Assert.<BR>
# BIT1 - Enable Debug Print.<BR>
# BIT2 - Enable Debug Code.<BR>
# BIT3 - Enable Clear Memory.<BR>
# BIT4 - Enable BreakPoint as ASSERT.<BR>
# BIT5 - Enable DeadLoop as ASSERT.<BR>
# @Prompt Debug Property.
# @Expression 0x80000002 | (gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask & 0xC0) == 0
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0|UINT8|0x00000005
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
매크로에서 이 PCD
를 DEBUG_PROPERTY_DEBUG_PRINT_ENABLED
와 비교해보자.
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DebugLib.h
//
// 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
보다시피 해당 검사는 SOURCE_DEBUG_ENABLE
와 관계없이 통과된다.
Checks if passed ErrorLevel
is set in PcdFixedDebugPrintErrorLevel
ErrorLevel
is set in PcdFixedDebugPrintErrorLevel
해당 PCD는 MdePkg.dsc
에 정의되었다.
https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdFixedDebugPrintErrorLevel|0xFFFFFFFF|UINT32|0x30001016
이 PCD 정보는 OVMF의 DSC 파일에서 재정의 되지 않기 때문에 빌드에 포함되는 값이다. 0xFFFFFFFF
는 디버그 메세지가 EFI_D_*
출력 레벨에 관계없이 이 검사를 통과함을 의미한다.
Checks if passed ErrorLevel
is set in PcdDebugPrintErrorLevel
ErrorLevel
is set in PcdDebugPrintErrorLevel
해당 PCD는 MdePkg.dec
에 정의되었다.
https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000|UINT32|0x00000006
기본적으로 MdePkg.dec
및 MdePkg.dsc
에서 0x80000000
으로 정의된다.
https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dsc
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000
0x80000000
이라는 값은 오류 메시지(EFI_D_ERROR
) 만 출력함을 의미한다.
빌드하는 OVMF의 DSC 파일에서는 해당 PCD를 재정의하고 있다.
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x8000004F
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
PcdDebugIoPort
에 대한 정의는 OvmfPkg.dec
에 존재한다.
https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkg.dec
[PcdsFixedAtBuild]
gUefiOvmfPkgTokenSpaceGuid.PcdDebugIoPort|0x402|UINT16|4
해당 PCD는 PlatformDebugLibIoPort
의 디버그 메세지에 대한 대상 입출력 포트를 제어한다.
DEBUG 테스트
디버그 메세지를 보기 위해서는 디버그 모드에서 OVMF를 다시 컴파일 해야 한다.
먼저 다음과 같이 DEBUG 모드로 빌드를 진행한다.
build --platform=OvmfPkg/OvmfPkgX64.dsc --arch=X64 --buildtarget=DEBUG --tagname=GCC5
결과에 대한 경로는Build/OvmfX64/DEBUG_GCC5
이다.
QEMU를 다음과 같은 옵션으로 실행한다.
qemu-system-x86_64 \
-drive if=pflash,format=raw,readonly,file=Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd \
-drive format=raw,file=fat:rw:~/UEFI_disk \
-net none \
-nographic \
-global isa-debugcon.iobase=0x402 \
-debugcon file:debug.log
이렇게 하면 OVMF의 모든 DEBUG 로그 메세지가 debug.log
파일에 포함되어 생성된다. 해당 파일은 매 실행마다 생성된다.
로그파일에서 GUID 치환하기
debug.log
파일을 확인해보면 다음과 같은 GUID 값을 확인할 수 있다.
$ head debug.log
SecCoreStartupWithStack(0xFFFCC000, 0x820000)
Register PPI Notify: DCD0BE23-9586-40F4-B643-06522CED4EDE
Install PPI: 8C8CE578-8A3D-4F1C-9935-896185C32DD3
Install PPI: 5473C07A-3DCB-4DCA-BD6F-1E9689E7349A
The 0th FV start address is 0x00000820000, size is 0x000E0000, handle is 0x820000
Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Register PPI Notify: EA7CA24B-DED5-4DAD-A389-BF827E8F9B38
Install PPI: B9E0ABFE-5979-4914-977F-6DEE78C278A6
Install PPI: DBE23AA9-A345-4B97-85B6-B226F1617389
DiscoverPeimsAndOrderWithApriori(): Found 0x7 PEI FFS files in the 0th FV
edk2 빌드 시스템이 GUID의 이름을 가진 파일을 만들기 때문에 이를 이용하면 GUID 값에 대응하는 텍스트로 대체시켜 가독성을 높일 수 있다.
$ head Build/OvmfX64/DEBUG_GCC5/FV/Guid.xref
8c1a6b71-0c4b-4497-aaad-07404edf142c PCDLesson
1BA0062E-C779-4582-8566-336AE8F78F09 ResetVector
df1ccef6-f301-4a63-9661-fc6030dcc880 SecMain
52C05B14-0B98-496c-BC3B-04B50211D680 PeiCore
9B3ADA4F-AE56-4c24-8DEA-F03B7558AE50 PcdPeim
A3610442-E69F-4DF3-82CA-2360C4031A23 ReportStatusCodeRouterPei
9D225237-FA01-464C-A949-BAABC02D31D0 StatusCodeHandlerPei
86D70125-BAA3-4296-A62F-602BEBBB9081 DxeIpl
222c386d-5abc-4fb4-b124-fbb82488acf4 PlatformPei
89E549B0-7CFE-449d-9BA3-10D8B2312D71 S3Resume2Pei
다음과 같은 파이썬 코드를 작성하자.
from shutil import copyfile
GUIDS_FILE_PATH = "Build/OvmfX64/DEBUG_GCC5/FV/Guid.xref"
EXTRA_GUIDS_FILE_PATH = "Guid_extra.xref"
LOG_IN_FILE_PATH = "debug.log"
LOG_OUT_FILE_PATH = "debug_parsed.log"
guids = {}
with open(GUIDS_FILE_PATH) as p:
for line in p:
l = line.split(" ")
if len(l)==2:
guids[l[0].upper()] = l[1][:-1]
if EXTRA_GUIDS_FILE_PATH:
with open(EXTRA_GUIDS_FILE_PATH) as p:
for line in p:
l = line.split(" ")
if len(l)==2:
guids[l[0].upper()] = l[1][:-1]
copyfile(LOG_IN_FILE_PATH, LOG_OUT_FILE_PATH)
f = open(LOG_OUT_FILE_PATH, 'r')
filedata = f.read()
f.close()
for key,val in guids.items():
filedata = filedata.replace(key, val)
f = open(LOG_OUT_FILE_PATH, 'w')
f.write(filedata)
f.close()
결과물은 debug_parsed.log
에 저장되며 추가적인GUID 값이 존재하는 경우 EXTRA_GUIDS_FILE_PATH
경로에 지정하여 추가할 수 있다.
$ python replace_guids.py
$ head debug_parsed.log
SecCoreStartupWithStack(0xFFFCC000, 0x820000)
Register PPI Notify: gEfiPeiSecurity2PpiGuid
Install PPI: gEfiFirmwareFileSystem2Guid
Install PPI: gEfiFirmwareFileSystem3Guid
The 0th FV start address is 0x00000820000, size is 0x000E0000, handle is 0x820000
Register PPI Notify: gEfiPeiFirmwareVolumeInfoPpiGuid
Register PPI Notify: gEfiPeiFirmwareVolumeInfo2PpiGuid
Install PPI: gEfiPeiLoadFilePpiGuid
Install PPI: gEfiTemporaryRamSupportPpiGuid
DiscoverPeimsAndOrderWithApriori(): Found 0x7 PEI FFS files in the 0th FV
추가적으로 해당 스크립트를 범용적으로 이용하기 위해서 아래와 같이 작성해볼 수 있다.
from argparse import ArgumentParser
parser = ArgumentParser(description="Convert GUIDs to text identifiers in UEFI firmware boot log")
parser.add_argument('-g', '--guids', help="Guid.xref file location", required=True)
parser.add_argument('-e', '--guids_extra', help="additional Guid.xref file location")
parser.add_argument('-i', '--log_input', help="input log file location", required=True)
parser.add_argument('-o', '--log_output', help="output log file location (by default input file is changed in place)")
args = parser.parse_args()
GUIDS_FILE_PATH = args.guids
EXTRA_GUIDS_FILE_PATH = args.guids_extra
LOG_IN_FILE_PATH = args.log_input
if args.log_output:
LOG_OUT_FILE_PATH = args.log_output
else:
LOG_OUT_FILE_PATH = args.log_input
...
if LOG_IN_FILE_PATH != LOG_OUT_FILE_PATH:
copyfile(LOG_IN_FILE_PATH, LOG_OUT_FILE_PATH)
...
$ python replace_guids.py -g Build/OvmfX64/DEBUG_GCC5/FV/Guid.xref -e Guid_extra.xref -i debug.log -o debug_parsed.log
$ python replace_guids.py --help
usage: replace_guids.py [-h] -g GUIDS [-e GUIDS_EXTRA] -i LOG_INPUT [-o LOG_OUTPUT]
Convert GUIDs to text identifiers in UEFI firmware boot log
optional arguments:
-h, --help show this help message and exit
-g GUIDS, --guids GUIDS
Guid.xref file location
-e GUIDS_EXTRA, --guids_extra GUIDS_EXTRA
additional Guid.xref file location
-i LOG_INPUT, --log_input LOG_INPUT
input log file location
-o LOG_OUTPUT, --log_output LOG_OUTPUT
output log file location (by default input file is changed in place)
DEBUG 메세지 출력길이 제한
DEBUg 문으로 출력할 수 있는 최대 문자열의 길이가 제한되어 있다. https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/PlatformDebugLibIoPort/DebugLib.c
#define MAX_DEBUG_MESSAGE_LENGTH 0x100
정의에 따라 0x100보다 긴 출력이 발생할 수 있으므로 해당 값을 변경하여 빌드함으로써 그 제한을 늘릴 수 있다.
build --platform=OvmfPkg/OvmfPkgX64.dsc --arch=X64 --buildtarget=DEBUG --tagname=GCC5
\
Last updated