Debugging
Many tools are at your disposal for debugging Pintos. This section introduces a few of them to you.
printf()
Don't underestimate the value of printf()
.
The way
printf()
is implemented in Pintos, you can call it from practically anywhere in the kernel, whether it's in a kernel thread or an interrupt handler, almost regardless of what locks are held.
printf()
is useful for more than just examining data. It can also help figure out when and where something goes wrong, even when the kernel crashes or panics without a useful error message.
The strategy is to sprinkle calls to
printf()
with different strings (e.g."<1>"
,"<2>"
, ...) throughout the pieces of code you suspect are failing. If you don't even see<1>
printed, then something bad happened before that point, if you see<1>
but not<2>
, then something bad happened between those two points, and so on.Based on what you learn, you can then insert more
printf()
calls in the new, smaller region of code you suspect. Eventually, you can narrow the problem down to a single statement. See the following expansion Triple Faults, for a related technique.
ASSERT
Assertions are useful because they can catch problems early, before they'd otherwise be noticed.
Ideally, each function should begin with a set of assertions that check its arguments for validity. (Initializers for functions' local variables are evaluated before assertions are checked, so be careful not to assume that an argument is valid in an initializer.)
You can also sprinkle assertions throughout the body of functions in places where you suspect things are likely to go wrong. They are especially useful for checking loop invariants.
Pintos provides the ASSERT
macro, defined in <debug.h>
, for checking assertions.
Macro: ASSERT (expression)
Tests the value of expression. If it evaluates to zero (false), the kernel panics. The panic message includes the expression that failed, its file and line number, and a backtrace, which should help you to find the problem. See the following expansion Backtraces, for more information.
Function and Parameter Attributes
These macros defined in <debug.h>
tell the compiler special attributes of a function or function parameter. Their expansions are GCC-specific.
Macro: UNUSED
Appended to a function parameter to tell the compiler that the parameter might not be used within the function. It suppresses the warning that would otherwise appear.
Macro: NO_RETURN
Appended to a function prototype to tell the compiler that the function never returns. It allows the compiler to fine-tune its warnings and its code generation.
Macro: NO_INLINE
Appended to a function prototype to tell the compiler to never emit the function in-line. Occasionally useful to improve the quality of backtraces (see below).
Macro: PRINTF_FORMAT** _(format, first)**_
Appended to a function prototype to tell the compiler that the function takes a
printf()
-like format string as the argument numbered format (starting from 1) and that the corresponding value arguments start at the argument numbered first. This lets the compiler tell you if you pass the wrong argument types.
GDB
You can run Pintos under the supervision of the GDB debugger.
First, start Pintos with the
--gdb
option, e.g.pintos --gdb -- run mytest
.
You should find your Pintos program blocked, which waits for the GDB to attach to this gdb session.
Second, open a second terminal on the same machine (or in the same container, see below) and use
pintos-gdb
to invoke GDB onkernel.o
:
If you develop Pintos in a virtual machine, it is easy to do the second step above by opening another terminal.
If you use docker, you need a way to attach your Pintos container to another terminal in your host computer.
Now open a new terminal and run
You may remember that when you launch the Pintos docker container , you name it as "pintos" by specifying "--name pintos". So here you just exec a bash shell in your pintos container.
Third, use
pintos-gdb
to invoke GDB onkernel.o
in the second terminal:
and issue the following GDB command:
A GDB macro debugpintos
is provided as a shorthand for target remote localhost:1234
, so you can type this shorter command (debugpintos
) instead.
Now GDB is connected to the simulator over a local network connection. You can now issue any normal GDB commands.
If you issue the "c" (continue) command, the simulated Qemu will take control, load Pintos, and then Pintos will run in the usual way. You can pause the process at any point with
Ctrl+C
.
Using GDB to Debug a Single Test
If you issue the make
command to run a single test (see Run an individual test), you will see the command our test suite uses to generate .result
output (see the example below). With this command added --gdb
option before --
, you can run this single test in gdb mode and then debug with pintos-gdb
as above.
Tips
The page allocator in threads/palloc.c
and the block allocator in threads/malloc.c
clear all the bytes in memory to 0xcc at time of free.
Thus, if you see an attempt to dereference a pointer like 0xcccccccc, or some other reference to 0xcc, there's a good chance you're trying to reuse a page that's already been freed.
Also, byte 0xcc is the CPU opcode for "invoke interrupt 3," so if you see an error like
Interrupt 0x03 (#BP Breakpoint Exception)
, then Pintos tried to execute code in a freed page or block.
An assertion failure on the expression sec_no < d->capacity
indicates that:
Pintos tried to access a file through an inode that has been closed and freed. Freeing an inode clears its starting sector number to 0xcccccccc, which is not a valid sector number for disks smaller than about 1.6 TB.
Last updated