This document describes how to use SmartGDB (in particular, to trace the execution of Nachos as in Home Work Assignment #1. This document assumes that you have downloaded the nachos archive and have it installed in your home directory.
SmartGDB is a breakpoint debugger. It lets you insert breakpoints into your program, which stop its execution. To start using SmartGDB on the nachos program:
This will start the SmartGDB debugger on the nachos program. You should see the following output:
GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.16.1 (SmartGDB 0.9) (i686-unknown-linux), Copyright 1996 Free Software Foundation, Inc... (gdb)
If you instead get a segmentation fault, you need to create a .Xdefaults file in your home directory. This is due to a bug in ITCL (which SmartGDB uses). You can create this failt with the following command:
Then, try to start smartgdb again.
SmartGDB creates an environment in which your program (nachos) will run. To create the proper environment you need to specify command line flags to nachos. These are done by setting the smartgdb variable args.
This tells SmartGDB to run the program as if you had typed ../userprog/nachos -x fork -S ~/swapfile on the command line. In this case, we're instructing nachos to execute the user program fork and use a file swapfile in our home directory for the swap file.
Note: if you do not specify a swapfile, nachos will create one in the current directory. In this case, ~/nachos/userprog.
Note: you must specify the path to the fork program. In this example, since we were in the nachos/test directory to begin with, we didn't have to since SmartGDB defaults to the current directory. If we had been somewhere else when we invoked SmartGDB, we would have to give a relative path to the file we wanted.
SmartGDB is a breakpoint debugger. To stop your program at an appropriate point, you need to insert a breakpoint. For example, if you wanted to trace the execution of the System_Fork call, it may be a good idea to set a breakpoint on the System_Fork function. To do this we would type:
This should produce the output:
Breakpoint 1 at 0x8051ec6: file ../userprog/systemcall.cc, line 424.
When the program is run and reaches the function System_Fork, it will stop and return control to you.
Nachos is a multi-threaded program. As such, SmartGDB needs to take special measures to deal with it. When you are ready to run your program, type:
Note: If you do not do this, you will not be able to debug anything. SmartGDB will not understand nachos unless you type this.
Now that we have taken care to set the command line arguments, configure the thread support, and set a breakpoint, we can start our program:
We should see the output:
Starting program: nachos/test/../userprog/nachos -x fork -S ~/swapfile
Our program should run for a while and then hit a breakpoint. When SmartGDB hits a breakpoint it will tell you which breakpoint it hit, and return to control to you:
Breakpoint 1, System_Fork () at ../userprog/systemcall.cc:424 424 char * filename = new char[MAXFILENAMELENGTH]; (gdb)
Salient points here are:
Note: If you do not set a breakpoint, the program will run as if everything is normal. You will not get control of the debugger back.
Now that we have control of the debugger back, we can print the value of variables, check the call-trace etc.
Once you have examined the state of the program at a breakpoint, SmartGDB allows you to continue execution until the next breakpoint in one of three ways.
For example, to step the program from the breakpoint in System_Fork we would say:
This will step the program one text line. The output should look like:
426 if (!filename) {
This lets us know that the program is now sitting on line 426 of the same file, about to execute the conditional statement.
When the debugger has control over the executing program (i.e. you are sitting at a gdb prompt) you can print the value of variables in the current scope.
To find out what the local variables are, you can use the i command (short for info).
Assuming we are stopped in the System_Fork function, we should see:
pid = 134549522 filename = 0xbffff2e8 "üòÿ¿:Ü\004\b\t" t = (Thread *) 0x807df08 space = (AddrSpace *) 0x1f0 dummy = 134727474 oldLevel = 97
That is, there are six local variables (pid, filename, t, spcae, dummy, and oldLevel). Their values are printed on the right.
Note: The i locals command requires that a frame be selected. To select a frame, use the fr command discussed below. By default, the current frame is selected. Many commands change this default behavior, so be aware of it.
To print the value of local or global variables, you can also use the p command (short for print). For example, if we wanted to see what the value of the global variable machine (pointer to the global nachos machine) is, we could type:
You should see the output:
$3 = (Machine *) 0x807df08
This output tells us that the variable machine is of type Machine * (a pointer to a Machine object), and it's value is 0x8077918. The p command can be used to print the value of any expression (including, for instance, p *machine, which would show you the values stored in the machine object).
One other useful command is ptype which will tell you about a type. For example, if we don't know what the object type Scheduler is, we can issue the command:
This should print the type of the Scheduler object as:
type = class Scheduler { private: List *readyList; int threadid; public: ~Scheduler (); Scheduler(void); Scheduler(Scheduler const &); Scheduler & operator=(Scheduler const &); void ReadyToRun(Thread *); Thread * FindNextToRun(void); void Run(Thread *); void Print(void); int addToList(Thread *); void removeFromList(Thread *); Thread * GetThis(Thread *); void Switch_To(Thread *); }
Which tells us that the Scheduler is a class and lists the member functions and data elements. The ptype command can also be used on variables to find out their type For example, ptype scheduler would produce the same output since the global variable scheduler is an object of the class Scheduler (note the case of the first letters--this is Nachos' naming convention for objects and classes, respectively, names of classes are capitalized while names of objects are not).
Nachos is a multi-threaded program. For each user-process there is a corresponding Nachos thread. SmartGDB allows you to get information on these threads. This is a feature that makes SmartGDB different from normal GDB.
To get information on the number of threads an application currently has, we can use the command i threads:
This should produce the result:
(gdb) i threads * 1 Thread Ptr: 0x807dcb8 System_Fork () at ../userprog/systemcall.cc:424
This tells us that the program currently has only one thread (since we haven't actually performed the fork, yet). See what happens if you execute four more instructions (using the n command four times, so that you are ready to execute line 432) and then use i threads again.
Note: SmartGDB identifies threads by their Thread_id number.
When a program is stopped at a breakpoint, gdb can be used to show the series of calls the program has made to get to that location. This output, called a back-trace is obtained with the bt command by applying it to a thread with the thread apply command. The thread apply command takes at least two arguments: the Thread_id of the thread you want to which you want to apply a command, and the command. For example, to print back-trace of thread 1, enter the command:
Thread 1 (Thread Ptr: 0x0): #0 System_Fork () at ../userprog/systemcall.cc:432 #1 0x80511b5 in do_system_call (syscall_num=9) at ../userprog/systemcall.cc:63 #2 0x804dc3a in ExceptionHandler () at ../userprog/exception.cc:58 #3 0x804e660 in Machine::RaiseException (this=0x807df40, which=SyscallException, badVAddr=0) at ../machine/machine.cc:109 #4 0x80502aa in Machine::OneInstruction (this=0x807df40, instr=0x808eb18) at ../machine/mipssim.cc:547 #5 0x804ec19 in Machine::Run (this=0x807df40) at ../machine/mipssim.cc:52 #6 0x804de01 in StartProcess (filename=0xbffff4f6 "fork") at ../userprog/progtest.cc:42 #7 0x80481f6 in main (argc=4, argv=0xbffff3c0) at ../threads/main.cc:99 #8 0x80480ee in _start ()
This print out shows the call-stack for thread 1, which is the thread that hit the breakpoint. Salient points are:
GDB also lets you jump back in the call stack to a specific frame. For example, if we wanted to see the state of the program in the ExceptionHandler call, we could select that frame using the fr command (short for frame):
This should produce the output:
#2 0x804dc3a in ExceptionHandler () at ../userprog/exception.cc:58 58 do_system_call(type);
This tells us that SmartGDB is now looking at the state of the program as it was in the call to ExceptionHandler. For example, all local variables are now different:
this = (Thread *) 0x808edb8
This document has introduced a small set of commands for use with the SmartGDB breakpoint debugger. If you would like more information on basic GDB commands, examine the following references:
Note: SmartGDB differs from GDB in that it supports threads. To support threads (and Tcl/Tk scripting) some of the standard gdb commands have been renamed (including array addressing). For a complete list of these commands and information on thread-debugging commands, see the SmartGDB documentation.