************ The Debugger ************ HeliOS has a powerful debug facility which can be user-customised to do almost anything. This debugger is deceptively simple but very powerful: it effectively allows you to step through your code checking any aspect of the system as you go and carrying out any procedure required. You enter DEBUG mode when you press the "DBug" button in the Interpreter, and you are then in DEBUG mode until you press the same button again. In DEBUG mode you lose the "Stack Display" but gain a few new buttons. The DEBUG functions will be described below in more detail, but first here is a quick overview of debugger operation. In DEBUG mode the Interpreter goes through a DEBUG "gate" each time it executes COLON/SEMICOLON threading operations. At this point you can have the debugger run your own user-definable HeliOS mini-program(s) to analyse what your code is doing and print the results for you to see. You can change the debug code at any point in the program, and even have the debug code change its own subsequent execution function depending on program action. You can, for example, pause the program, display variables, dump memory, single step, wait on variable states.........whatever you choose. The debugger has a STEP function which allows you to specify how many times to skip between actual debug operations. By default you will debug every word, but you can change this interactively while your program is running. You can send debug output to the screen or to the "Out" buffer, and while you are debugging you can send the normal output of the program either to the screen or the "Out" buffer. These two options are in the form of a double toggle - you cannot have debugger AND program output to either one single output stream, for obvious reasons of general confusion....... The debugger has a fully interactive mode of operation, which means that you can press any of the debugger control buttons while the program is executing. Thus you can press "Dump" to dump a predefined area of memory at any time while the program executes. This will halt program execution as soon as you hit the "Dump" button, and resume when the dump is finished. You can halt a program by using the debug control panel, or by setting "breakpoints". The breakpoint mechanism is very flexible, in that you can control the debug functions either from the control panel buttons or from programmable functions within the code. At any point you can temporarily pause the debug session by using the bar, and you can abort at any time by pressing . This is very much a user-definable debugger, which is important since debugging requires a very flexible approach for particular contingencies. The debug functions include the ability to list to screen a full nested display of the names of the HeliOS words in any current "thread". By default the debugger runs a very simple standard function which lists all the levels of the current execution thread and displays stack contents. ************************** Listing the current thread ************************** The debug control word SHOWDEBUGWORD can display the current execution word or the complete current execution thread. To understand this, you need to see how a threaded interpretive language such as HeliOS strings together nested command words. Let us assume that you create a word called PRINTHELLO, like this: : PRINTHELLO ." Hello " ; Let us assume then that you create a word called SHOWHELLO, like this: : SHOWHELLO 20 20 CURPUT PRINTHELLO ; Let us assume then that you create a word called SHOWREDHELLO, like this: : SHOWREDHELLO 6 FPENSET SHOWHELLO ; Let us assume then that you create a word called HELLOFRIEND, like this: : HELLOFRIEND SHOWREDHELLO ." Friend!" ; Now let us look what happens when you execute HELLOFRIEND: First the interpreter gets the word HELLOFRIEND and starts to interpret it. The interpreter finds a new word SHOWREDHELLO, so it remembers where it was in its execution of the word HELLOFRIEND and tries to interpret the new word SHOWREDHELLO. The interpreter is now at a second nested level, and the current thread consists of the two words HELLOFRIEND and SHOWREDHELLO. Now the interpreter goes into the word SHOWREDHELLO and starts to interpret it. Here it will encounter a few execution commands for 6 FPENSET followed by your word SHOWHELLO. The interpreter has now found yet another a new word SHOWHELLO, so it once again remembers where it was in all the previous threaded words and starts to interpret SHOWHELLO. In SHOWHELLO, after the cursor positioning commands the interpreter finds yet another of your words, called PRINTHELLO. Once again the interpreter remembers where it was and tries to interpret the latest word PRINTHELLO. The word PRINTHELLO has a simple execution result and requires with no further threading, so now the interpreter goes back one level and continues where it left off on the previous threaded command SHOWHELLO. The interpreter will now work its way back out through the thread until all words have completed. When the interpreter was executing PRINTHELLO, the full thread would look like this, with a depth of four words: HELLOFRIEND SHOWREDHELLO SHOWHELLO PRINTHELLO The debugger would by default use the debug control word SHOWDEBUGWORD to display the full thread of the current execution word at each threading point. Thus if you were to debug the word HELLOFRIEND you would see a series of statements displaying the threading, step by step, which at one point would contain "HELLOFRIEND SHOWREDHELLO SHOWHELLO PRINTHELLO" . Try this in the interpreter, making sure that output is enabled to both the screen AND the buffer. Allow the execution of the word HELLOFRIEND to complete, then look at the results in the "Out" editor. You will notice that the word LIT appears every time HeliOS handles a pure numeric value on the stack. You will also notice some new words used to actually print to the screen. One of these words is called NONAME, which means that it is an internal HeliOS word with no vocabulary entry. There are many of these words with no user name, so in many cases your debug thread displays will contain multiple instances of words with NONAME. Do not worry about this. In particular, you fill find NONAME cropping up often when executing core HeliOS words rather than your own constructed threads. Careful examination of the full thread display in the "Out" editor will allow you to determine exactly which word is executing at any time and what the stack contains at this point. ****************** Using the debugger ****************** By default the debugger has a very simple debug execution function. If you enter DEBUG mode without setting your own debug function, the sytem will execute the default debug function at every threading point. The default debug function will, at each threading point: 1. Print out stack contents in red 2. Print out in black a list of the current thread Pressing the bar will pause the debug operation. Pressing a second time will restart debugging. Pressing will abort debugging at any time. Use the thread display to determine exactly which part of your code is currently executing. The simple stack display used by default may be enough in many cases, but remember that you can create your own debug functions to check memory locations, print variables, etc. You can write debug words to do whatever you like, and you can use the debug control words listed below to control the action of the debugger from within your own debug software, allowing great flexibility. The rest is up to you, and how you wish to approach any particular debug task. **************************** Why NOT to use the debugger! **************************** Strictly speaking, using a debugger is a matter of personal preference, but in most cases you can debug code more quickly and efficiently by the intelligent use of print statements from within your code combined with the use of WAITSPACE commands to allow you to step through your code as it is executing. This can be far quicker and simpler than using a debugger, and we recommend that you avoid getting too involved with using the debugger unless you really need its powerful capabilities to crack a very difficult bug. You will make more progress in understanding your code if you undertake to find most bugs by careful inspection of your code along with the display of vital parameters as your program executes. In this way you become more expert in how your own code functions rather than in the intricacies of setting up complex debugging procedures. Of course, many programmers just love setting up fantastic sophisticated debug capabilities just for the sake of the feeling of power they get by squeezing alarming amounts of esoteric information from recalcitrant and malicious computers. This can be great fun of course, and constructing a subtle and cunning debug system to entrap a really nasty bug can be very satisfying in its own right. **************************************************** A few simple hints on debugging without the debugger **************************************************** HeliOS may indeed have a very powerful user-configurable debugger, but for most debugging you should never need to use it! The following hints should be taken as general programming guidelines, and intelligent programming is preferable to reliance on the debugger. There are a few simple techniques which, combined with sensible programming strategies (for example, do not write 200k of source code altogether with no testing then expect it to work instantly...), will enable you to debug code very simply. When a bug occurs the program will often (probably!) crash. The first stage of debugging is to find exactly at what point in the code the program goes wrong. One good simple debugging technique is to put a simple repetitive "pause and print" debug line into the code which allows you to step through the program slowly until the point where things go wrong. This "pause and print" line should display the stack status and wait for the space bar to be pressed. Something like the following will do nicely: ." Now doing xxxxx. Stack reads: " .S ." to continue" WAITSPACE CR A few of these lines included in your code will give you a sequential talk-through until the crash point is reached. Having written the program yourself you should be able to work out quite well just where in the execution of your code the fault is happening. Once you know where in the code the problem lies, intelligent examination of your code in combination with all the available "clues" will generally reveal the trouble quite easily. Start with the high level code first and then work into your subroutines: in this way you can home in on the actual fault. It is good practice to check each subroutine thoroughly as it is completed, but nevertheless the most unexpected results can occur when even apparently sound subroutines are used together in complex ways. The stack status message debug line suggested above will probably mess up your program's screen display, but that does not really matter in a debug operation. Always check for stack aberrations first. Be aware of what the stack should be reading and check your debug stack readouts scrupulously. If you like, include DUMP commands in your debug instructions to dump areas of memory for careful scrutiny. Displaying variables is also useful, as is the creation of special debug "flag" variables which you can use to warn you of various internal states of your program. There are so many ways for code to fail it is impossible to cover all the aspects of debugging. Debugging is a very intuitive thing and everyone has very personal preferences. The main thing is to never assume ANYTHING to be OK without double checking, and be prepared to be patient and check EVERYTHING relating to your problem with an open mind. Check your stack dumps minutely, dump and also check any areas of memory you think may be suspect, check variable values by including them in your debug lines where appropriate. One big source of trouble is using address pointers which should really be pointing at something useful like a screen structure address, but actually contain zero. A golden rule on a multitasking machine like the Amiga is never to assume that an allocation or opening routine must always succeed. Always check to see if everything has opened correctly, and always take care to initialise all fields of data structures correctly. ******* Warning ******* Sometimes using the debugger can have unpredictable results with certain types of code, so use it carefully and ensure that your work is saved before starting to debug complex code. You may often find that you want to debug software where the screen output of the debugger will cause conflicts with the program execution. In these cases you may wish to supply your own debug function or switch off output to the screen. It is a good idea to always switch off the debugger except when you are actually using it. ***************** Debugger controls ***************** ***************** The "DBug" button ***************** This changes the stack display bar into a set of debugger control buttons and simultaneously enters debug mode. You will then be in debug mode until you press the "DBug" button again, which reverts to normal interpretation. Debug control buttons: ************* Screen Output ************* - These buttons toggle debugger output streams ************* Buffer Output ************* The idea here is that when in debug mode, your program's normal screen output will be stopped, and the screen will be used by the debugger to print debug messages. Similarly the "Out" buffer will get debug output. If you switch off either of the Screen/Buffer output buttons, the normal program output will be sent to that output stream rather than the debug output. Thus you can choose which output you want to see, and can view one output while sending the other to the "Out" buffer. These buttons can be pressed as your program is executing so that you can alternate between output modes at will during a debugging session. This gives great flexibility to the system. What is perhaps even more useful is that the output streams can be changed under program control by your debug routines within the executing program. This system gives you a lot of control and choice over the posting of debugger information. ********************************************** The "Step" button and associated string gadget ********************************************** The step string gadget lets you set how many times the debugger will "skip" between calls to the debug routines. A setting of "1" will cause the debug code to be run for every threaded word, a setting of "4" will sample every fourth threading etc. Again this can be altered at will as the program executes, and can also be changed by the debug software. Thus you could insert debug routines which step through certain sections of code in large jumps until a "vital" part of the program is reached, whereupon the debugger could be made under the control of software to "automatically" change to single stepping. The "Step" button, if switched "On", will cause the software to pause at each debug entry point rather than free running. Thus you can skip parts of a program and return to debug "paused" mode by entering a certain value into the string gadget, setting "Step" to "On", then pressing "Run". Again this can be done interactively while the program is executing or from within the software under debug program control. **************** The "Run" button **************** This starts the program executing after you have paused within debug mode. This function is also available from program control. ****************** The "Pause" button ****************** This stops the program executing and pauses within debug mode. Pressing the bar will also pause the debug operation. Pressing a second time will restart debugging. Pressing will abort debugging at any time. This function is also available from program control. ****************** The "Abort" button ****************** This aborts the debug session and quits the program. Pressing will abort debugging at any time. Pressing the bar will pause the debug operation. Pressing a second time will restart debugging. This function is also available from program control. ********************************************** The "Dump" button and associated string gadget ********************************************** This button lets you set up an address for a memory dump which can be carried out by pressing the "Dump" button at any time while the program is executing. This function is also available from program control. ********************** Debugger control words ********************** There are several HeliOS words which control the action of the debugger from within your program. These words can be used to set up breakpoints and conditional debugging etc. ******** SETDEBUG ( cfa [or 0] - - - ) ******** Used in the form: FIND MYDEBUGWORD SETDEBUG -> Makes MYDEBUGWORD the subsequent debug action 0 SETDEBUG -> Restores the system default debug action ************* GETDEBUGLEVEL ( - - - n1 ) ************* Returns n1, the current depth, or level, of the threaded interpreter. You can use this word to determine how many nested levels deep your current execution word lies. ************* SHOWDEBUGWORD ( n1 - - - ) ************* This word will print to screen the name of the HeliOS word which lies at level n1 in the current execution thread. If you use a value of "n1 = -1", a full list of the full execution thread will be printed. This is the default used by the system debug word. ************* SETDEBUGPAUSE ( - - - ) ************* Acts like the debug "Pause" button to enter debugger paused state. ************* SETDEBUGABORT ( - - - ) ************* Acts like the debug "Abort" button to abort the debug session. ************** SETDEBUGSCREEN ( n1 - - - ) where n1 = switch (1 for "On", 0 for "Off") ************** Acts like the debug "Screen" button to toggle screen output mode. ************** SETDEBUGBUFFER ( n1 - - - ) where n1 = switch (1 for "On", 0 for "Off") ************** Acts like the debug "Buffer" button to toggle buffer output mode. ********* SETDEBUG# ( n1 - - - ) ********* Inserts a number n1 into the debug step number string gadget and updates the step number. ************ SETDEBUGSTEP ( n1 - - - ) where n1 = switch (1 for "On", 0 for "Off") ************ Sets the current status of the debug "Step" button.