Help with gdb

What is gdb?

gdb is a debugger that allows you to stop a program while it is running, look at variables, step through the program line by line, and examine stack and memory. It is a very powerful tool to find what is wrong with programs or to understand what they are doing.

gdb allows you to debug both at the programming language level and at the machine language level. So, you can stop at a line in C, look at the program state (e.g., variables) and step through it C line by C line. Alternatively, you can stop at a machine language instruction, look at the machine state (registers, memory stack), step through it at the machine language level, etc...

How do I use it for this assignment?

You will run qemu (running xv6/jos) inside gdb. You can trace execution at the C level, or for the relevant components such as BIOS in assembly. You can also make gdb stop by setting breaks, make it execute instruction by instruction and examine the registers, memory and stack to get an idea about what is going on.

OK, but what are the relevant commands?

Download and print the gdb quick reference guide. It lists the most important gdb commands and their options. Below is a description of the most useful ones.

Running

To start the program running in gdb, go to the xv6 directory from two windows. In the first, start QEMU/xv6 in gdb mode (make qemu-nox-gdb). In the other, which you should do second, run gdb to attack to QEMU by giving it gdb -q -iex "set auto-load safe-path /home/csprofs/nael/xv6-master/" , replacing the last argument with your own path.

gdb will break when the machine is ready to jump to the BIOS (please see instructions in lab 1). You can continue execution at this point by typing (continue), but that is not too interesting since the program will simply run.

Stopping

If you want the program to stop at a certain point, you do that using breakpoints. Before running the program, set breakpoints using the break command. Here is an example:

break syscall

The above command causes the xv6 to stop at the first line in the function syscall (which is the main handler for system calls called from the trap function, which handles all interrupts).

Assembly debugging: You can also set breakpoints at specific assembly language instructions by setting the breakpoint at the address of the instruction. You should not need to do that for this assignment.

Look at code

You can look at C code by typing list, list function_name or list linenumber. This gives you a few lines of code starting with the current location (for list) or at the function or linenumber you specify. If you type return, the next lines of code will be visible.

Assembly debugging: To look at the code in assembly, a helpful command is "display/i $pc". This command shows you at every step the next machine language instruction you will execute (the one pointed to by the program counter). You only need to type it once -- display prints the value after every command. You can also look at a complete function by typing disassemble foo where foo is the function name.

Look at the program state

To see what is happening in the program, you need to examine its state. The state at the C level includes the values in the variables, the call stack, etc... At the assembly level, the program state includes registers and memory. Of course, the stack is also part of memory. Understanding how procedures work (check these lecture slides for a refresher) will give you a huge advantage here.

In C

To see what is happening in the program, you need to examine its state. You can examine any variable in the current scope (in the current function you are in or a global variable) by using

print variable_name

You can also provide print with options on how to print the variable, similar to the options that you have with printf in C (e.g., to display it as hex, or as a floating point number, or to display the string pointed to by a pointer). Look at the section titled "Display" in GQR.

You can also track a variable as you step through the program using the command

display variable_name

display has similar options to print; the difference is that it prints the variable value every time as you move through your program execution. Look at the section titled "Automatic Display" in GQR.

In assembly

To print the value of a register type (e.g., eax) use "print $eax".

You can also see all the registers by typing "info registers"

You can also use display $eax so that $eax is printed after every step

You may also be interested in what is on memory. The command to print the value of a memory location is "x".

to figure out the value of an argument at 8($ebp), for example, you first:

"print $ebp" this will give you the value of ebp, say 0xffffff00

add 8 to it, so 0xffffff08, to get the address of the argument; we added 8 since it is at 8($ebp)

"x/1x 0xffffff08" to print what is at that memory location assuming it is a hexadecimal word. The 1 in the command above specifies how many memory locations to print. The "x" after the 1 refers to the format you should print it as. If you think a string is stored at that location, you can print it using "x/1s 0xffffff08".

There are a number of other commands that you can use for printing state, and a number of options to the commands we discussed so far that you will find on the gdb quick reference sheet. However, you should have enough to get going now.

Stepping through your program

In C

step lets your program execute the next line in the program and stop after it. If the next line is a function, it will step inside the function and stop at the first line in there.

next is like step, but does not go into a function (it will execute the function and stop after that staying in the same function.

You can also use continue to go back to running the program which will cause it to run to the end or stop at the next breakpoint.

In assembly

stepi (or si for short) lets your program execute the next machine language instruction then stop. You can then look at what changes happened to your state.

nexti (or ni for short) will jump over a function call, while si, will go into the function and stop at the first instruction in there.

Examining the stack

In C

When you stop inside a point in the code, you may be inside a function called from main or from another function. Often you will find that one of the arguments, lets say, has a value different from what you expect and you want to go up to the calling function and see what is going on there. You can do this in gdb. Here are some of the useful commands.

where shows you the function stack (which function you are in, and what is the sequence of calls that got you there)

You can then move up to the calling function by typing up, once you move up to that function, you can use list to look at the code, or print to look at variable values in that context. Using up again moves you up the call stack to the calling function. You can use down to move down the calling stack again.

Look at the section titled "Program Stack" in GQR to get more information on options.

In assembly

You have to manually look at the memory locations starting from $esp or $ebp (please see above).

More advanced commands

gdb is a powerful tool, with many other commands and other options for the commands we discussed. Take a look at the gdb quick reference guide. Also, you can use the help command to get help on any command and its options. For example, you can set breakpoints at certain lines in certain files. You can set conditional breakpoints (only when a variable has a certain value), etc..

Getting started with Lab 1

  • Take advantage of the resources:
    • The xv6 book chapters 1, 3 (for the system calls) and 5 (for scheduling). The book refers to the code, so you read and lok at the code at the same time.
    • The gdb quick start guide to be able to trace through the code and understand what is happening
  • Browse the code
    • Learn the command egrep which you can use to search in files. For example, egrep syscall *.c would give you every line that includes syscall in all files ending with .c
    • Look at proc.h and the struct proc to understand the process control block. In proc.c you will find all the system calls that have to do with processes such as fork, exit and wait. Take a look at these to understand what they do.
  • Use gdb to trace the code
    • Look at the trap function which is where the OS starts execution whenever an event happens (syscall, fault, interrupt, etc..). Notice that if the event is a system call, we call syscall()
    • Set a breakpoint at syscall in gdb once you start qemu. Type continue so that the program continues execution. When it stops the next time, it will be at syscall after a system call occurred. You can step through the code, examine variables and understand how the system call got executed. Start thinking about what you need to add to add an additional system call.