This page has been machine-translated from the original page.
I have been reading xv6 OS, inspired by First OS Code Reading – Learning Kernel Mechanisms with UNIX V6.
I want to get better at reverse engineering and to deepen my knowledge of kernels and operating systems.
Understanding the Linux Kernel was quite heavy going, so I was looking for somewhere lighter to start. That is when I learned that UNIX V6 has a total code size of around 10,000 lines — just barely comprehensible for a human being — and I became interested.
However, UNIX V6 itself does not run on x86 CPUs, so I decided to read the source code of kash1064/xv6-public: xv6 OS, which is a fork of xv6 OS, an x86-architecture port of UNIX V6.
In the previous article, we read through the xv6 OS build and boot process.
I was eager to start reading the kernel proper, but there were parts I could not understand just by reading the code. So I decided to set up a debugging environment first to help deepen my understanding.
Table of Contents
Debugging xv6 OS with QEMU-GDB
The following article was helpful as a reference for the basic procedure.
Reference: Creating a debug environment for xv6 - Qiita
In my environment I wanted the QEMU console to appear in a separate GUI window, so unlike the article above I am using qemu-gdb rather than qemu-nox-gdb.
Connecting the debugger is very straightforward — simply run the following command:
# Run in the same directory as the Makefile
make qemu-gdbNext, open another terminal and enter the following commands to enable debugging:
# Specify the kernel binary as the debug target in gdb
gdb kernel
# Remote debug via gdb
target remote localhost:26000First, make qemu-gdb builds xv6 OS and then invokes qemu-system-i386 -serial mon:stdio -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp 2 -m 512 -S -gdb tcp::26000.
The QEMU option arguments are examined one by one below.
QEMU Option Arguments During Debugging
The option arguments used when running make qemu-gdb are as follows:
| Option Argument | Purpose |
|---|---|
| -serial <dev> | Redirect the virtual serial device to the host. With mon:stdio, both the console and the QEMU monitor are displayed in the terminal. |
| -drive <options> | Add a new block device or interface. Here, xv6.img and fs.img are each loaded as a disk. |
| -smp <cpus> | Emulate SMP (multi-processor system) using the specified number of CPUs. |
| -m <MB or GB> | Specify the memory size at virtual machine startup (default unit: MB). A prefix such as 1G can be used to specify gigabytes. |
| -S | Do not use the CPU at startup (i.e., halt immediately after power-on and wait for a GDB connection). |
| -gdb <tcp::port> | Accept GDB connections on the specified protocol and port. |
Reference: Invocation — QEMU documentation
Reference: QEMU Options Cheat Sheet for Kernel Debugging - Qiita
Trying the Debugger
Symbol information for the xv6 kernel is stored in kernel.sym at build time.
Looking up the address of a symbol on the GDB side gives the same result:
$ info address main
Symbol "main" is a function at address 0x80103040.Set a breakpoint at the main function:
$ main
$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x80103040 --> 0xfb1e0ff3
EBX: 0x10094 --> 0x0 --> 0xf000ff53
ECX: 0x0 --> 0xf000ff53
EDX: 0x1f0 --> 0xf000ff53
ESI: 0x10094 --> 0x0 --> 0xf000ff53
EDI: 0x0 --> 0xf000ff53
EBP: 0x7bf8 --> 0x0 --> 0xf000ff53
ESP: 0x8010b5c0 --> 0x0 --> 0xf000ff53
EIP: 0x80103040 --> 0xfb1e0ff3
EFLAGS: 0x86 (carry PARITY adjust zero SIGN trap interrupt direction overflow)
[-------------------------------------code-------------------------------------]
0x80103034 <mpenter+20>: call 0x801027a0 <lapicinit>
0x80103039 <mpenter+25>: call 0x80102fe0 <mpmain>
0x8010303e: xchg ax,ax
=> 0x80103040 <main>: endbr32
0x80103044 <main+4>: lea ecx,[esp+0x4]
0x80103048 <main+8>: and esp,0xfffffff0
0x8010304b <main+11>: push DWORD PTR [ecx-0x4]
0x8010304e <main+14>: push ebp
[------------------------------------stack-------------------------------------]
0000| 0x8010b5c0 --> 0x0 --> 0xf000ff53
0004| 0x8010b5c4 --> 0x0 --> 0xf000ff53
0008| 0x8010b5c8 --> 0x0 --> 0xf000ff53
0012| 0x8010b5cc --> 0x0 --> 0xf000ff53
0016| 0x8010b5d0 --> 0x0 --> 0xf000ff53
0020| 0x8010b5d4 --> 0x0 --> 0xf000ff53
0024| 0x8010b5d8 --> 0x0 --> 0xf000ff53
0028| 0x8010b5dc --> 0x0 --> 0xf000ff53
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Thread 1 hit Breakpoint 1, main () at main.c:19In my environment I have gdb-peda enabled, so quite a bit of information is displayed.
This completes the setup for debugging the kernel.
Summary
I originally planned to use bochs, but the troubleshooting did not go well, so I decided to use GDB for debugging instead.
This approach turned out to be better in the end since it is easier to configure and I am more comfortable with it.
This time I will really, truly start reading the kernel proper.