When the kernel panics, it prints a “backtrace”, that is, a summary of how your program got where it is, as a list of addresses inside the functions that were running at the time of the panic. You can also insert a call to
debug_backtrace, prototyped in
<debug.h>, to print a backtrace at any point in your code.
debug_backtrace_all, also declared in
<debug.h>, prints backtraces of all threads.
The addresses in a backtrace are listed as raw hexadecimal numbers, which are difficult to interpret. We provide a tool called
backtrace to translate these into function names and source file line numbers. Give it the name of your
kernel.o as the first argument and the hexadecimal numbers composing the backtrace (including the
0x prefixes) as the remaining arguments. It outputs the function name and source file line numbers that correspond to each address.
If the translated form of a backtrace is garbled, or doesn’t make sense (e.g. function A is listed above function B, but B doesn’t call A), then it’s a good sign that you’re corrupting a kernel thread’s stack, because the backtrace is extracted from the stack. Alternatively, it could be that the
kernel.o you passed to
backtrace is not the same kernel that produced the backtrace.
Sometimes backtraces can be confusing without any corruption. Compiler optimizations can cause surprising behavior. When a function has called another function as its final action (a tail call), the calling function may not appear in a backtrace at all. Similarly, when function A calls another function B that never returns, the compiler may optimize such that an unrelated function C appears in the backtrace instead of A. Function C is simply the function that happens to be in memory just after A.
Here’s an example. Suppose that Pintos printed out this following call stack:
Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8.
You would then invoke the
backtrace utility like shown below, cutting and pasting the backtrace information into the command line. This assumes that
kernel.o is in the current directory. You would of course enter all of the following on a single shell command line, even though that would overflow our margins here:
backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8
The backtrace output would then look something like this:
0xc0106eff: debug_panic (lib/debug.c:86) 0xc01102fb: file_seek (filesys/file.c:405) 0xc010dc22: seek (userprog/syscall.c:744) 0xc010cf67: syscall_handler (userprog/syscall.c:444) 0xc0102319: intr_handler (threads/interrupt.c:334) 0xc010325a: intr_entry (threads/intr-stubs.s:38) 0x0804812c: (unknown) 0x08048a96: (unknown) 0x08048ac8: (unknown)
The first line in the backtrace refers to
debug_panic, the function that implements kernel panics. Because backtraces commonly result from kernel panics,
debug_panic will often be the first function shown in a backtrace.
The second line shows
file_seek as the function that panicked, in this case as the result of an assertion failure. In the source code tree used for this example, line 405 of
filesys/file.c is the assertion
assert (file_ofs >= 0);
(This line was also cited in the assertion failure message.) Thus,
file_seek panicked because it passed a negative file offset argument.
The third line indicates that
file_seek, presumably without validating the offset argument. In this submission,
seek implements the
seek system call.
The fourth line shows that
syscall_handler, the system call handler, invoked
The fifth and sixth lines are the interrupt handler entry path.
The remaining lines are for addresses below
phys_base. This means that they refer to addresses in the user program, not in the kernel. If you know what user program was running when the kernel panicked, you can re-run
backtrace on the user program, like so: (typing the command on a single line, of course):
backtrace tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8
The results look like this:
0xc0106eff: (unknown) 0xc01102fb: (unknown) 0xc010dc22: (unknown) 0xc010cf67: (unknown) 0xc0102319: (unknown) 0xc010325a: (unknown) 0x0804812c: test_main (...xtended/grow-too-big.c:20) 0x08048a96: main (tests/main.c:10) 0x08048ac8: _start (lib/user/entry.c:9)
You can even specify both the kernel and the user program names on the command line, like so:
backtrace kernel.o tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8
The result is a combined backtrace:
0xc0106eff: debug_panic (lib/debug.c:86)| 0xc01102fb: file_seek (filesys/file.c:405)| 0xc010dc22: seek (userprog/syscall.c:744)| 0xc010cf67: syscall_handler (userprog/syscall.c:444)| 0xc0102319: intr_handler (threads/interrupt.c:334)| 0xc010325a: intr_entry (threads/intr-stubs.s:38)|
0x0804812c: test_main (...xtended/grow-too-big.c:20)| 0x08048a96: main (tests/main.c:10)| 0x08048ac8: _start (lib/user/entry.c:9)|
Here’s an extra tip:
backtrace is smart enough to strip the
Call stack: header and
. trailer from the command line if you include them. This can save you a little bit of trouble in cutting and pasting. Thus, the following command prints the same output as the first one we used:
backtrace kernel.o Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8.