Many people debug with printfs because any idiot can do it, whereas even smart people need to spend a few hours learning to use a debugger. However, investing those few hours will save you many more hours of debugging time that could be better spent watching TV. Printfs can be useful, but be aware that printfs do not always work right, because data is not always printed synchronously with the call to printf(). Rather, printf buffers ("saves up") printed characters in memory, and writes the output only when it has accumulated enough to justify the cost of invoking the write system call. If your program crashes while characters are still in the printf buffer, then you may never see those messages print. If you use printf, it is good practice to follow every printf with a call to fflush(stdout) to avoid this problem.
If you want to debug with print statements, the nachos DEBUG() function (declared in threads/utility.h) is your best bet. In fact, the Nachos code is already peppered with calls to the DEBUG function. You can see some of them by doing an fgrep DEBUG *h *cc in the threads subdirectory. These are basically print statements that keep quiet unless you want to hear what they have to say. By default, these statements have no effect at runtime. To see what's going on, you need to invoke nachos with a special command-line argument that activates the DEBUG statements you want to see.
See main.cc for a specification of the flags to the nachos command. The relevant one for DEBUG is -d. The -d flag followed by a space and a series of debug flags cause the DEBUG statements in nachos with those debug flags to be printed when they are executed. For example, the the t debug flag activates the DEBUG statements in the threads directory. The machine subdirectory has some DEBUG statements with the i and m debug flags (see threads/utility.h for a description of the meanings of the current debug flags (there are no current DEBUG statements with the s debug flag). Feel free to add new debug flag values of your own.
For a quick peek at what's going on, run nachos -d ti to activate the DEBUG statements in threads and machine. If you want to know more, add some some more DEBUG statements. For each of the problems assigned below I suggest you sprinkle your code liberally with DEBUG() statements.
When you trace the execution path, it is helpful to keep track of the state of each thread and which procedures are on each thread's execution stack. You will notice that when one thread calls SWITCH, another thread starts running, and the first thing the new thread does is to return from SWITCH. This is because of the way context switches work in Nachos. Because gdb and xxgdb do not understand threads, tracing in xxgdb across a call to SWITCH might be confusing sometimes.
Warning: in our implementation of threads, each thread is assigned a small, fixed-size execution stack. This may cause bizarre problems (such as segmentation faults at strange lines of code) if you declare large data structures to be automatic variables (e.g., int buf[1000];). You will probably not notice this during the semester, but if you do, you may change the size of the stack by modifying the StackSize define in switch.h.