If you don’t want to read the whole thing, just go to the TLDR section with the precise steps on what and how to install/configure.

Debugging the operation system in real time and seeing how interrupts occurs and threads context switching is a fascinating thing I must say. While working on a PingPorts, I was curious how the OS network stack decides when to send a TCP Reset (RST) packet when nobody listens on the port (spoiler alert! See net/ipv4/tcp_ipv4.c file, tcp_v4_rcv function, which calls __inet_lookup_skb and branching to no_tcp_socket). Quickly looking at the TCP/IP implementation in the kernel did not give any specific results, so I decided to setup virtual machine with the latest kernel compiled in debug mode and step in gdb to observe what is happening there. Here is steps to configure VirtualBox, build the Linux kernel and finally debug using gdb.

For the sake of clarity:

  • Host - The actual physical computer on which a virtual machine is running.
  • Guest - Virtual machine running on a host.

VirtualBox Setup

I’ve used Ubuntu as host and guest OS installed on VirtualBox. Make sure you’ve installed Guest Additions on guest to be able to share compiled kernel with host (using Shared Folders settings). After the guest OS is installed, let’s configure the Serial Ports in VirtualBox, so gdb on host can communicate with the guest, let’s call it /tmp/debug-pipe.

auto_center Serial port configuration in VirtualBox

The next step is to install all the required software packages on guest, needed to build the kernel: sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison

Building Linux Kernel

Go ahead and pick kernel version you want to debug, in my case it was the latest stable version on the time of writing, then, clone the source code on a guest machine: git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git.
The configuration .config file in the kernel source root folder must be adjusted, in order to build the debug version of kernel. First, let’s copy existing kernel configuration in guest: cp /boot/config-<your_kernel_release> .config to the root folder with the kernel source code. Then, change the following configuration properties by opening .config file in text editor or running make menuconfig in a source code root folder:

  • Set CONFIG_DEBUG_INFO to y. As the name says, this will enable including debug information into output executable modules.
  • Set CONFIG_KGDB to y. This enables built-in kernel debugger.
  • Comment out CONFIG_RANDOMIZE_BASE, to disabled KASLR. This is needed as gdb will fail to identify the correct address of symbols, when trying to set breakpoints, using function name, etc.
  • Set CONFIG_GDB_SCRIPTS to y. This will create a symbolic link to the vmlinux-gdb.py gdb helper commands and functions for kernel debugging in a source code root folder.
  • Set CONFIG_KGDB_SERIAL_CONSOLE to y. This will enable usage of gdb over the serial console.

Now, the final step is to actually start the build process: switch to the root source code directory and run: make and then make modules, this will take some time. Then, once it has been compiled, run the installation process: sudo make modules_install, sudo make install and make scripts_gdb.

Debugger Setup

On a guest you need to enable debugging support during the boot process by editing the boot settings file: /etc/default/grub. Just append the following line:GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 nokaslr kgdboc=kbd,ttyS0,115200" to the end of the file and reboot the guestto use kgdb over serial console.
Now, the next step is to convert the socket created by VirtualBox (/tmp/debug-pipe) using socat tool to PTY using the following command: socat -d -d -d -d /tmp/debug-pipe PTY,link=/tmp/debug-pipe-pty.
OK, now the PTY is ready on the /tmp/debug-pipe-pty and the final step is to start gdb on a host and attach it to the guest machine. But first, let’s share the guest kernel source code folder with the host machine, so gdb can load the vmlinux file from this folder.
After the kernel source code folder is available to host machine, run the following command in kernel source code root folder on a host: gdb vmlinux -ex "target remote /tmp/debug-pipe-pty" and immediately run the echo g > /proc/sysrq-trigger on guest machine to debug break in gdb on a host machine. If everything works fine, there must be the following output from gdb:

auto_center GDB on host system connected to guest

TLDR

  • VirtualBox Setup:
    • Install Ubuntu on VirtualBox.
    • Install Guest Additions.
    • Install required packages: sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison.
    • Add serial port: /tmp/debug-pipe in Serial Ports settings.
  • Building Linux Kernel:
    • git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git on the guest machine.
    • Copy current kernel config file on guest to kernel source code root folder: cp /boot/config-<your_kernel_release> .config.
    • Edit .config file in kernel source code root folder:
      • Set CONFIG_DEBUG_INFO to y.
      • Set CONFIG_KGDB to y.
      • Comment out CONFIG_RANDOMIZE_BASE.
      • Set CONFIG_GDB_SCRIPTS to y.
      • Set CONFIG_KGDB_SERIAL_CONSOLE to y.
    • Run make and then make modules in the kernel source code root folder.
    • After compilation is finished, run: sudo make modules_install, sudo make install and make scripts_gdb.
  • Debugger Setup:
    • Append GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 nokaslr kgdboc=kbd,ttyS0,115200" to /etc/default/grub on the guest machine and reboot.
    • Run: socat -d -d -d -d /tmp/debug-pipe PTY,link=/tmp/debug-pipe-pty on host machine.
    • Share the Linux kernel source code folder from guest to host machine.
    • Run: gdb vmlinux -ex "target remote /tmp/debug-pipe-pty" on the host machine in the kernel source code folder.
    • Run echo g > /proc/sysrq-trigger on the guest machine.