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, change your working directory to proj-pregame/src/userprog/ and run
FORCE_SIMULATOR=--bochs PINTOS_DEBUG=1 pintos-test do-nothing
Side note: You can alias it as pintos-debug by adding it to bashrc.
echo "alias pintos-debug='FORCE_SIMULATOR=--bochs PINTOS_DEBUG=1 pintos-test'" >> ~/.bashrc
GDB should now be open. At a high level, the following must happen before Pintos can start the do-nothing process:
The BIOS reads the Pintos bootloader (
proj-pregame/src/threads/loader.S) from the first sector of the disk into memory at address0x7c00.The bootloader reads the kernel code from disk into memory at address
0x20000and then jumps to the kernel entrypoint (proj-pregame/src/threads/start.s).The code at the kernel entrypoint switches to 32-bit protected mode 1 and then calls main (
proj-pregame/src/threads/init.c).The main function boots Pintos by initalizing 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 proj-pregame/src/userprog/process.c.
Now, answer the following questions:
Step into the
process_executefunction. What is the name and address of the thread running this function? What other threads are present in Pintos at this time? Copy theirstruct threads. (Hint: for the last part,dumplist &all_list thread allelemmay be useful.)What is the backtrace for the current thread? Copy the backtrace from GDB as your answer.
Set a breakpoint at
start_processand continue to that point. What is the name and address of the thread running this function? What other threads are present in Pintos at this time? Copy theirstruct threads.Step through the
start_processfunction until you have stepped over the call to load. Note that load sets theeipandespfields in theif_structure. Print out the value of theif_structure, displaying the values in hex (hint:print/x if_).The first instruction in the
asm volatilestatement 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 volatilestatement, and then step through the instructions. As you step through theiretinstruction, 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 timeiretis executed, and the functionality of theiretinstruction.Once you’ve executed
iret, typeinfo registersto print out the contents of registers. Include the output of this command on Gradescope. How do these values compare to those when you printed outif_?Notice that if you try to get your current location with
backtraceyou’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, namelydo-nothing. To do this, useloadusersymbols tests/userprog/do-nothing. Now, usingbacktrace, you’ll see that you’re currently in the_startfunction. Using thedisassembleandstepicommands, 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, sobacktracewill show the current stack in kernel mode, not the user stack at the time of the page fault. However, you can usebtpagefaultto find the user stack at the time of the page fault. Copy down the output ofbtpagefault.