GDB Reference Card

RISC-V Reference Card

Debuggers, Compilers, and Linker

Common issues with running GDB

  1. Running gdb from the wrong directory. You MUST run make qemu-gdb AND gdb-multiarch inside the xv6-labs directory. Not in ~ and not in xv6-labs/kernel or xv6-labs/user.
  2. Running the wrong gdb. The XV6 machine code is RISC-V. You must run gdb-multiarch, which understands RISC-V machine code. The UMW CPSC Server is an x86 machine, and the plain gdb on the server understands x86 machine code.
  3. gdb not being able to find what you are trying to debug. You might need to run target remote localhost:<PORT_NUMBER> from item 5 in the first window steps.
  4. Not using the file command to enter the file you are trying to debug. You may get an error message that says something about using the file command or unknown symbol. The following are sample words - warning: No executable has been specified and target does not support determining executable automatically. Try using the file command. For example, (gdb) file kernel/kernel tells gdb knows to use the kernel code for debugging.
  5. If you quit gdb, and then re-run gdb-multiarch without also re-running make qemu-gdb, weird things might happen. Most of the times it works but sometimes it does not and then you just need to restart both and start over. This might exhibit itself in a behavior of gdb not being responsive to your requests.
  6. Not using gdb to help you debug! I know that using gdb is really annoying in the beginning but it is super super helpful in the later labs and we want you all to know the basic commands to make debugging less painful in the future.

How to run GDB on the CPSC Server

Using gdb to debug XV6 requires two windows, each connected to the CPSC server. The Xv6 window runs XV6. The GDB window runs gdb, which allows you to control the execution of XV6 in the first window. The following steps show Linux commands and gdb commands. Linux commands are preceded by a $, for example $ cd xv6-labs. gdb commands are preceded by a (gdb), for example, (gdb) b syscall.

Xv6 Window

  1. $ ssh <your_userid>@cpsc.umw.edu
  2. $ git clone https://github.com/gustycooper/xv6-labs.git (omit if you already have cloned the lab repo)
  3. $ cd xv6-labs
  4. $ make CPUS=1 qemu-gdb
  5. NOTE: The last line of the output of the previous command should say something like tcp::<PORT_NUMBER>. For example, you might see tcp::26000. The port number is used in the second window.
GDB Window
  1. $ ssh <your_userid>@cpsc.umw.edu
  2. $ cd xv6-labs
  3. $ gdb-multiarch
  4. (gdb) target remote localhost:<PORT_NUMBER>
    The PORT_NUMBER in step 4 is from step 5 in the first window steps above.
Say you wanted to break every time the kernel enters the function syscall from kernel/syscall.c
  1. (gdb) file kernel/kernel (this is a binary that has all kernel code)
  2. (gdb) b syscall
  3. (gdb) c. At this point you will start hitting the breakpoint set on syscall.
  4. Keep entering (gdb) c to see where the kernell hits the syscall function. By entering c many times, you will how the output in the first window is progressing.
Now, say you wanted to break in the ls function in user/ls.c. Then, you would need to enter (gdb) file user/_ls at step 5 since this is the name of the binary where that function is. You would also enter (gdb) b ls in step 6.

Sample GDB Session Debugging an Xv6 Utility

This sample session "debugs" the file ptr.c, which is an example C program discussed during lecture 2.

GDB Window

$ ssh @cpsc.umw.edu
$ cd xv6-labs
$ gdb-multiarch
(gdb) target remote localhost:<PORT_NUMBER> The PORT_NUMBER is from item 5 in the first window steps above.
file user/_ptr
(gdb) file user/_ptr
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from user/_ptr...
b ptr.c:8
(gdb) b ptr.c:9
Breakpoint 1 at 0xa: file user/ptr.c, line 9.
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000000000a in main at user/ptr.c:9
(gdb) c
Continuing.

Breakpoint 1, main (ac=1, av=0x3fe0) at user/ptr.c:9
9	  int l = 5;   // local variables don't have a default value
(gdb) c
Continuing.

Breakpoint 1, main (ac=8224, av=0x64 ) at user/ptr.c:9
9	  int l = 5;   // local variables don't have a default value
(gdb) c
Continuing.

Breakpoint 1, main (ac=1, av=0x3fe0) at user/ptr.c:9
9	  int l = 5;   // local variables don't have a default value

(gdb) layout src   // to show source code
(gdb) n
(gdb) p l
$1 = 5
(gdb) n
(gdb) p q
$2 = (int *) 0x3fac
(gdb) p *q
$3 = 5
(gdb) b 29
Breakpoint 2 at 0x54: file user/ptr.c, line 29.

(gdb) c
Continuing.

Breakpint 2, main (ac=, av=) at user/ptr.c:29

The following shows the GDB window after Breakpoint 2 has been hit.

GDB Window

(gdb) p ptr->a
s4 = 10
(gdb) p s.a           // ptr->a and s.a are equivalent
$5 = 10
(gdb) p s.b
$6 =   // We did not assign s.b a value
(gdb) p ptr->b
$7 =   // ptr->b and s.b are equivalent
(gdb) b 40
Breakpoint 3 at 0x9a: file user/ptr.c, line 40.

(gdb) c
Continuing.

Breakpint 3, main (ac=, av=) at user/ptr.c:40
(gdb) p pp
$8 = (int **)0x3fa0
(gdb) p *pp
$9 = (int *)0x1000
(gdb) p **pp
$10 = 11
(gdb) p *p
$11 = 11
(gdb) c
Continuing.

Breakpint 2, main (ac=8224, av=0x64 ) at user/ptr.c:9
(gdb) c
Continuing.

Xv6 Window

$ make CPUS=1 qemu-gdb
*** Now run 'gdb' in another window.
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 1 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::26003

xv6 kernel is booting

init: starting sh

$ ptr   
p 0x0000000000001000 q 0x0000000000003FAC
g 11 l 13
10 10
pp 0x0000000000003FA0 0x0000000000001000 11
main: 0x0000000000000000

GDB Webpages TOC and Index

We have some helpful webpages on using GDB with our labs located at GDB Webpages TOC, with an index at GDB Webpages Index.

Out helpful webpages have been crafted for our class, and they are an abbreviated version of the MIT GDB Webpages, with an index at MIT GDB Webpages.

GDB is a source-level debugger created by Richard M. Stallman. Many have contributed to the development of GDB: Contributors to GDB

Commonly Used GDB Commands

Telling GDB Where to Find Symbols

To perform symbolic debugging, you must tell gdb where to find symbol definitions. When a program is loaded into memory, the code and variables are loaded into specific addresses. The symbol information is created by the compiler/linker and defines the relationships between symbols (functions and variables) and memory. The gdb file filename command tells gdb where the symbol information is located. The following is a sample.

(gdb) file kernel/kernel

Printing Expressions, which include Variables

The gdb p exp command is used to print the value of expressions. Just like in programming languages, the expression contains operands (i.e., variables, constants) and operators. When a variable is used in an expression, the value of the variable is printed. The following show simple uses of the gdb p command where the expressions are addition of constants, a variable, and multiplication of a variable and a constant.

(gdb) p 5 + 6
$7 = 11
(gdb) p num
$8 = 7
(gdb) p num * 4
$9 = 28

You can also use the p command to print the values of pointers and the struture that that pointer points to. Suppose you had a variable struct proc *p that points to a struct proc. The definition of struct proc is in kernel/proc.h, which is repeated here.

// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};

When a pointer is used in an expression, the value of a pointer is an address. When you use the p command to print the variable p, you see the value of the pointer p, which is just a hex number. What you are really interested in is the structure that p points to.

(gdb) p p
$1 (struct proc *)0x80008eb0 

When a pointer is dereferenced (e.g., *p) in an expression, the value of the dereferenced pointer is the entire structure that it points to. The value of *p is the entire contents of the struct proc. To see the contents of the struct proc that p points to, and the value of the struct proc's member trapframe, you enter the following commands. The /x option of p /x instructs gdb to display the values as hexadecimal numbers.

(gdb) p /x *p
$2 = {lock = {locked = 0x0, name = 0x80008178, cpu = 0x0}, state = 0x4, chan = 0x0, killed = 0x0, xstate = 0x0, pid = 0x1, mask = 0x0,
  parent = 0x0, kstack = 0x3fffffd000, sz = 0x1000, pagetable = 0x87f73000, trapframe = 0x87f74000, context = {ra = 0x800014ba, sp = 0x3fffffde70,
    s0 = 0x3fffffdea0, s1 = 0x80008eb0, s2 = 0x80008a80, s3 = 0x1, s4 = 0x3fffffded0, s5 = 0x8000ed38, s6 = 0x3, s7 = 0x80019b50, s8 = 0x1,
    s9 = 0x80019c78, s10 = 0x4, s11 = 0x0}, ofile = {0x0 }, cwd = 0x80016fc0, name = {0x69, 0x6e, 0x69, 0x74, 0x63, 0x6f, 0x64,
    0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}
(gdb) p /x *(p->trapframe)
$3 = {kernel_satp = 0x8000000000087fff, kernel_sp = 0x3fffffe000, kernel_trap = 0x80001d0c, epc = 0x18, kernel_hartid = 0x1,
  ra = 0x505050505050505, sp = 0x1000, gp = 0x505050505050505, tp = 0x505050505050505, t0 = 0x505050505050505, t1 = 0x505050505050505,
  t2 = 0x505050505050505, s0 = 0x505050505050505, s1 = 0x505050505050505, a0 = 0x24, a1 = 0x2b, a2 = 0x505050505050505, a3 = 0x505050505050505,
  a4 = 0x505050505050505, a5 = 0x505050505050505, a6 = 0x505050505050505, a7 = 0x7, s2 = 0x505050505050505, s3 = 0x505050505050505,
  s4 = 0x505050505050505, s5 = 0x505050505050505, s6 = 0x505050505050505, s7 = 0x505050505050505, s8 = 0x505050505050505, s9 = 0x505050505050505,
  s10 = 0x505050505050505, s11 = 0x505050505050505, t3 = 0x505050505050505, t4 = 0x505050505050505, t5 = 0x505050505050505, t6 = 0x505050505050505}

  1. list elements here.