!lm10
!rm76
Step-Trace Utility

The Motive:

"Not that it was that good, mind you!  But we needed something, and they should not have yanked it out without providing some other way to debug machine language programs."

When Apple converted over to the Autostart ROM, they not only removed the hardly-ever-used 16-bit multiply and divide subroutines.  They also stripped the S and T commands, which left assembly language programmers naked.  How can you possibly debug complicated 6502 code without at least a single step capability?

Several programs are now on the market, in the $50 price range, which give you step, trace, breakpoints, stack display, et cetera.  "John's Debugger", from John Broderick & Associates, 8635 Shagrock, Dallas, TX 75238 is one.  Someone called me from Augusta, GA, yesterday to tell me about a similar package he has written and wants to market (I'll be reviewing this one; it may become an S-C SOFTWARE product).  I saw another ad this month somewhere, but I cannot find it now.

But I wanted to do something special this month for the Assembly Line, so here is a limited STEP-TRACE program...free!

The Manner:

It is set up as a BRUNnable file, to load at $0800.  If you want to load it somewhere else, you can put in an origin directive (.OR).  The code executed when you BRUN the file (lines 1390-1460) merely installs the "control-Y vector".  This enables the control-Y monitor command, which is a user-definable command.

Once the control-Y vector is loaded, you have two new commands.  If you type a memory address and a control-Y (and a carriage return), the instruction at that memory address will be disassembled and displayed on line 23.  The flashing cursor will be positioned at the end of the disassembled instruction.  Just above the cursor, on line 22, you will see the current register contents.  Line 24 is an inverse mode line which labels the registers, and reminds you of the options you have.

At this point you can type one of the five register names (A, X, Y, S, or P), or a space, or a carriage return.  If you type a carriage return, the trace is aborted and you are returned to the assembler.  If you type a space, the disassembled instruction will be exectuted.  The new register contents will be displayed, the screen will scroll up, and the next instruction will be disassembled on line 23.  If you type a register name, the cursor will be moved under that register.  You can type in a new value for the register, and then hit a space for the next register or a return to get ready to execute again.

If you want to step through a little faster, hold down the space bar and the repeat key.

Once you have terminated the trace (by typing a carriage return), you can restart where you stopped by typing a control-Y and a carriage return.  Since there is no address given, STEP-TRACE will begin where you stopped the last time.  You can stop the trace, do some monitor commands, and then start tracing again.

Two warnings:  I wrote STEP-TRACE to be used from inside the S-C ASSEMBLER II.  That means all monitor commands, including the control-Y, need to be preceded by a dollar sign ($).  If you want to use STEP-TRACE directly from the monitor, and not return inside the assembler after stopping, you need to change line 3500.  It now says JMP $3D0, which restarts DOS and the assembler.  Change it to JMP $FF69, which restarts the monitor.  Line 3470 requires the .DA modification published in the December 1980 issue of AAL.  If you haven't installed that yet, then rewrite line 3470 as five separate lines; if you don't, it will assemble without error but it will be WRONG!

The Method:

Now let's look through the listing, and see how it works.  When the monitor decodes the control-Y command, the address you typed (if any) is loaded into $3C,3D in page zero.  Then the monitor branches to $3F8, where we have already loaded a JMP STEP.TRACE instruction.  We step into the action at line 1510.

Lines 1520-1570:  the X-register is zero if no address was typed.  In this case, we skip around the code to copy the address into MON.PC.  If there was an address, copy it into MON.PC.

Lines 1580-1630:  Set the stack pointer to $FF, giving the whole stack to the program under test.  Move the cursor to the bottom of the screen and print a carriage return.

Lines 1650-1680:  Call on subroutines to display the current register values (from the SAVE.AREA at line 4350-4400), disassemble the instruction pointed to by MON.PC, and wait on you to type something on the keyboard.  This last subroutine does not return unless you type a space, indicating you want to execute the disassembled instruction.

Lines 1690-1860:  Clear the XQT.AREA to NOP instructions.  Get the stack pointer from the SAVE.AREA.  Pick up the opcode byte, and see if it is one we have to interpret rather than execute (BRK, JSR, RTI, JMP, RTS, or JMP indirect).  If so, jump to the appropriate code for each opcode.

Lines 1870-2010:  Get the instruction length (less one) in Y, so we can copy the instruction into XQT.AREA.  See if the opcode is one of the relative branches; if so, change the displacement to $04, so that we can execute it inside XQT.AREA.  Copy the instruction bytes into XQT.AREA.  Restore the registers from the SAVE.AREA, restoring status (P-register last of all.

Lines 2030-2160:  Execute the instruction.  Unless it is a relative branch instruction which branches, jump to did.not.branch.  Relative branches which branch go to line 2100, where the effective address is computed and stored in MON.PC.

Lines 2180-2190:  A BRK instruction displays the registers and returns to the assembler (aborts STEP-TRACE).

Lines 2210-2250:  The RTI instruction checks the stack pointer; if there are not three bytes left on the stack, STEP-TRACE is aborted.  If there are three left, the next byte is pulled off the stack and stored in the SAVE.AREA for the P-register.  The rest of the RTI instruction is the same as an RTS istruction.

Lines 2260-2350:  The RTS instruction checks the stack pointer; if there are not two bytes left on the stacke, STEP-TRACE is aborted.  If there are two left, they are pulled off and stored in MON.PC.

Lines 2370-2470:  The JSR instruction picks up the current MON.PC, adds two, and pushes the result on the stack.  The new stack ponter value is saved in SAVE.AREA.  Then a JMP instruction is simulated.

Lines 2480-2490:  Simulate a JMP instruction by copying the address into MON.PC.

Lines 2500-2530:  Simulate a JMP indirect instruction.  Copy the address contained in the two bytes pointed to by the instruction address into MON.PC.

Lines 2550-2640:  After a normal executed instruction, save all the registers in SAVE.AREA.  Be sure the processor is in binary mode (not decimal).

Lines 2650-2690:  Add the instruction length to MON.PC, and go back to get the next instruction.

Lines 2710-2800:  Using the current MON.PC as a pointer, pick up the two bytes pointed to and put them into MON.PC.  This is used by the JSR, JMP, and JMP indirect processors.

Lines 2820-2930:  Set cursor position to line 23, column 27, and wait for you to type a key.  If you type a carriage return, abort STEP-TRACE.  If you type a space, return to whoever called WAIT.ON.KEYBOARD.

Lines 2940-2990:  See if you typed a register name (letter A, X, Y, S, or P).  If not, go back and wait till you type something else.  If so, go on to line 3000.

Lines 3000-3100:  Set inverse mode, position the cursor to the selected register column, and display the current contents of that register in inverse mode.  Switch back to normal mode.

Lines 3110-3340:  Wait again for you type a character on the keyboard.  If you type a hexadecimal digit, shift the current register contents one digit position to the left, and add in the digit you just typed.  (You can type as many digits as you want to; the last two you type will be the new contents.)  If you type a space or a carriage return, branch to line 3350 or 3400.

Lines 3350-3390:  You typed a space, so move over to the next register.  If you just modified the S-register, move back to the A-register.

Lines 3400-3440:  You typed a carriage return, so scroll up the screen and go back to the top of WAIT.ON.KEYBOARD.

Lines 3450-3470:  REG.NAMES defines the register names.  REG.INDEX is an index into REG.NAMES and REG.CH.  REG.CH is a list of column positions for each of the registers.  (If you have not installed the .DA modification from AAL Volume 1, Issue 3, you need to spread the data values out on five separate lines.)

Lines 3490-3500:  Clear from the cursor to the end of screen, and return through DOS to the assembler.  Change line 3500 if you want to go somewhere else after leaving the STEP-TRACE.

Lines 3540-3590:  Adds the contents of the A-register to MON.PC.

Lines 3630-3740:  Displays the register contents from SAVE.AREA.

Lines 3810-3840:  Prints MON.PC and a dash.  This is called by the disassembly subroutine.

Lines 3880-4330:  Disassembles the instruction starting at MON.PC.  This code is very similar to code in the Apple monitor ROM at $F882.  It is modified slightly to change the spacing, so that there will be room for the register display on the same line.

Lines 4440-4480:  A test program for you to try STEPping through.  Another neat program to trace is at $FCA8 in the monitor (a delay loop).
