DigiBarn Documents: Teach Xerox Pilot Debugger from Wildflower site
This tutorial introduces the Xerox Development Environment debugger, called CoPilot. CoPilot is a source level debugger: it allows you to debug with the same language constructs and concepts you used in writing the original source program. CoPilot also allows you to make procedure calls from the debugger, to assign values to variables during program execution, and to evaluate expressions.
This tutorial contains two different kinds of messages. The first 14 messages discuss the debugger user interface and introduce many of the common debugger commands. You should read through these messages to get a general idea of the kinds of commands that are available, but don't worry about remembering all of the details.
The remaining messages provide some debugging examples, and some suggestions on general debugging techniques. While you are working through the debugging examples, you should use the earlier messages as a reference if you want more complete information on any of the commands.
When you are through with this tutorial, you will not know everything that there is to know about CoPilot, but you will hopefully have some idea of how much it can help you debug your programs. CoPilot is a good debugger; learning to use it well can save you lots of time.
You should start this tutorial in the CoPilot volume. Bring up your CoPilot window. (If it is on the Inactive menu or tiny it is called CoPilot 12.0; if it is active, the name in the herald of the window is Debug.log.) You will also need the files called Function.mesa, MiscProcs.mesa, Heap.bcd, and String.bcd. Use your Executive or your File Tool to verify that you have these three files. If you don't have them, have someone else help you locate them.
By now, you should be familiar with the basic idea of world-swap debugging: the debugger is in the CoPilot volume, but your test programs execute in the Tajo volume. Thus, if a test program does not work, you can debug in in CoPilot, make some changes, and then rerun it in Tajo again.
When you are executing a client program, there are basically three ways that you can return to the debugger. You can interrupt (SHIFT-STOP); this is a request to the system to stop what you are doing and return to the debugger. You can then later return to the Tajo volume and keep going, if you like.
You can also reach the debugger via a breakpoint in your code. This is essentially the same as an interrupt.
Finally, you can reach the debugger via a program error. We will discuss some of the more common errors and how to deal with them later in this tutorial.
Set a type-in point in the debugger window and type a question mark. You will see a list of possible commands. The debugger works in command completion mode; that is, the debugger extends each input character to the maximal unique string that it specifies. Thus, you type only as much of a command as is needed to uniquely identify it, no more and no less. For example, the list tells you that B stands for Break, but that there is more than one command that starts with the letter A. To see what those options are, type "A?" (Note that the letter is automatically capitalized, regardless of whether you type it in lower or upper case.)
The options that start with the letter A are AScii and ATtach. The capitalized letters tell you how much of the command you need to type. Thus, AS is ascii, and AT is attach. Try typing "at" after the command prompt. As soon as you enter the "t", the debugger fills in the rest of the command for you. If you try to type more, the debugger will interpret the remaining letters as the argument to Attach. To see that this is so, clear out the existing command line by hitting the DELETE key, and then try typing "att". The debugger will not recognize the second t, and will just kill the command.
To see the possible arguments to Attach, type "at?" on a command line. You will see that you can ATtach Condition, ATtach Keystrokes, or ATtach Symbols. (Don't worry; you aren't expected to know what any of these commands means--yet.)
If you type something that you don't mean, you can always use the DELETE key to abort anything you have typed and return you to the top level command processor. As usual, when you give a command or provide input, you will have to enter a carriage return at the end of each line.
You can also control the format (octal or decimal) for debugger output. Invoke the Options command from the CoPilot menu (which you can see by Chording in the debugger window). The values in this window are all enumerated types: that is, you can see all of the options available to you, and the value currently in effect is highlighted. You can change these values by selecting the desired format and invoking Apply!
The values that you specify in this window will apply to all output. However, you can force a particular interpretation of a number by suffixing it: a suffix of D or d forces decimal; b or B forces octal.
Once you are in the debugger, there are a number of commands that you can use to exit the debugger.
K (ill session) kills the current debugging session; in other words, it boots the physical volume. To prevent you from accidentally booting when you didn't really mean it, this command asks for confirmation before it actually executes.
Q (uit) aborts the process that was executing when you entered the debugger; this usually deletes that process. Quit also requires confirmation.
P (roceed) resumes execution of the client program. For example, suppose that you have set a breakpoint, and returned to CoPilot to look at the state of things. When you are ready to return to the client world and continue execution of your program, you can Proceed out of CoPilot into Tajo. If you haven't booted your client volume (Tajo), proceeding will not do anything.
W (orry) is used primarily for debugging the operating system. You don't need to worry about this for now.
If you are debugging in CoPilot, and decide that you need to take another look at something in Tajo, you don't have to Proceed back just to take a look at the screen. Instead, the debugger Userscreen (u) command will return you to Tajo for 20 seconds and allow you to look at the current state of the Tajo screen. (If you want to be able to look at it for a different period of time, consult the documentation in your XDE User's Guide.)
When you use the Userscreen command, control will automatically return to CoPilot when your 20 seconds are up. (You can return to the debugger before the 20 seconds are up by pressing STOP, or keep the Userscreen longer by holding STOP.)
One of the possible debugger commands is --, which is used to insert a comment into the debugger log. When it sees --, the debugger will ignore all input until you enter a carriage return. Thus, you can insert comments amongst your debugging to serve as reminders of what you were doing.
Try inserting some comments in the debug log, if you like.
The current context is the domain for symbol lookup: it consists of the current frame and its corresponding process, module, and configuration. For example, when you ask for information on a variable, the debugger will look only in the current context; if it doesn't find the specified variable within the current context, it will look no further.
Try typing "cu" for current context in the debugger window. This command will give you the name of a module and configuration, as well as the global and local frame number (G and L), and the Process State Block (PSB). (You aren't expected to know what all these things are right now: if you do know, that's great; if you don't know, you will learn about them later in your Mesa training.)
When a program crashes, the debugger is quite good at figuring out where the crash occurred, and setting the current context accordingly. Sometimes, however, such as when you interrupt from the client world to the debugger, you will have to set the context manually.
If the current context isn't the one that you are interested in, there are several commands that let you change it to something that you are interested in. In particular, SEt Configuration, SEt Module context, SEt Octal context, SEt Process context, and SEt Root configuration all allow you to control the current context. You can use any of these commands to change the context, depending on whether you are interested in a module, a process, or a configuration.
Try typing "lp" in the debug.log window, to produce a list of currently active processes. Pick any one, and use the SEt Process context command ("sep") to set the current context to that process. (Use the process number, as in "SEt Process context: 76B".) Now take another look at the current context.
Like all good debuggers, CoPilot allows you to set breakpoints so that you can check up on your program. In the Xerox Development Environment, breakpoints apply to modules that are known within the current context. Thus, you cannot set a breakpoint in your module unless you have first run the program and set the current context so that the debugger knows where to look for your code.
You can set breakpoints either from the DebugOps menu or via commands in the CoPilot window. Through the CoPilot window, you can Break Xit procedure (bx), Break Entry procedure (be), Break All Xits module (bax), or Break All Entries module (bae). These commands allow you to set a breakpoint at entry or exit (or both) to a specified procedure, or to set breaks at entry or exit to all procedures in a specified module.
If you want to set a breakpoint on a location other than the entry or exit of a procedure, you will have to use the DebugOps menu of a file window. The DebugOps Break command uses the current selection to set a breakpoint at the closest statement enclosing the selection. Thus, if you load your source file into an Empty window, you can select the line where you would like to set a breakpoint and invoke Break, either from the DebugOps menu or from the EM Symbiote. (Note: remember, you shouldn't edit your source file while you are debugging, unless you are ready to recompile it and try again. You don't have to edit the source file to set breakpoints.)
Breakpoints are numbered sequentially throughout a debugging session. Breakpoint numbers are never reused within a session: that is, if you set five breakpoints and then remove them all, the next breakpoint that you set will be #6, and not #1 again.
Breakpoints may also be conditional; that is, you can attach a condition so that the breakpoint is only taken when the specified condition holds.
The ATtach Condition command in the debugger takes two arguments: a breakpoint number, and a condition for that breakpoint. (If you don't know the number of the breakpoint that you're interested in, you can find it out with the List Breaks command.)
You can use any of the relational operators (< , >, =, <=, =>) to specify your conditions. You can specify the number of times that a break will be bypassed before the debugger is called. For example, "ATtach Condition #: 1, condition: 3".
There are several commands that allow you to remove a breakpoint. CLear All Breaks, CLear All Entries, and CLear All Xits allow you to remove more than one breakpoint at once.
You can clear a specific break with the Clear Break [#] debugger command, or with the Clear command in the DebugOps menu. You can also CLear Condition, CLear Entry Break, or Clear Xit Break.
You can use the List Breaks command or the Display Break command to take a look at your currently active breakpoints. Display Break takes a breakpoint number as argument and lists its type, the procedure or module name in which it is found (for source breakpoints, the source text is also displayed.) Any conditions attached to the breakpoint are also displayed.
List Breaks lists the above information for all currently active breakpoints.
Once you have returned to the debugger, either via a program error or a breakpoint, you can start actually probing around and looking at values within the current context. Display Stack is one of the most common debugger commands; it allows you to look at the runtime state.
Display Stack shows the procedure call stack of the current process. Try typing "ds" in the debugger window now. Notice that the debugger returns with some information (most likely that it doesn't have any symbols for the current module), and then gives another >. Type another question mark, and you will notice that you have a different set of commands available. The Display Stack command gets you into a different command mode, where you have a different group of commands available:
g displays the global variables of the module j(n) jumps down the stack n levels l lists the source line that invoked the debugger n moves to the next frame b reverse of n; shows the previous frame p displays the formal parameters of the current procedure q quits out of Display Stack mode and returns you to the top level of the debugger.(DELETE will also do this.) r displays the return values s loads the source file into a window (if it isn't already), and scrolls the source line that invoked the debugger to the top of the window. The source line is also displayed in the debugger window. v displays the frame's variables
Note: the full set of Display Stack subcommands is not always available. For example, when the debugger cannot find a necessary symbol table, the commands that allow you to look at variables and parameters are all disabled. After all, if the debugger can't find the symbols, how can it let you look at them?
Therefore, you will have to wait until the debugging session at the end of this module before you get to practice Display Stack. When you want to try it, you can look back at this message to see the possible options. Remember that you will have to Quit out of Display Stack mode before you will have access to any of the top-level debugger commands.
CoPilot contains an interpreter that allows you to perform operations such as assigning new values to variables, dereferencing, making procedure calls, indexing, field access, displaying variables and types, and simple type conversion.
If you type a question mark in the debugger again, you will see that the first command choice listed is a space (" "). Typing a space gets you into interpreter mode. (You can also type a space to get into the interpreter from within Display Stack mode.)
For example, let the interpreter evaluate an expression for you: try typing " 2 + 6 MOD 4" in the debugger (don't forget the initial space); the debugger will do the calculation and provide the answer.
The rest of this tutorial consists of two practice debugging sessions that will allow you exercise the debugger and its interpreter.
The first example is a program called Function, which does not execute properly. To get started, compile and run the program from CommandCentral. When you reach Tajo, type "help function" in the Tajo Executive to see how the program works, and then type the following into the Executive:
Function s/20 Function c/5 Function s/6 s/10 c/4 Function q/
At this point, the program will crash, and you will return to the CoPilot volume.
To find out what happened, bring up your debugger window. The debug log should look something like this:
*** Address fault, PSB: 137B, at 0, in L: 3170B, PC: 4660B (in StringsImplB, G: 37154B) ***
An address fault is one of the most common errors that you will run into. Basically, an address fault occurs when a program tries to access an invalid region in memory. This is generally the result of a NIL pointer.
You should almost always start a debugging session with the Display Stack command, which lets you take a look at the call stack. Execute this command now by typing DS into the debugger window. This will produce a line that looks something like this:
No symbols for L: 3170B, PC: 4660B (in StringsImplB, G: 37154B) >
This tells you that the error occurred in the module StringsImplB, but that the module StringsImplB.bcd is not on the CoPilot volume, so the debugger cannot deduce exactly what went wrong. One way to find out more information is to retrieve the module StringsImplB.bcd; once you have this module, you can determine the line of code where the error occurred.
However, in general you will not have to do this. If you continue down the call stack with the next command (just type n), you will see the entire call stack, one module at a time. In general, you should just look down the call stack until you see your own code. There will be times when your code is not on the stack, in which case you will have to work a little harder to figure out what went wrong, but in general you will see your code somewhere on the stack.
In this case, the original cause of the error is in the module Function, but Function is not on the top of the stack when the program crashed. Thus, you should continue down the stack until you find the module Function. Your debug log should then look something like this:
*** Address fault, PSB: 137B, at 0, in L: 3100B, PC: 4660B (in StringsImplB, G: 37154B) *** >Display Stack No symbols for L: 3100B, PC: 4660B (in StringsImplB, G: 37154B) >n No symbols for L: 54540B, PC: 4427B (in StringsImplB, G: 37154B) >n Main, L: 10034B, PC: 307B (in Function, G: 106754B) >
Once you have found the correct module on the stack, you can look at the line of code where it died with the Source command or the List command. (Remember, these commands are subcommands within Display Stack mode.) Try doing one of these commands. Your debugger window should look something like this:
>Display Stack No symbols for L: 3170B, PC: 4660B (in StringsImplB, G: 37154B) >n No symbols for L: 54754B, PC: 4427B (in StringsImplB, G: 37154B) >n Main, L: 55310B, PC: 301B (in Function, G: 106754B) >s <>cardinal ¬ String.StringToNumber[number, 10];
Thus, the program died while making the call to the procedure String.StringToNumber. Your next question might be to ask what the procedure StringToNumber is supposed to do. To find this out, you can use the Show Type command, as described in the next message.
You can use the debugger to find out the type of various procedures and variables, provided that you have the appropriate file on your local disk. You will learn more about this later; for now, you just need to worry about the process of finding out the type of a procedure, and not about how you can tell when this will work.
To find out the type of the procedure String.StringToNumber, select the name of the procedure anywhere on your screen (this message is probably the easiest place), chord in the debugger window, and select the Show Type command from the CoPilot menu. This will display the type declaration of the procedure in the debug log, like this:
String.StringToNumber: PROCEDURE [s: LONG STRING, radix: CARDINAL ¬ 10] RETURNS [UNSPECIFIED];
Thus, this procedure takes two arguments, a long string and a cardinal, and returns a result of unspecified type.
(You can also execute the Show Type command by typing SH and then the name of the procedure, but not from within Display Stack mode.)
The next step is to take a look at the values that our program is passing to this procedure.
Once you know the line of code on which the error occurred, the next step is typically to start looking at the values of various variables at that point. To look at the local variables for the procedure, use the v command; to look at the global variables, use the g command, and to look at the procedure parameters (if there are any), use the p command.
Try using these three commands. You should get something like this:
>v h = 410720B clientData = NIL outcome = normal OutputProc = PROCEDURE (indirect, 10756B) [10756B] (in module ExecImpl, G: 34004B) operation = 3447042B(1,100)"q" number = NIL cardinal = 0 answer = 0 >g func = PROCEDURE CubeInput (in module Function, G: 106754B) z = ?[4141422B] >p h = 410720B clientData = NIL
You can also use the interpreter to look at a specific variable. For example, in this case, you we know that we are interested in the variable number, so you could look at just that variable by typing a space to enter interpreter mode, and then typing number. Your debug log would look like this:
> number number = NIL
When you type the name of an identifier (variable, module, etc.) to the debugger, you must capitalize it correctly (i.e., the way that it is declared). In Mesa, gamma, GamMa, and GAMMA are three different variables. Remember, you can also control the format of the output using the debugger's Option command. (See the earlier message entitled Debugger output for details.)
Thus, in this case, we are making a call to String.StringToNumber, and passing in a NIL pointer (number.) Remember, address faults are generally caused by NIL pointers. To be really thorough, you could go read the code for the string procedure and find out what happens when you pass in a NIL pointer, but in general you don't need to be that thorough. Address faults almost always mean a NIL pointer, and you have a NIL pointer on the line of code where the program died, so it is a fairly safe bet that this NIL pointer is what caused the problem.
Thus, the next step is figuring out how you should go about fixing the program.
Look at the line of code directly preceding the line of code where the error occurred. This is the line where we do our error checking:
IF (operation = NIL) AND (number = NIL) THEN EXIT;
Thus, if both operation and number are NIL, we exit gracefully. However, in this case number is NIL but operation is not; thus, we get through the error checking, but the program still causes an error. To fix this program, all you have to do is change the word AND to the word OR. Try making this change, and rerunning the program. This time, it should work just fine.
The second debugging example is a "program" called MiscProcs. This program consists of three miscellaneous procedures grouped together in one file: there is no mainline code, so there will be no visible results when you run the program.
Load the file MiscProcs.mesa into a file window, and take a look at the code if you like. (You aren't supposed to be familiar with Mesa syntax yet, but you should be able to get a general idea of what's going on.) There are some global declarations at the beginning of the file, and then four procedures, called Factorial, FreeOldNodes, Interchange, and MakeLinkedList.
You will need to compile this program in CoPilot and then run it in Tajo. Since this program doesn't have any mainline code, you will have to hit SHIFT-STOP to return to CoPilot.
Now use Command Central to Compile and Run MiscProcs. You don't need to bind anything, since there is only one module in this program.
You should now be back in CoPilot; the debug.log will tell you that the reason you are here is that you interrupted (from Tajo). Type "cu" in the debugger to take a look at the current context.
Since MiscProcs doesn't execute any mainline code, the current context will not be the context that you are interested in (look at the module name). So, the first thing that you need to do is set the current context. In general, you will have to set the current context when you reach the debugger by interrupting, but not when you reach it via a program error.
To do this, use the SEt Module context ("sem") command, with MiscProcs as argument. You must type the name just as it appears in the internal declaration (MiscProcs: PROGRAM). (I.E., capitalization matters, and the extension should be left off, as it is not part of the internal name.)
(Remember, there are a lot of ways to specify the current context, such as by process number, module name, or configuration name.)
Try CUrrent context again to see the new context.
You are now going to make a call to the recursive Factorial procedure. As you can see, there is no global code to make a call to Factorial; this module by itself contains no way to make calls to Factorial.
In the debug.log window, type " Factorial" (don't forget the space, which invokes the interpreter). This doesn't invoke the procedure; it just displays information about it so that you can be sure that you and the debugger are talking about the same procedure Factorial. (Again, the name of the procedure must match the internal declaration of the procedure.)
Now, to actually make a call to Factorial, type " Factorial [#]", where # is any number between 1 and 12. This number will be used as the procedure argument. The procedure will only accept input in this range; it will return 0 for the factorial of any number not between 1 and 12. (You might want to read the next paragraph before you hit a carriage return, so that you will know what to expect.) Your screen will go grey as control returns to Tajo to execute the procedure call. When the call has finished, control will return to CoPilot, and the result will be printed in the debug.log window.
Now suppose that you would like to get a closer look at this recursive procedure. By setting a breakpoint as you exit Factorial, you can watch the recursion unwind. Use the debugger "bx" command to set a breakpoint at the exit of Factorial ("Break Xit procedure: Factorial".) This will be Breakpoint #1.
Call Factorial through the interpreter again (" Factorial "). When you return to CoPilot, type " j" in the interpreter to look at the value of the variable j, which is the value returned by Factorial. (Don't forget the carriage return after the j.) Since this is the first time through, j will be 1, the factorial of 0. Now type p to Proceed back to Tajo and continue execution. Remember, Proceed just continues execution where you left off.
The second time that you hit your breakpoint, the value of j will still be 1 (the factorial of 1). Use the interpreter to check this. Now try the Display Stack command. This command will tell you that you are in the procedure Factorial, which is in the module MiscProcs. When you are in Display Stack mode, type a question mark to see the commands that are available to you within this mode. Try L, P, R, S, V, and G. When you are through experimenting with the Display Stack subcommands, use Q(uit) or DELETE to return to the upper level command processor.
If you are now convinced that the program works, use Clear All Breaks, or Clear Break #1 to remove the breakpoint. Then Proceed back to Tajo; the procedure will finish executing and tell you that the factorial of 4 is 24.
Take a look at the procedure Interchange in MiscProcs. To see that the array (A) is currently empty, type " A" in the debugger; you will see that the array has 13 elements, and that they are all currently 0.
Suppose that you want to change some of the values in this array. For example, you can set the first element to 7 by typing " A ¬ 7". Change the values of the first three elements of this array to be 7, 8 and 9. The array should now look like this: A = (13)[7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].
Now call Interchange, and interchange the values of the first and last elements (" Interchange [0, 12]"). When you return to CoPilot, take another look at the array to verify that your switch has in fact been made.
The last procedure in MiscProcs is called MakeLinkedList. This procedure builds a simple linked list. For example, if you specify a list of 5 elements, headNode will point to an element that contains the letter E; headNode.next to the letter D, and so on. To see the first element, type " headNode^, to see the second, type " headNode^.next^", etc.
You are undoubtedly familiar with linked lists, so we will leave you to experiment with the debugger as much as you like. Set some breakpoints in MakeLinkedList; display the stack; change some values if you like; make a breakpoint conditional. Remember, the fact that your program runs in Tajo and your development environment runs in CoPilot means that you cannot harm any of your development tools by experimenting.
If you run into trouble, you can always look back at earlier messages in this file, or reference the debugger chapter of the XDE User's Guide. And take heart: all of the material in this tutorial will be covered in the first training unit, and you will have a second chance to learn anything that you have had trouble with or don't remember how to do.
When you are through experimenting, there is one additional tutorial that you might want to look at. This final tutorial, called TeachToolBuilding.nsmail, that discusses how to build new tools for the XDE.
Back to Wildflower site
The Digibarn's extensive collection of Xerox computers and other artifacts
send site comments to
Please see our notices about the content of this site and its usage.
(cc) 1998- Digibarn Computer Museum, some rights reserved under this Creative Commons license.