42. GDB를 이용한 Driver/Application 및 OVMF Debug

GDB를 사용하여 디버깅을 수행하기 위해서는 올바른 오프셋에 디버그 심볼을 로그해야 한다.

  • 메모리 상의 애플리케이션 오프셋

  • 애플리케이션 내부의 오프셋

이번 과정에서는 이전에 개발한 ShowBootVariable.efi 애플리케이션을 디버깅한다.

메모리 상의 애플리케이션 오프셋 구하기

QEMU에서 OVMF를 실행한다.

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

또 다른 터미널을 이용하여 아래 명령어를 실행한다.

tail -f debug.log

이를 통해 런타임 로그를 추적할 수 있다.

부팅 이후 ShowBootVariables.efi 애플리케이션을 실행하고 아래와 같은 메세지를 debug.log 에서 확인한다.

Loading driver at 0x00006649000 EntryPoint=0x0000664C80F ShowBootVariables.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 666B898
ProtectUefiImageCommon - 0x666B4C0
  - 0x0000000006649000 - 0x0000000000005540
InstallProtocolInterface: 752F3136-4E16-4FDC-A22A-E5F46812F4CA 7EA36C8

다음 문장에서 제작한 이미지가 로드된 주소를 확인한다. 0x6649000

Application section의 offset 구하기

objdump 명령어를 이용하여 .efi 파일에 포함된 섹션을 확인한다.

또한 .debug 파일에 존재하는 섹션도 확인한다.

두 정보의 차이는 .debug 정보의 유무이다.

objdump 결과를 통해서 필요한 섹션의 오프셋을 확인할 수 있다. (환경에 따라 그 결과가 다를 수도 있다.)

  • .text -> 0x240

  • .data -> 0x4f40

GDB에 디버그 심볼적용

QEMU 실행 시 -s 옵션을 포함하여 원격 디버깅을 활성화 한다. 해당 옵션은 tcp::1234 의 축어이며 QEMU는 해당 포트에서 GDB 연결을 수신할 수 있다.

심볼 적용을 위해 사전에 확인한 애플리케이션 이미지 주소에 구한 .text.data 의 오프셋 값 연산한다.

GDB를 실행하고 다음과 같이 심볼을 적용한다.

애플리케이션의 Entry Point에 break point을 적용하기 위해서 ShellAppMain 함수를 검색한다.

이후 진입점에 break point를 설정한다.

원격 디버깅을 실행한다.

연결 이후 QEMU는 중단되며 재실행을 위해서는 GDB에서 c 명령어를 입력하면 된다.

설정한 break point를 trigger 하기 위해 애플리케이션을 실행한다.

GDB에 편의를 위한 TUI 기능이 존재한다.

efi.py

앞선 과정들을 효율적으로 수행하기 위해서 Artem Nefedov 의 efi.py 스크립트를 사용한다.

해당 스크립트를 통해 디버그 심볼을 로드하는데 큰 도움을 받을 수 있다. https://github.com/artem-nefedov/uefi-gdb/blob/master/efi.py

gdb 내부에서 추가된 efi 명령어를 이용해 디버그 심볼을 로드한다.

위 과정은 스크립트가 ShowBootVariables 문자열에 대해 debug.log 파일에서 검색하고 주소 정보를 가져온 다음 필요한 심볼을 구해 gdb에 로드한다.

디버깅을 시도해보면 앞선 과정과 동일한 결과를 얻을 수 있다.

run_gdb.sh

efi.py 도 훌륭한 스크립트이지만 아직 수행할 작업이 많다.

  • 적절한 인자로 QEMU를 실행하고 부팅 이후 애플리케이션을 실행해야 하며, 애플리케이션을 다시 컴파일 할 경우 이 과정을 매번 수행해야 한다.

  • efi.py 를 GDB에 로드한 뒤 실행하여 efi 명령을 실행해야 한다.

  • break point 지정을 위해 함수 Entry Point를 검색해야 한다.

  • QEMU 환경을 디버깅하기 위해 별도의 터미널이 요구된다.

  • GDB를 QEMU에 연결하고 애플리케이션을 실행해야 한다.

따라서 디버깅과 동시에 OVMF 로그를 볼 수 있으며 위 과정을 자동화할 수 있는 스크립트를 작성하자. https://github.com/Kostr/UEFI-Lessons/blob/master/scripts/run_gdb.sh

위 코드는 tmux 세션을 이용하여 필요한 키를 자동적으로 전달할 수 있다.

처음 디버깅하는 경우 debug.log 를 생성해야 한다.

이후 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 를 실행하면 된다.

함수들의 심볼이 존재하므로 원하는 지점에 break point를 설정할 수 있다.

OVMF 자체 Debug

OVMF 자체를 디버깅할 경우에 시작 시 QEMU를 중지해야 한다. 이 작업을 위해 -S 옵션을 사용할 수 있다.

이후 GDB을 실행하고 이전과 같이 debug.log 파일을 통해 필요 symbols를 로드해야 한다.

아래는 해당 작업을 자동화하기 위한 run_gdb_ovmf.sh 스크립트이다. https://github.com/Kostr/UEFI-Lessons/blob/master/scripts/run_gdb_ovmf.sh

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 명령어를 이용할 수 있다. 다음은 장치 경로를 출력하는 예제이다.

또는

만약 출력 데이터의 길이가 길어 완전히 출력하지 못할 경우 다음 명령어를 통해 출력 길이 제한을 해제할 수 있다.

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

Last updated