Using SmartGDB

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.


Starting SmartGDB

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:

cse451[101]> cd nachos/test/
cse451[102]> smartgdb ../userprog/nachos

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:

cse451[103]> touch ~/.Xdefaults

Then, try to start smartgdb again.


Command Line Arguments

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.

(gdb) conf args -x fork -S ~/swapfile

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.


Setting Breakpoints

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:

(gdb) b System_Fork

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.


Initializing the Thread Library

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:

(gdb) target nachosthreads

Note: If you do not do this, you will not be able to debug anything. SmartGDB will not understand nachos unless you type this.


Running the Program

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:

(gdb) run

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:

  • SmartGDB tells you what breakpoint you have hit (#1 in this case).
  • SmartGDB tells you what file you are currently executing in (../userprog/systemcall.cc in this case).
  • SmartGDB tells you what line you are on in the file, and shows you the code from it (line 422).
  • 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.


    Continuing the Program

    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.

    s steps the program into a function call, or one text line.
    n steps the program over a function call, or one text line.
    c continues execution until the next breakpoint.

    For example, to step the program from the breakpoint in System_Fork we would say:

    (gdb) s

    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.


    Checking Program Data With SmartGDB

    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).

    (gdb) i locals

    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:

    (gdb) p machine

    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:

    (gdb) ptype Scheduler

    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).


    Threaded Programs

    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:

    (gdb) 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.


    Examining the Stack

    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:

    (gdb) thread apply 1 bt
          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:

  • Stack frames are numbered. Lower numbers represent more recent function invocation.
  • Function names, arguments, and source line numbers are listed for each call frame.
  • 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):

    (gdb) fr 2

    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:

    (gdb) i locals
           this = (Thread *) 0x808edb8
        

    Conclusion

    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.


    Steve Goddard
    Last modified: Fri Sep 18 11:46:28 CDT 1998