A dedicated unikernel for microservices

Wednesday, December 22, 2021

Building a GDB stub for a Virtual Machine Monitor (VMM)

This post talks about how the GDB stub has been adapted to be used in a VMM. The VMM is ToroV (see https://github.com/torokernel/torov). ToroV is a kernel in user space that allows the deployment of applications as Virtual Machines. Applications request services from the OS by using hypercalls that the VMM catches. For example, to open a file, the application triggers the open hypercall, the ToroV catches the hypercall, it serves it and, finally, it returns the control to the guest. On way to debug these applications is to add a GDB stub to the VMM that is used to control the guest. If you want to know more about the GDB stub, please check the following post

The GDB stub in the VMM allows debugging applications that are deployed by using ToroV. Roughly speaking, the GDB stub is the interface between a GDB client and the underlying debugging hardware. In one side, the GDB stub interacts with a GDB client, e.g., gdb, to set breakpoints, to read/write registers or to execute step-by-step. In the other side, the GDB stub interacts with the underlying hardware/hypervisor to implement these features. QEMU includes a built-in GDB stub that allows to debug guests. You can configure the stub to listen on a TCP port to which the GDB client connect to. On that port, the stub gets commands like "breakpoint main", "continue", to control the execution of the guest.   

The GDB stub runs in the VMM, i.e., as a part of a user's process in the host. The GDB stub sets breakpoints by replacing the first byte of the instruction with the INT3 opcode. In addition, we need to tell KVM that the debugging interruptions must trigger a VMEXIT. To do this, we need to set the kvm_guest_debug structure by using the KVM_SET_GUEST_DEBUG to the corresponding VCPU. The following snip of code shows this:

ret := fpIOCtl(vcpu.vcpufd, KVM_SET_GUEST_DEBUG, @debug);

The structure kvm_guest_debug is defined as follows:

kvm_guest_debug = record
  control: DWORD;
  pad: DWORD;
  debugreg: array[0..7] of QWORD;

By setting this, the INT3 and INT1 triggers a KVM_EXIT_DEBUG. In the following lines, we can see how we identify the reason for the VMEXIT. Depending on the exit reason, we inform the GDB stub that either a breakpoint or an step have been reached:
debugexit := pkvm_debug_exit_arch(@guestvcpu.run.padding_exit[0]);

if debugexit^.exception = 3 then
  BPHandler(@guestvcpu) // handler a breakpoint
  StepHandler(@guestvcpu); // handler an step

The debugexit variable is a pointer to a kvm_debug_exit_arch structure, which is defined as follows:

kvm_debug_exit_arch = record
    exception: DWORD;
    pad: DWORD;
    pc: QWORD;
    dr6: QWORD;
    dr7: QWORD;

The GDB stub that ToroV includes is similar to the built-in GDB stub in Qemu. The user configures it by setting in which port the gdb stub listens for new connections from gdb clients.