MACHINE CODE TRACE
by Carol Brooksbank
from ZX Computing Feb/Mar.1986

Coventry's Carol Brooksbank wrote this
utility to find bugs in her programs and
she thought she'd share it with us.


I don't know about you, but I don't think that I have ever
written a machine code program which ran perfectly first
time. You know the feeling. Eagerly, you type in your latest
masterpiece, enter RANDOMIZE USR something-or-other - and
CRASH! There you sit, with a frozen keyboard and only a
blank screen or a pretty psychedelic pattern to look at. You
have no idea whether there is a fault in the logic of your
program, whether you have made a typing mistake or
miscounted a displacement, and you don't know where to start
looking for the trouble, because you do not know how far
into the program the crash occurred. Well, help is at hand.

This machine code program will give you a hex trace at the
right of the screen, as your program runs. It is only a
partial trace, as it does not show the address of every
instruction as it is executed - if it did, the display would
change so fast that you would never be able to read it - but
every 1/50 second it gives you the address that the program
has reached. This is enough to let you keep an eye on the
progress of your program, and to see where things start to
go wrong. For instance, if the crash is caused by the
program getting into an endless loop, you will see the same
sequence of bytes repeated over and over again after the
crash happens. If you left out a return instruction, so that
the program starts running through the empty bytes above
your program, that too will be obvious. But remember that
the trouble is not always at the point where the crash
happens. A wrong displacement instruction may be some way
away from the point to which it directs the program. You
will still have to think for yourself to decide why the
program runs as it does.

Why is the display in hex? Two reasons. The first is purely
personal. I wrote the program for myself in the first place,
and I always work in hex, so a decimal display would not be
very helpful. (One of these days I shall find myself asking
the greengrocer for "0A pounds of potatoes, please".) The
second reason is rather more important. There is a direct
relationship between the binary form of a number - the bit
pattern held in the registers - and the hex form, which
makes the conversion between the two very straightforward.
Converting an address to decimal would involve multiplying
the high byte by 256, adding the low byte, then isolating
the 5 digits one by one for printing, all of which would
make the routine much more complicated. Since the trace
routine is in the form of an interrupt subroutine, it is
desirable that it should be as short and simple as possible.

The routine makes use of the fact that, whenever the
Spectrum performs a subroutine, the return address is pushed
onto the stack. On an interrupt subroutine, the return
address is the program counter, the point reached in the
main program. If we can retrieve this address from the stack
and display it, we have a trace. Obviously, there are a lot
of instructions performed in between the interrupts which
are not displayed, but this is usually enough to let you see
where a program takes a wrong turning. So, if your machine
code program crashes, load in this routine with your own
program - I am assuming that you always take the precaution
of taping your programs before running them, just in case -
enter RANDOMIZE USR 65271 (48K), 32503 (16K(, run your
program again, and all should be revealed.


Details

The program is explained by the notes in the listing, but
there are one or two details which need more explanation.
The interrupt subroutine starts by saving the present value
of HL in the two spare bytes in the system variables area at
5C80. This is necessary because the existing values of
registers must always be saved at the start of an interrupt
subroutine, and if we push it onto the stack, it will cover
up the address we are trying to retrieve. The address is
then POPped from the stack in HL, PUSHed back again so that
it is in its correct place when the return is made from the
subroutine, and the other register values can then be saved
on the stack. The other spare byte among the system
variables, 5C81, is used as an interrupt counter. If this
has reached 22d, the printing position is set to the top of
the screen and the counter reset to 0. Otherwise, the
routine jumps forward to print the address.

The print subroutine starts with the instruction AND 0F,
which has the effect of resetting bits 4-7 of the A
register, leaving bits 0-3 unchanged, isolating the number
we wish to print. PRINT must be called, therefore, with the
number to be printed in bits 0-3 of A. If the number to be
printed is the "left-hand" digit of the two in the A
register, the instruction RRA is performed 4 times, to move
it to the "right-hand" position, but the print subroutine is
called directly when the "right-hand" digit is to be
printed. When PRINT is called, the DE register holds the
first byte of the screen position for the digit, and at the
end of the PRINT subroutine, DE is restored to that
position.

Since there are only 16 digits which we shall need to print,
0-9 and A-F, a table is set up, starting at FED7 (7ED7 16K),
which holds the start addresses of the bit patterns of those
digits in the ROM character table. Doubling the value of the
number to print and adding it to the address of our table,
points to the correct place in the table to retrieve the ROM
address for that character. The digit can then be printed.
After the 4 digits have been printed, the program variable
SCRP at FF13 (7F13) is pointed to the next screen row down,
and the program exits via the normal interrupt subroutine.

The listing is for the 48K machine. 16K folk should change
the initial "F" in the addresses to "7", each CALL PRINT
instruction should read CDB97E, and the bytes at 7EBD, which
point to HL to the start of the table should be 21D77E. At
START, the high byte of the interrupt vector address should
be 28, giving the bytes 3E28. The interrupt vector address
is not required at 7EFF, so the four bytes between 7EFD and
7F01 may be changed to NOP if you wish, though if they are
left as they are the program will simply ignore them.


Saving

To SAVE the routine on tape:

SAVE "m/c trace" CODE 65116,184 (48K)
SAVE "M/c trace" CODE 32348,184 (16K)

[NOTE: There are actually 185 bytes in the listing, although the last]
[two are just used as temporary storage.                         JimG]

To START the trace:

RANDOMIZE USR 65271 (48K)
RANDOMIZE USR 32503 (16K)

To STOP the trace:

RANDOMIZE USR 65292 (48K)
RANDOMIZE USR 32524 (16K)

Finally, remember that the trace will not work if the
interrupts are disabled. You must change your DI and EI
instructions to NOP while using the trace, and restore them
when you have corrected your problems.


-- end of file --
