Debugging OVMF with GDB
Tags: firmware, uefi, ovmf, gdb
Published: 2019-12-09 02:36:39

In this blog post, I will describe how to debug OVMF using GDB without any special tool unlike another post[1].

## Code Mapping in UEFI

On x64 UEFI, it provides flat single address memory space and places the firmware itself and UEFI images on the space without any memory protection. In this way, we can do source code level debugging any UEFI code with a debugger. On OVMF, each feature is modularized
and the module is loaded as UEFI image. BootServices is included in `DxeCore.efi`, loaded at boot time.

```
Notify: PPI Guid: EE16160A-E8BE-47A6-820A-C6900DB0250A, Peim notify entry point: 836CA9
PlatformPei: ClearCacheOnMpServicesAvailable
DiscoverPeimsAndOrderWithApriori(): Found 0x0 PEI FFS files in the 1th FV
DXE IPL Entry
Loading PEIM D6A2CB7F-6A18-4E2F-B43B-9920A733700A
Loading PEIM at 0x00007EA8000 EntryPoint=0x00007EAB0BC DxeCore.efi
Loading DXE CORE at 0x00007EA8000 EntryPoint=0x00007EAB0BC
```

## Debug Symbols in EDK2

EDK2 build system generates debug symbol information `*.debug` along with executables `*.efi` on debug build (`-b DEBUG`). If you use gcc (example: `GCC5`), it compiles source code to ELF object files, link with custom linker script, and convert to PE format. Thus, the debug info is for ELF
and can be recognized by GDB. On the other hand, Visual Studio and clang/lld (`CLANG9`)[2] generates PE/COFF directly and the debug info may be PDB.

To summarize, the points are:

* OVMF code is placed on the flat single memory space.
* GDB can debug EDK2 UEFI image built with gcc

Let's take a look at how to debug OVMF.

## Building EDK2

Build EDK2 using gcc as usual.

```shell
$ git clone git@github.com:tianocore/edk2.git
$ cd edk2
$ git submodule update --init --recursive
$ make -C BaseTools
$ source ./edksetup.sh
$ build -p OvmfPkg/OvmfPkgX64.dsc -b DEBUG -a X64 -t GCC5
```

To make debugging easy, create a Makefile as follow. Note that we have to connect debugcon at 0x402 to dump debug information (`debug.log`) from OVMF[4].

```makefile
#!/usr/bin/env make

SHELL=/bin/bash

LOG=debug.log
OVMFBASE=edk2/Build/OvmfX64/DEBUG_GCC5/
OVMFCODE=$(OVMFBASE)/FV/OVMF_CODE.fd
OVMFVARS=$(OVMFBASE)/FV/OVMF_VARS.fd
QEMU=qemu-system-x86_64
QEMUFLAGS=-drive format=raw,file=fat:rw:image \
          -drive if=pflash,format=raw,readonly,file=$(OVMFCODE) \
          -drive if=pflash,format=raw,file=$(OVMFVARS) \
          -debugcon file:$(LOG) -global isa-debugcon.iobase=0x402 \
          -serial stdio \
          -nographic \
          -nodefaults

run:
    $(QEMU) $(QEMUFLAGS)

debug:
    $(QEMU) $(QEMUFLAGS) -s -S

.PHONY: run debug
```

Before debugging, run the firmware to get `debug.log`. It may be better to provide `startup.nsh` script.

```shell
$ make run
```

Now, we have `debug.log`. It includes the addresses of loaded UEFI images like this:

```
Loading PEIM at 0x00007EA8000 EntryPoint=0x00007EAB0BC DxeCore.efi
```

Next, extract text section (`.text`) RVA from `*.efi` PE binaries. This can be done by `readelf` if it is ELF, but the images are PE format. Here we use [retrage/peinfo](https://github.com/retrage/peinfo)[3].

```shell
$ git clone git@github.com:retrage/peinfo.git
$ cd peinfo
$ make
```

peinfo extracts section information from a binary. This time we want to know VirtualAddress in RVA.

```
Name: .text
VirtualSize: 0x000204c0
VirtualAddress: 0x00000240
SizeOfRawData: 0x000204c0
PointerToRawData: 0x00000240
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0x60000020
```

Run following bash script with `debug.log` and peinfo. This outputs a snippet of GDB script that adds symbol information (`add-symbol-file`). It calculates the address of UEFI image text section from base address and VirtualAddress.

```bash
#!/bin/bash

LOG="debug.log"
BUILD="edk2/Build/OvmfX64/DEBUG_GCC5/X64"
PEINFO="peinfo/peinfo"

cat ${LOG} | grep Loading | grep -i efi | while read LINE; do
  BASE="`echo ${LINE} | cut -d " " -f4`"
  NAME="`echo ${LINE} | cut -d " " -f6 | tr -d "[:cntrl:]"`"
  ADDR="`${PEINFO} ${BUILD}/${NAME} \
        | grep -A 5 text | grep VirtualAddress | cut -d " " -f2`"
  TEXT="`python -c "print(hex(${BASE} + ${ADDR}))"`"
  SYMS="`echo ${NAME} | sed -e "s/\.efi/\.debug/g"`"
  echo "add-symbol-file ${BUILD}/${SYMS} ${TEXT}"
done
```

```shell
$ bash gen_symbol_offsets.sh > gdbscript
```

The generated GDB script is like this:

```gdb
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/PcdPeim.debug 0x82c380
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/ReportStatusCodeRouterPei.debug 0x831080
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/StatusCodeHandlerPei.debug 0x833100
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/PlatformPei.debug 0x835100
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/PeiCore.debug 0x7ee8240
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/DxeIpl.debug 0x7ee3240
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/S3Resume2Pei.debug 0x7edf240
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/CpuMpPei.debug 0x7ed6240
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/DxeCore.debug 0x7ea8240
add-symbol-file edk2/Build/OvmfX64/DEBUG_GCC5/X64/DevicePathDxe.debug 0x7b8f240
```

Now we are ready.

```shell
$ make debug
```

Let's place a breakpoint at `BootServices->HandleProtocol()`.

```gdb
(gdb) source gdbscript
(gdb) b CoreHandleProtocol
(gdb) target remote localhost:1234
(gdb) c
```

The debugger stops, and we can do source code level debug.

```gdb
   ┌──/home/akira/src/ovmf-debug/edk2/MdeModulePkg/Core/Dxe/Hand/Handle.c──────┐
   │933     CoreHandleProtocol (                                               │
   │934       IN EFI_HANDLE       UserHandle,                                  │
   │935       IN EFI_GUID         *Protocol,                                   │
   │936       OUT VOID            **Interface                                  │
   │937       )                                                                │
B+>│938     {                                                                  │
   │939       return CoreOpenProtocol (                                        │
   │940               UserHandle,                                              │
   │941               Protocol,                                                │
   │942               Interface,                                               │
   │943               gDxeCoreImageHandle,                                     │
   │944               NULL,                                                    │
   │945               EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL                     │
   └───────────────────────────────────────────────────────────────────────────┘
remote Thread 1 In: CoreHandleProtocol                      L938  PC: 0x7eb6ad4 



(gdb) 
```

### 2019/12/05 Postscript

tnishinaga gave me the improved version of the script
to support multiple search paths. Thank you!

```bash
#!/bin/bash

LOG="debug.log"
BUILD="./Build"
SEARCHPATHS="./Build/OvmfX64/DEBUG_GCC5/X64/ ./Build/Edk2SamplePkgX64/DEBUG_GCC5/X64/"
PEINFO="peinfo/peinfo"

cat ${LOG} | grep Loading | grep -i efi | while read LINE; do
  BASE="`echo ${LINE} | cut -d " " -f4`"
  NAME="`echo ${LINE} | cut -d " " -f6 | tr -d "[:cntrl:]"`"
  EFIFILE="`find ${SEARCHPATHS} -name ${NAME} -maxdepth 1 -type f`"
  ADDR="`${PEINFO} ${EFIFILE} \
        | grep -A 5 text | grep VirtualAddress | cut -d " " -f2`"
  TEXT="`python -c "print(hex(${BASE} + ${ADDR}))"`"
  SYMS="`echo ${NAME} | sed -e "s/\.efi/\.debug/g"`"
  SYMFILE="`find ${SEARCHPATHS} -name ${SYMS} -maxdepth 1 -type f`"
  echo "add-symbol-file ${SYMFILE} ${TEXT}"
done
```

## References

* [1] https://jp3bgy.github.io/blog/uefi/2018/12/24/How-to-Source-Debug-OVMF.html
* [2] https://github.com/tianocore/edk2/commit/15330934dc860c20b2143c802f3b4285e89021e3
* [3] https://github.com/retrage/peinfo
* [4] https://github.com/tianocore/tianocore.github.io/wiki/How-to-debug-OVMF-with-QEMU-using-GDB