$ gdb
(gdb) add-symbol-file Build/UefiLessonsPkg/RELEASE_GCC5/X64/ShowBootVariables.debug 0x65FD240 -s .data 0x6601F80
add symbol table from file "Build/UefiLessonsPkg/RELEASE_GCC5/X64/ShowBootVariables.debug" at
.text_addr = 0x65fd240
.data_addr = 0x6601f80
(y or n) y
Reading symbols from Build/UefiLessonsPkg/RELEASE_GCC5/X64/ShowBootVariables.debug...
애플리케이션의 Entry Point에 break point을 적용하기 위해서 ShellAppMain 함수를 검색한다.
(gdb) b UefiLessonsPkg/ShowBootVariables/ShowBootVariables.c:63
Breakpoint 1 at 0x664c50f: file /home/kostr/tiano/edk2/UefiLessonsPkg/ShowBootVariables/ShowBootVariables.c, line 68.
원격 디버깅을 실행한다.
(gdb) target remote :1234
Remote debugging using :1234
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x000000000716e151 in ?? ()
연결 이후 QEMU는 중단되며 재실행을 위해서는 GDB에서 c 명령어를 입력하면 된다.
(gdb) c
Continuing.
설정한 break point를 trigger 하기 위해 애플리케이션을 실행한다.
FS0:\> ShowBootVariables.efi
Breakpoint 1, ShellAppMain (Argc=1, Argv=0x6609b98)
at /home/minishell/src/edk2-ws/edk2/UefiLessonsPkg/ShowBootVariables/ShowBootVariables.c:69
69 Status = GetNvramVariable(L"BootCurrent", &gEfiGlobalVariableGuid, (VOID**)&BootCurrent, &OptionSize);
GDB에 편의를 위한 TUI 기능이 존재한다.
(gdb) tui enable
efi.py
앞선 과정들을 효율적으로 수행하기 위해서 Artem Nefedov 의 efi.py 스크립트를 사용한다.
(gdb) efi -64 ShowBootVariables
Turning pagination off
Using pre-defined driver list: ['ShowBootVariables']
The target architecture is assumed to be i386:x86-64:intel
With architecture X64
Looking for addresses in debug.log
EFI file Build/UefiLessonsPkg/RELEASE_GCC5/X64/ShowBootVariables.efi
Base address 0x000065FD000
.text address 0x0000000000000240
.data address 0x0000000000004f80
add symbol table from file "Build/UefiLessonsPkg/RELEASE_GCC5/X64/ShowBootVariables.debug" at
.text_addr = 0x65fd240
.data_addr = 0x6601f80
Restoring pagination
위 과정은 스크립트가 ShowBootVariables 문자열에 대해 debug.log 파일에서 검색하고 주소 정보를 가져온 다음 필요한 심볼을 구해 gdb에 로드한다.
디버깅을 시도해보면 앞선 과정과 동일한 결과를 얻을 수 있다.
run_gdb.sh
efi.py 도 훌륭한 스크립트이지만 아직 수행할 작업이 많다.
적절한 인자로 QEMU를 실행하고 부팅 이후 애플리케이션을 실행해야 하며, 애플리케이션을 다시 컴파일 할 경우 이 과정을 매번 수행해야 한다.
#!/bin/bash### Copyright (c) 2021, Konstantin Aladyshev <aladyshev22@gmail.com>## SPDX-License-Identifier: MIT####### Controllable parameters #####QEMU_SHARED_FOLDER=~/UEFI_diskPACKAGE=UefiLessonsPkg###################################functionshow_help {echo"Description:"echo" run_gdb.sh is a script that helps to debug UEFI shell applications and drivers"echo""echo"Usage: run_gdb.sh -m <module> [-1|-f|-p <package>|-q <dir>]"echo" -1 This is a first run of this configuration" echo " (in this case before main gdb launch there would be another QEMU start that will create 'debug.log' file)"
echo" -f Load all debug symbols"echo" (this will load all OVMF debug symbols - with this you could step inside OVMF functions)"echo" -m <module> UEFI module to debug"echo" -p <package> UEFI package to debug"echo" (by default it is equal to PACKAGE variable in the head of the script)"echo" -q <dir> QEMU shared directory"echo" (by default it is equal to QEMU_SHARED_FOLDER variable in the head of the script)"echo""echo"Examples:"echo" run_gdb.sh -1 -m MyApp - create 'debug.log' file with the necessary address information for the 'MyApp'"echo" and debug it with gdb"echo" run_gdb.sh -1 -m MyApp -f - create 'debug.log' file with the necessary address information for the 'MyApp'" echo " and debug it with gdb (all debug symbols are included, i.e. you can step into OVMF functions)"
echo " run_gdb.sh -m MyApp - debug 'MyApp' with gdb ('debug.log' was created in the last run, no need to remake it again)"
}# A POSIX variableOPTIND=1# Reset in case getopts has been used previously in the shell.whilegetopts"h?1fm:q:p:"opt; docase"$opt"inh|\?)show_helpexit0 ;;1) FIRST_RUN=1 ;;f) FULL=1 ;;m) TARGET=$OPTARG ;;q) QEMU_SHARED_FOLDER=$OPTARG ;;p) PACKAGE=$OPTARG ;;esacdoneshift $((OPTIND-1))[ "${1:-}"="--" ] &&shiftif [[ !-z"${FULL}" ]]; then DRIVERS=''else DRIVERS=${TARGET}fiif [[ -z $TARGET ]]; thenecho"Error! Module is not provided."echo""show_helpexit1fifunctiontest_file { FILE_NAME=$1if [[ !-f ${FILE_NAME} ]]; thenecho"Error! There is no file ${FILE_NAME}"exit1;fi}TARGET_INF="${PACKAGE}/${TARGET}/${TARGET}.inf"TARGET_C="${PACKAGE}/${TARGET}/${TARGET}.c"OVMF="Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd"test_file"${TARGET_INF}"test_file"${TARGET_C}"test_file"${OVMF}"ENTRY_POINT_NAME=$(grepENTRY_POINT ${TARGET_INF} |cut-f2-d"=")if [ ${ENTRY_POINT_NAME} =="ShellCEntryLib" ]; then ENTRY_POINT_NAME="ShellAppMain"fiENTRY_POINT_LINE=$(grep-n ${ENTRY_POINT_NAME} ${TARGET_C} |cut-f1-d":")MODULE_TYPE=$(grepMODULE_TYPE ${TARGET_INF} |cut-f2-d"=")if [ ${MODULE_TYPE} =="UEFI_DRIVER" ]; then LAUNCH_COMMAND="load fs0:${TARGET}.efi"else LAUNCH_COMMAND="fs0:${TARGET}.efi"fiif [[ !-z"${FIRST_RUN}"||!-f debug.log ]]; thentouchdebug.log# If it is a first run, we need to create 'debug.log' file for addresses TARGET_EFI="Build/${PACKAGE}/RELEASE_GCC5/X64/${TARGET}.efi"test_file"${TARGET_EFI}"cp ${TARGET_EFI} ${QEMU_SHARED_FOLDER}tmuxnew-session \; \send-keys"tail -f debug.log"Enter \; \split-window-v \; \send-keys"qemu-system-x86_64 \ -drive if=pflash,format=raw,readonly,file=${OVMF} \ -drive format=raw,file=fat:rw:${QEMU_SHARED_FOLDER} \ -net none \ -nographic \ -global isa-debugcon.iobase=0x402 \ -debugcon file:debug.log \ -s"C-mEnter \; \send-keysC-mEnter \; \send-keys"${LAUNCH_COMMAND}"Enter \;fitest_file"${QEMU_SHARED_FOLDER}/${TARGET}.efi"touchdebug_temp.logtmuxnew-session \; \send-keys"gdb -ex 'source efi.py' -tui"Enter \; \split-window-h \; \send-keys"tail -f debug_temp.log"Enter \; \split-window-v \; \send-keys"qemu-system-x86_64 \ -drive if=pflash,format=raw,readonly,file=${OVMF} \ -drive format=raw,file=fat:rw:${QEMU_SHARED_FOLDER} \ -net none \ -nographic \ -global isa-debugcon.iobase=0x402 \ -debugcon file:debug_temp.log \ -s"C-mEnter \; \select-pane-t0 \; \send-keys"efi -64 ${DRIVERS}"Enter \; \send-keys"b ${TARGET_C}:${ENTRY_POINT_LINE}"Enter \; \send-keysEnter \; \send-keys"target remote :1234"Enter \; \send-keys"c"Enter \; \select-pane-t2 \; \send-keysC-mEnter \; \send-keys"${LAUNCH_COMMAND}"Enter \; wnd
위 코드는 tmux 세션을 이용하여 필요한 키를 자동적으로 전달할 수 있다.
$ ./run_gdb.sh -h
Description:
run_gdb.sh is a script that helps to debug UEFI shell applications and drivers
Usage: run_gdb.sh -m <module> [-1|-f|-p <package>|-q <dir>]
-1 This is a first run of this configuration
(in this case before main gdb launch there would be another QEMU start that will create 'debug.log' file)
-f Load all debug symbols
(this will load all OVMF debug symbols - with this you could step inside OVMF functions)
-m <module> UEFI module to debug
-p <package> UEFI package to debug
(by default it is equal to PACKAGE variable in the head of the script)
-q <dir> QEMU shared directory
(by default it is equal to QEMU_SHARED_FOLDER variable in the head of the script)
Examples:
run_gdb.sh -1 -m MyApp - create 'debug.log' file with the necessary address information for the 'MyApp'
and debug it with gdb
run_gdb.sh -1 -m MyApp -f - create 'debug.log' file with the necessary address information for the 'MyApp'
and debug it with gdb (all debug symbols are included, i.e. you can step into OVMF functions)
run_gdb.sh -m MyApp - debug 'MyApp' with gdb ('debug.log' was created in the last run, no need to remake it again)
처음 디버깅하는 경우 debug.log 를 생성해야 한다.
./run_gdb.sh -1 -m ShowBootVariables
이후 QEMU 환경에서 enter만 입력해도 대상 애플리케이션이 실행된다.
이후 다른 터미널을 통해 tmux 세션을 닫거나 tmux 환경에서 ctrl+b 입력하고 :kill-session 을 입력하여 tmux 세션을 닫는다.
다시 로드된 QEMU 환경과 디버그 심볼이 추가된 GDB 환경을 확인할 수 있다. QEMU 환경에서 enter 를 입력하면 대상 애플리케이션 Entry Point에break point가 발생한다.
모든 symbols 로드하기
만약 애플리케이션 또는 드라이버 외부에서 정의된 함수 (예: gRT->GetVariable)를 단계별로 실행하려면 모든 심볼을 GDB에 로드해야 한다.
이 때 -f 인자를 통해 run_gdb.sh 를 실행하면 된다.
./run_gdb.sh -1 -m ShowBootVariables -f
함수들의 심볼이 존재하므로 원하는 지점에 break point를 설정할 수 있다.
OVMF 자체 Debug
OVMF 자체를 디버깅할 경우에 시작 시 QEMU를 중지해야 한다. 이 작업을 위해 -S 옵션을 사용할 수 있다.
#!/bin/bash### Copyright (c) 2021, Konstantin Aladyshev <aladyshev22@gmail.com>## SPDX-License-Identifier: MIT####### Controllable parameters #####QEMU_SHARED_FOLDER=~/UEFI_disk###################################functionshow_help {echo"Description:"echo" run_gdb_ovmf.sh is a script that helps to debug OVMF"echo""echo"Usage: run_gdb_ovmf.sh [-1] [-q <dir>]"echo" -1 This is a first run of this configuration" echo " (in this case before main gdb launch there would be another QEMU start that will create 'debug.log' file)"
echo" -q <dir> QEMU shared directory"echo" (by default it is equal to QEMU_SHARED_FOLDER variable in the head of the script)"echo""echo"Examples:"echo" run_gdb_ovmf.sh -1 - create 'debug.log' file with the necessary address information"echo" and debug OVMF it with gdb" echo " run_gdb_ovmf.sh - debug OVMF with gdb ('debug.log' was created in the last run, no need to remake it again)"
}# A POSIX variableOPTIND=1# Reset in case getopts has been used previously in the shell.whilegetopts"h?1q:"opt; docase"$opt"inh|\?)show_helpexit0 ;;1) FIRST_RUN=1 ;;q) QEMU_SHARED_FOLDER=$OPTARG ;;esacdoneshift $((OPTIND-1))[ "${1:-}"="--" ] &&shiftfunctiontest_file { FILE_NAME=$1if [[ !-f ${FILE_NAME} ]]; thenecho"Error! There is no file ${FILE_NAME}"exit1;fi}OVMF="Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd"test_file"${OVMF}"if [[ !-z"${FIRST_RUN}"||!-f debug.log ]]; thentouchdebug.log# If it is a first run, we need to create 'debug.log' file for addressestmuxnew-session \; \send-keys"tail -f debug.log"Enter \; \split-window-v \; \send-keys"qemu-system-x86_64 \ -drive if=pflash,format=raw,readonly,file=${OVMF} \ -drive format=raw,file=fat:rw:${QEMU_SHARED_FOLDER} \ -net none \ -nographic \ -global isa-debugcon.iobase=0x402 \ -debugcon file:debug.log \ -s"C-mEnter \;fitouchdebug_temp.logtmuxnew-session \; \send-keys"gdb -ex 'source efi.py' -tui"Enter \; \split-window-h \; \send-keys"tail -f debug_temp.log"Enter \; \split-window-v \; \send-keys"qemu-system-x86_64 \ -drive if=pflash,format=raw,readonly,file=${OVMF} \ -drive format=raw,file=fat:rw:${QEMU_SHARED_FOLDER} \ -net none \ -nographic \ -global isa-debugcon.iobase=0x402 \ -debugcon file:debug_temp.log \ -s -S"C-mEnter \; \select-pane-t0 \; \send-keys"efi -64"Enter \; \send-keys"target remote :1234"Enter \;
$ ./run_gdb_ovmf.sh -h
Description:
run_gdb_ovmf.sh is a script that helps to debug OVMF
Usage: run_gdb_ovmf.sh [-1] [-q <dir>]
-1 This is a first run of this configuration
(in this case before main gdb launch there would be another QEMU start that will create 'debug.log' file)
-q <dir> QEMU shared directory
(by default it is equal to QEMU_SHARED_FOLDER variable in the head of the script)
Examples:
run_gdb_ovmf.sh -1 - create 'debug.log' file with the necessary address information
and debug OVMF it with gdb
run_gdb_ovmf.sh - debug OVMF with gdb ('debug.log' was created in the last run, no need to remake it again)
run_gdb.sh 와 별반 다르지 않지만 break point을 설정하거나 OVMF를 실행하지 않는다는 차이점이 존재한다.
GDB cheatsheet
아래는 GDB 환경에서 사용할 수 있는 명령어 일부의 정보이다.
s - step
n - next
fin - step out
i loc - info about local variables
i arg - info about function arguments
p <var> - print value of <var>
b <number> - set breakpoint at line <number> in the current file
b <file>:<number> - set breakpoint at line <number> in the <file> file
b <func> - break on function <func>
i b - info breakpoints
d <number> - delete breakpoint <number>
c - continue
q - quit GDB
Ctrl+p - previous GDB command in history
Ctrl+n - next GDB command in history
Ctrl+x and o - change active window in tui mode
16 진수로 값을 출력하고 싶을 경우에는 p/x 를 사용한다.
CHAR16 문자열을 출력할 경우 x /sh 명령어를 이용할 수 있다. 다음은 장치 경로를 출력하는 예제이다.
(gdb) p ConvertDevicePathToText(DevicePath, 0, 1)
$1 = (CHAR16 *) 0x6d04518
(gdb) x /sh 0x6d04518
0x6d04518: u"PciRoot(0x0)/Pci(0x2,0x0)"
또는
(gdb) x /sh ConvertDevicePathToText(DevicePath, 0, 1)
0x6d02098: u"PciRoot(0x0)/Pci(0x2,0x0)"
만약 출력 데이터의 길이가 길어 완전히 출력하지 못할 경우 다음 명령어를 통해 출력 길이 제한을 해제할 수 있다.
(gdb) set print elements 0
TMUX cheatsheet
Ctrl+b and up/down/left/right - switch between panes
Ctrl+b and :kill-session - close all panes
Ctrl+b and Ctrl+up/down/left/right - change pane size