Debugging with Bochs and gdb

  1. Enable debugging symbols
  2. Enable debugging with gdb in bochs
  3. Connect a gdb session to bochs
  4. Using gdb
  5. Custom gdb commands

Enable debugging symbols

Edit Makefile and include -g in your compiler options by adding the line, CFLAGS+= -g. Optionally, you can disable optimizations by removing the line, CFLAGS+= -O2.
Optimizations may get in the way of debugging.

Finally, rebuild using make clean; make. Cleaning the project is a necessary first step because changing compilation flags will not cause make to rebuild anything.

Enable debugging with gdb in bochs

Add the line,
gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0
to bochsrc to enable gdb debugging. In this case, the port is 1234, but you can choose any open port numbered above 1024 as long as you tell gdb to connect to the same port.

By adding this line, you may only use the bochsrc file with a version of bochs configured with the gdb-stub enabled. Running without this configuration will cause bochs to panic and die; comment it out by prepending a # at the beginning of the line.

Connect a gdb session to bochs

Run bochs with gdb-stub enabled. In lab 010, run /u/318/bin/bochs-gdb (optionally with the -q option to skip the intro menu). Bochs will load and await a connection from gdb. If gdb, when debugging with bochs, interprets addresses and registers incorrectly (breakpoints stop working, too), the problem may lie with your configuration.

Run gdb kernel to debug kernel (the ELF file generated by compiling your kernel).

Rather than running the kernel on the local machine (this will not work), you need to debug the instance running inside of bochs. Connect gdb to that session by invoking target remote localhost:1234, where localhost should be replaced with the name or IP address of the computer running bochs if it's not running locally, and where 1234 is whichever port you've chosen.

Upon a successful connection, bochs will break at the first instruction in the BIOS (not the bootloader nor the kernel). Notice that you won't be able to inspect kernel data at this point because your kernel has not yet been loaded by the bootloader.

Using gdb

Refer to
http://users.ece.utexas.edu/~adnan/gdb-refcard.pdf, a search engine, and gdb's help command for more information.

If you want to debug a user program (not part of the kernel), such as process1, load its symbol file using add-symbol-file process1 0x4000. In this case, process1 is loaded at address 0x4000 (specified in your Makefile). Then try setting a breakpoint, for example: b process1.c:draw.

To streamline the process, create a .gdbinit file in the project directory that contains something along the lines of:

file kernel add-symbol-file process1 0x4000 target remote :1234 set disassembly-flavor att b kernel.c:25

There are many front-ends for gdb which often provide graphical interfaces to aid the display of data and source code, such as gdbtui, ddd, xcode, and kdbg. (The first and last are already installed in lab 010). Some, such as kdbg, won't read .gdbinit and may have other limitations.

Custom gdb commands

Add the following to .gdbinit:
define ltsk set $count = sizeof(task)/sizeof(task[0]) set $idx = 0 printf "Entry Type\n" while($idx < $count) set $t = task[$idx] set $idx++ if($t.task_type == KERNEL_THREAD) printf "%p \t KERNEL_THREAD\n", $t.entry_point else printf "%p \t PROCESS\n", $t.entry_point end end end
to create create the ltsk command which lists the processes that your OS will run, printing the entry point and thread type. Custom commands are particularly useful for debugging operating systems because many data structures are used and navigating through them by hand is tedious. For example, you can create a command to list objects of a particular type (list all blocked programs) or perform an analysis (how many resources are consumed?). Commands also accept arguments.