Classic Computer Magazine Archive COMPUTE! ISSUE 49 / JUNE 1984 / PAGE 118

VIC And 64 TRACE

Roger Harris

Debugging is far easier if you can watch your program in execution. This program adds a valuable TRACE feature to your debugging toolkit.

Some versions of BASIC have a feature called TRACE, for debugging programs. Apple BASIC has a typical implementation: When the interpreter executes a program with TRACE enabled, the line number of each executed line will be printed on the screen. This allows you to observe the path being taken through your program.

This information can save a great deal of effort in locating logic errors—problems caused by improper program flow. In BASIC, such problems can be caused by using the wrong line number on a GOTO or GOSUB, or by using the wrong variable or conditional test in an IF statement. Tracing, you can determine the first point at which the program begins to behave oddly.

Commodore Upgrade BASIC, used by the VIC-20 and 64, does not have a TRACE. However, the BASIC program presented here will load a machine language (ML) routine which provides the same capability. When the program is run, the ML is read from DATA statements and POKEd into memory. After it's been loaded, you may delete the BASIC program with a NEW command. Now, enter or LOAD your program and RUN it normally.

Taking A TRACE

With the trace routine loaded, a SYS statement may be used to call a subroutine which will enable the trace. The SYS command is always followed by the address of a machine language routine. In this case, the address will depend on where the ML program was loaded, as I shall explain presently. There is another SYS address to disable the trace. You may enter either of the SYS statements before you RUN your program, or they can be statements within the program itself.

The trace produced by the routine will consist of a series of program line numbers, separated by spaces. This display will "wrap" at the end of screen lines, with no attempt to avoid splitting the numbers. Any PRINT output produced by your program will be intermixed with the line numbers.

An unusual feature of this trace is that it will show you the result of each IF statement executed. These results are indicated by printing a T or an F (true or false) after each line number that contained a conditional statement. Statements with multiple conditions will cause a T for each test which is true, or an F for the first condition which is false. It is often very important to know if the conditional part of a statement was executed; this feature gives you an easy way to verify that the program is making its decisions properly.

On the VIC or the 64, you can press the STOP key at any time when your program is running. You will get a message, BREAK IN 150, for example (meaning that the program stopped at line 150), and you will get the READY prompt. You may then resume execution with a CONT (continue) command. When the trace is enabled, you will occasionally find it necessary to use the STOP key to prevent the trace from scrolling off the screen too quickly. In some cases, you may want to add some STOP commands to your program. You can also edit your program to turn the trace on only at critical sections and turn it off for sections which are not under observation. When a program has been STOPped, by the STOP key or command, you may enter any immediate mode statement, such as a PRINT statement to display your variables (?A,B$,F), or a calculation. You may also change the value of variables with assignment statements (for example, X=4). You can still use CONTinue to resume execution, or you can GOTO a particular line number. However, the system will not allow you to CONTinue if you change the program, or if you enter a statement which causes a syntax error. If you edit a statement, you may still use the immediate mode GOTO, but you will have lost the previous value of any variables.

The trace function will not solve all your debugging problems, but obviously you must find a bug before you can fix it. When you can't find a bug by reading the listing, it's time to start investigating, to start TRACEing. You must determine what the program is really doing. The PRINT statement and STOP command are the BASIC programmer's primary debugging tools, but a trace is often the fastest way to find problems caused by failure to execute the proper instruction at the proper time.

The Loader Program

The program is a relocating loader, which will work on any VIC or 64. The program steals 248 bytes of memory from BASIC to load the machine language routine. The routine will not fit in the cassette buffer. Line 10 of the program PEEKs the current BASIC "limit of memory" from locations 55 and 56, and subtracts 248. (The number PEEKed from 56 is multiplied by 256 because it is the high byte of the two-byte address, and 55 is the low byte.) The address saved at 55 and 56 is the highest address, plus one, of memory available to BASIC. The initial address, minus 248, will be the new top of BASIC memory. Line 15 converts this address back to high byte and low byte. Line 20 POKEs these bytes back into 55 and 56, and does a CLR (clear variables) so that BASIC will recognize the new memory limit. The variable TRACE is then set in line 25 to be the new limit of BASIC. This will be the starting location for POKEing the machine language. If your program needs to allocate some memory for custom characters or screen buffers, you can set 55 and 56 to the required value before you run the loader; the routine will always be POKEd above the "current" limit.

This technique will also work on the 64, but you probably will not have to steal any of BASIC'S memory; there is a 4K block of memory starting at 49152 which is not used by BASIC. Unless your program is already using that memory, you can change line 10 to set TRACE = 53000 and completely omit lines 15, 20, and 25. 53000 is a particularly good location, since the number is fairly easy to remember, and the routine will use only the last 248 bytes of the 4K RAM. This will leave the beginning of that memory available for programs which use custom characters, sprites, or other ML routines.

Whatever TRACE is located will also be the SYS address which will enable the trace. TRACE plus 24 will be the SYS address which disables the trace. For example, on the unexpanded VIC, TRACE will normally be set to 7432, so SYS 7432 will turn on the trace, and SYS 7456 will turn it off.

All lines from line 30 down should be included in any version of the program. Line 50 of the program is the beginning of a FOR loop which READs the DATA statements. The trace routine is not inherently relocatable; it uses many absolute addresses. Fortunately, all the external (system) addresses used are the same on the VIC and the 64. This leaves only the problem of addresses of internal subroutines and working storage. The loader program does the relocation by checking for negative numbers in the DATA statements; a negative number indicates a place where a two-byte absolute address is required. The address generated will be the absolute value of the negative number, plus the initial value of TRACE. When all the data has been POKEd, the program re-POKEs two locations which are also dependent on TRACE.

As Always, SAVE Before RUN

As always when typing in programs, you must be careful to get all the numbers correct, and you should save a copy of the program before you run it. If any numbers are wrong in the DATA statements, the results will be unpredictable. When it is run, the program adds up the numbers from the DATA statements and compares the total to the correct sum. This will catch most errors, but it is not foolproof. If the sum is correct, the program will say TRACE READY and display the SYS addresses which will enable and disable the trace. Please be very careful when using any SYS statement; there is a high probability that your computer will "lock up" if you use a wrong SYS address. Such a state can only be fixed by turning the power off and then back on.

How Trace Works

The routine which enables the trace places a three-byte JMP (jump) instruction into locations 124, 125, and 126. This overlays the middle of the CHRGET subroutine, which is used by the interpreter to fetch characters from the BASIC program. The destination of the jump is the beginning of the trace handling routine. This technique is sometimes called a wedge.

When the trace routine is activated, each fetched character will arrive in the A register. If the byte is a space character, the routine jumps back to CHRGET to get the next. Otherwise, the character is pushed on the stack. Next, the program checks a flag which indicates the presence of a conditional statement. The routine then compares the current line number, stored by BASIC at locations 57 and 58, to the line number which was last displayed. If a new line is being executed, the new line number is saved for future reference, and is converted from 16-bit binary to ASCII decimal characters for printing. (However, the routine does not output the line number if it is greater than 64000—BASIC puts a high value in the "current line" location when it is interpreting an immediate command.) Each character of the line number is output to the screen by calling the Kernal (operating system) subroutine CHROUT.

The routine then pops the fetched character from the stack, and checks if the character is the BASIC token (one-byte representation) for a THEN. If so, a flag is set. The presence of a THEN indicates a conditional statement which is about to be resolved. I originally thought that the next call to the trace routine could determine if the condition was true or false by checking for a change in the line number. However, BASIC will make one more call to CHRGET even if the condition is false. Therefore, the flag processing is designed to wait for one call before deciding whether to output a T or an F. If the line number has not changed by then, the condition was true.

The routine always returns the fetched character to the interpreter, with the status register (condition codes) set, as CHRGET normally does.

The routine which disables the trace does so by restoring CHRGET to its original state.