Step through the Crash
Now that we understand why the do-nothing
program crashes, we will use GDB to step through the execution of the do-nothing
test in PintOS, starting from when the kernel boots. Our goal is to find out how we can modify the PintOS user program loader so that do-nothing
does not crash, while becoming acquainted with how PintOS supports user programs. To do this, select the Debug do-nothing
from the top of the VSCode debugging pane.
As a side note, we have added two files .vscode/launch.json
and .vscode/tasks.json
to the starter repository that contain configurations enabling to debug the PintOS kernel from inside VSCode. Here is an excerpt from the file .vscode/tasks.json
:
{
"version": "2.0.0",
"tasks": [
{
"label": "[P1] compile",
"type": "shell",
"command": "make",
"options": {
"cwd": "${workspaceFolder}/src/userprog"
}
},
{
"label": "[P1] Run Test in GDB mode",
"type": "shell",
"isBackground": true,
"problemMatcher": [],
"dependsOn": [
"[P1] compile"
],
"command": "pintos -v -k -T 60 --bochs --gdb -- -q run do-nothing",
"options": {
"cwd": "${workspaceFolder}/src/userprog/build"
}
}
]
}
Please familiarize yourself with those files so you can modify them as needed in the future. For more information about this file format, please see the corresponding documentation.
GDB should now be open and your program should stop executing at the entry point to PintOS. At a high level, the following must happen before PintOS can start the do-nothing
process:
- The BIOS reads the PintOS boot-loader (
src/threads/loader.S
) from the first sector of the disk into memory at address0x7c00
. - The boot-loader reads the kernel code from disk into memory at address
0x20000
and then jumps to the kernel entrypoint (src/threads/start.S
). - The code at the kernel entrypoint switches to 32-bit protected mode 1 and then calls main (
src/threads/init.c
). - The main function boots PintOS by initializing the scheduler, memory subsystem, interrupt vector, hardware devices, and file system.
You’re welcome to read the code to learn more about this setup, but you don’t need to understand how this works for the PintOS projects or for this class.
Set a breakpoint at run_task
and continue in GDB to skip the setup. As you can see in the code for run_task
, PintOS executes the do-nothing
program (specified on the PintOS command line), by invoking
process_wait(process_execute("do-nothing"));
from run_task
. Both process_wait
and process_execute
are in src/userprog/process.c
.
If you use the gdb commands listed below from the debug window in VSCode you need to prefix them with
-exec
, i.e., instead of typinginfo registers
in that window you need to use-exec info registers
to pass on the command to gdb.
Now, answer the following questions in results/answers.md
:
Q1: Step into the
process_execute
function. What is the name and address of the PintOS thread running this function? What other threads are present in PintOS at this time? Copy the content of theirstruct thread
data structures toanswers.md
. (Hint: for the last part,dumplist &all_list thread allelem
may be useful. The commandp running_thread()
may also be of help)Q2: What is the backtrace for the current PintOS thread? Copy the backtrace from GDB as your answer and also copy down the line of C code corresponding to each function call.
Q3: Set a breakpoint at
start_process
and continue to that point. What is the name and address of the PintOS thread running this function? What other threads are present in PintOS at this time? Copy the content of theirstruct thread
data structures toanswers.md
.Q4: Where is the thread running
start_process
created? Copy down this line of code.Q5: Step through the
start_process
function until you have stepped over the call toload
. Note thatload
sets theeip
andesp
fields in theif_
structure. Print out the value of all members of theif_
structure, displaying the values in hex (hint:print/x if_
). Alternatively you can select the Variables/Locals pane in the Debug pane to see the values. Add the content ofif_
to youranswers.md
Q6: The first instruction in the
asm volatile
statement sets the stack pointer to the bottom of theif_
structure. The second one jumps tointr_exit
. The comments in the code explain what’s happening here. Step into theasm volatile
statement, and then step through the instructions. As you step through theiret
instruction, observe that the function “returns” into userspace. Why does the processor switch modes when executing this function? Feel free to explain this in terms of the values in memory and/or registers at the timeiret
is executed, and the functionality of theiret
instruction.Q7: Once you’ve executed
iret
, typeinfo registers
to print out the contents of registers. Include the output of this command inanswers.md
. How do these values compare to those when you printed outif_
?Q8: Notice that if you try to get your current location with backtrace you’ll only get a hex address. This is because because the debugger only loads in the symbols from the kernel. Now that we are in userspace, we have to load in the symbols from the PintOS executable we are running, namely
do-nothing
. To do this, useloadusersymbols tests/userprog/do-nothing
. Now, usingbacktrace
, you’ll see that you’re currently in the_start
function. Using thedisassemble
andstepi
commands (or step-over in the disassembler window), step through userspace instruction by instruction until the page fault occurs. At this point, the processor has immediately entered kernel mode to handle the page fault, so backtrace will show the current stack in kernel mode, not the user stack at the time of the page fault. However, you can usebtpagefault
to find the user stack at the time of the page fault. Copy the output ofbtpagefault
and add it to youranswers.md
.
The next step for this assignment is described here.