Introduction

The GNU DeBugger (gdb) is a very powerful debugger which allows to debug programs at source level. It allows evaluation of C statements (in contrast to the Windows Kernel debugger) and many more things.

We will show how to setup a virtual machine on top of a Linux host using libvirt (virsh and virt-install) and how to use gdb to debug the ReactOS inside this VM.

Note that there are two ways to debug ReactOS:

  • via the gdb support built into ReactOS.
  • via the gdb support built into qemu

Since qemu offers more features we will use this approach in this guide. If you install ReactOS on physical hardware (for example when you write a driver for a new device) then you need to debug it via the gdb support built into ReactOS via a serial cable.

Step 1: Get the ReactOS build environment (RosBE) working.

Download RosBE from following location:

https://reactos.org/wiki/Build_Environment

It will be a file named RosBE-Unix-2.2.1.tar.bz2. Always pick the latest version.

Untar it using

tar jxf RosBE-Unix-2.2.1.tar.bz2

You might have to change the version number.

Then inside the extracted directory run RosBE-Builder.sh as follows:

cd RosBE-Unix-2.2.1
./RosBE-Builder.sh

Answer the questions (like installation directory) We assume that the installation directory is

/home/johannes/reactos/RosBE

in this document.

Step 2: Check out current ReactOS source code

Get the current source code. Development is made against the master branch there are also other branches starting with releases (such as releases/0.4.14). We will now use the master branch since we want to do some debugging and development.

So here we go:

git clone https://github.com/reactos/reactos.git

The master branch should be checked out initially so no need to switch branches now.

Step 3: Configure and build ReactOS

To build ReactOS you first need to put your shell into RoSBE mode. To do so, run

/home/johannes/reactos/RosBE/RosBE.sh 

ReactOS is built in separate directories. By doing so one can have several different builds and the source directories are kept clean.

When debugging via the qemu gdb support one has only to tell the build system that binaries containing debug symbols should be used. You can use any directory name instead

mkdir output-gdb
cd output-gdb
../configure.sh -DSEPARATE_DBG:BOOL=TRUE
ninja bootcd

When debugging with the built in ReactOS gdb support you need to enable it: configure it with

../configure.sh -DGDB:BOOL=TRUE -DSEPARATE_DBG:BOOL=TRUE

Step 4: Create a virtual machine using virt install

To install libvirt and qemu on a Ubuntu (or derivate) machine, do:

sudo apt install libvirt-bin
sudo apt install libvirt-daemon
sudo apt install qemu
sudo apt install libvirt-clients
sudo apt install virtinst

To create a virtual machine, you can use virt-install as follows (you may want to adjust some parameters):

virt-install --name reactos --vcpus 1 --ram 4096 --cdrom ./bootcd.iso --disk size=40 --graphics vnc,port=5970,listen=0.0.0.0 --arch i386

Note that it is important to specify i386 as architecture, else debugging will not work.

Use this inside the output-gdb directory (where you built ReactOS).

Step 5: Install ReactOS

With a VNC viewer of your choice connect to the graphical console if the new VM. The IP address should be the one of the Linux machine where the VM is running on and the port is the one specified in the virt-install call.

Example would be:

vncviewer 10.43.224.40:5970

You can accept all default values (including using the whole harddisk for the installation). The install process is very straight forward, so I won’t go into more details here.

To (re-)start the VM use (reactos is the name of the VM):

virsh start reactos

Step 6: Prepare VM for use with gdb

To configure qemu’s gdbserver support, you need to edit the XML configuration of the virtual machine. Run:

virsh edit reactos

and add:

xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'

to the first line, so it reads something like:

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>

Then add command line parameters for starting qemu with gdb server (at the end of the file, but before the closing domain tag:

<qemu:commandline>
   <qemu:arg value='-gdb'/>
   <qemu:arg value='tcp::2000'/>
</qemu:commandline>

Also now is a good time to double check the qemu emulator used: this should be:

<emulator>/usr/bin/qemu-system-i386</emulator>

For most changes it is necessary to restart the virtual machine (with:

virsh shutdown reactos
virsh start reactos

Step 7: Attach gdb to a running ReactOS VM.

For gdb, make sure you have installed version 13.2 or above. Else file names of the source files might be wrong.

I had to build gdb from source which is straight forward but out of scope of this document.

Before starting gdb, cd to the symbols subdirectory inside the output-gdb directory. This is where the dll’s, drivers (sys) and the ntoskrnl.exe with debug symbols are placed.

Once gdb is started, you can attach to the VM with:

target remote localhost:2000

where 2000 is the port specified in the qemu command line tag above.

Step 8: Load the symbols

For finding the hexadecimal offsets of the various sections of the ntoskrnl.exe use objdump:

objdump -h ntoskrnl.exe

It should output something like:

ntoskrnl.exe:     file format pei-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         001b1ed0  00401000  00401000  00000000  2**4
                  ALLOC, LOAD, READONLY, CODE
  1 PAGE          000046a8  005b3000  005b3000  00000000  2**2
                  ALLOC, LOAD, READONLY, CODE
  2 .data         00004034  005b8000  005b8000  00000000  2**5
                  ALLOC, LOAD, DATA
  ...

and so on. On i368 systems you need to add 0x80000000 for the ntoskrnl, so the beginning of the .text section would be 0x80401000 in the above example.

Here is how the symbols are loaded on my system (you need to adjust the offsets):

add-symbol-file ../../output-gdb/symbols/ntoskrnl.exe 0x80401000 -s .bss 0x8061c000 -s .data 0x805b8000 -s .edata 80645000

To verify if the .text offset is correct you can disassemble a function:

 (gdb) x/i 'KeAcquireSpinLockAtDpcLevel@4'
    0x8049fc60 <KeAcquireSpinLockAtDpcLevel@4>:	push   %ebp

Note that the single quotes are mandatory if there is an ‘@’ in the function name. The assembly should show

push   %ebp

To load drivers you need to find the offsets where the driver is loaded. You can do so with scanning the kernel log (which can be seen with

virsh console reactos

when the system is booting) or you can use following gdb macro:

define list-modules
  set $m = (struct _LDR_DATA_TABLE_ENTRY*)PsLoadedModuleList->Flink
  while &$m->InLoadOrderLinks != &PsLoadedModuleList
    p $m->BaseDllName.Buffer
    p $m->DllBase
    set $m = (struct _LDR_DATA_TABLE_ENTRY*)$m->InLoadOrderLinks.Flink
  end
end

The output looks somehow like:

$1 = (PWSTR) 0xb7a61eb8 L"ntoskrnl.exe"
$2 = (PVOID) 0x80400000
$3 = (PWSTR) 0xb7a61e50 L"hal.dll"
$4 = (PVOID) 0x80974000
....

and so on. To load you need to add 0x1000 (4KB) to the value printed after the filename (the PVOID value) so if you have:

$65 = (PWSTR) 0xb72c3b10 L"windrbd.sys"
$66 = (PVOID) 0xf5fd8000

the command to load the symbols for the windrbd.sys driver would be:

add-symbol-file windrbd.sys 0xf5fd9000

Now if you backtrace, you should see kernel symbols (if the VM is currently executing in kernel mode) in the backtrace.

To backtrace simply do:

bt

Step 9: You’re done :)

You now can start your debug session. To break on blue screen for example you can enter:

break RtlpBreakWithStatusInstruction@0

You can also use C expressions like the following to enable TCPIP logging:

 p *(int*)(((char*)(&Kd_TCPIP_Mask))+0x80000000) = 0x800

I’ve put a collection of gdb macros (‘real’ gdb macros, not python) in a github repo:

https://github.com/johannesthoma/gdb-macros-for-reactos

As always, happy hacking !!!