The Einsteins Machine Code Monitor System
Introduction
This article like many others I will be doing, is a recreation, tidy up, etc of articles originally
published in old User Group Magazines. As the quality of these old magazines is quite
poor, I decided to take it upon myself to recreate those articles and present them in an
improved format, with pictorial enhancements and where possible improved content.
This article was originally written by C.P. Wallis and Published in Einstein Monthly News
Volume 2 Issue 4.
The Article
Did you know that MOS has a powerful debugger? The monitor is the section of MOS
which can be controlled from the keyboard and it contains commands which allow
individual instructions in a program to be examined as they are processed by the 8 0 chip -
-although reading the DOS/MOS manual is unlikely to produce any enlightenment until you
know what you need.
There are two ways to debug a program: you can insert write statements by trial and error
until you find the variable which fails to change as it should. Or you can step through the
program to the stage at which the error appears and then follow the register changes
which occur. The second method requires a program called a monitor (or various other
names). There are Public Domain (e.g. Z E ) and commercial programs which provide
sophisticated control over the progress of the run if you can master all the commands. The
MOS monitor is simpler to use, but its main advantage is that it is in ROM and so there is
only minimal code exposed if your program goes off the rails. As well as debugging new
programs, the monitor is very useful for finding the correct location for modifications or why
a perfectly legitimate instruction causes Syntax Error.
Preparation
There is no doubt that the quickest way to learn machine code is to step through a short
program. You will need a list of Z80 opcodes in numerical order, together with their
operands (that is the bytes which follow each opcode). The list should show the number of
bytes and type of operand - data, address, port, offset etc. Omit the codes DD and FD
(using registers IX and IY) until you understand the other codes. You will probably have to
write out the list yourself, but it is worth checking as many assembler and disassembler
manuals as you can find. Assembly language textbooks tend to copy the Zilog manual
which gives the codes in binary and needs a lot of work to sort out.
For example the assembly language instruction CALL is represented by code CD and is
followed by two bytes specifying the address of a subroutine to be called (remember that
the HIGH byte is SECOND). So your list will contain…
CODE OPCODE BYTES TYPE
CD CALL 2 Address
Making a list like this takes you half-way to learning machine code. It will also become
obvious that there are various ways t o condense the list into a reasonably small space.
The opcodes are always given a s hexadecimal numbers but don't let this worry you.
There i s no arithmetic needed for opcodes, so think of them as names. The only time
arithmetic is needed i s to convert relative jumps to addresses and MOS provides the A
instruction to do this for you.
Starting the Program
Getting started is the most difficult part and the DOS/MOS manual gives no clues. The
simplest procedure is to use the LOAD command and then go into MOS. If you have a
short well-behaved program TEST.COM, proceed as follows: Remember to type
<ENTER> after each line
PROMPT TYPE IN COMMENTS
0: LOAD TEST.COM Reads program into memory
0: MOS Enters
MOS> G 0100 0100 Set up monitor
This will only work if the program
(1) makes no use of the system stack and
(2) does not expect a command tail.
The command tail is anything which you would type after TEST when the program is
started normally. Look for it in the buffer at address 080H.
There are many ways of starting the monitor and most of them work most of the time but
you are always liable to run into obscure errors on occasion which will waste a lot of time.
After experimenting for more than three years, I have found that the simplest way to be
sure that the monitor is set up correctly is the following sequence (assuming that you
would normally start the program by typing TEST Q.DAT):
0: LOAD TEST.COM Begin as above
0: MOS
> G 0 0100 Dont confuse G0 with GO
0: GO Q.DAT Load command tail
The monitor is now set up at the start of the program with the registers displayed to prove
it. This procedure should be used even if there is no command tail, because it ensures that
there is a valid stack. The DOS/MOS Manual has a good description of the effect of these
instructions which is much easier to follow when you know what needs to be done. The
second address in the G instruction can be in the middle of the program when you have
located a suitable position.
An alternative procedure if you want to skip straight to the middle of the program is to
modify the .com filly placing an FF code at the required start address. This can be done
with a disc editor or from MOS using LOAD and SAVE (remember to note the number of
blocks). The program is run with a normal call and it will stop in MOS when it reaches the
FF code. Change the FF back to the original value with the M instruction and you are
ready to start
Running the Program
The MOS E instruction is the core of the monitor. When you have set up the program, type
T 0100 010F
to display the first sixteen bytes of code. Suppose the value at 0100H is 31 (LD SP,): this
loads the stack pointer and is followed by the address (2 bytes) to which the SP register
should point on completion of the instruction. The whole instruction occupies 3 bytes and
therefore the next opcode is at 0100H + 3 = 0103H. So type
E 0103
The Z80 registers are displayed, showing that the program counter (PC) is now at 0103H.
To check the SP register type
Z2
The next opcode is determined (the code display should still be on the screen) and the
whole process i s repeated. This leads to single-stepping through the program. When you
get more expert, it will be possible to jump further ahead than one instruction: whatever
address is specified after E, the program will run on until it encounters an opcode at this
address, which is called a breakpoint.
The E instruction works by changing the opcode at the address typed to FF, which calls
MOS, so if you have not calculated the address correctly, the program will either run on to
the end or more likely get lost because you have altered an address instead of an opcode.
When this occurs, it is necessary to start again, but it is possible to jump directly to the last
breakpoint encountered. For example if the last breakpoint examined was at 010FH you
would start up the program again using the following in place of the G instruction above.
G 0 010F
Jumps
The first codes you must learn to recognise are the jump codes, because the next address
may be in another part of the program. Unconditional jumps (C3) are easy - the next
address follows directly after the opcode. Relative jumps (18) need more attention
because you have to calculate the address. For example if the program is
0108 18 02
010A 3E 01
010C B7
the value 2 at 0109H is the relative jump, but it must be added to the NEXT address, that
is 010A + 2 = 010C, s o control passes from 0108H to 010CH.
Conditional jumps require some care. It is necessary to check the flags given in the last
register display (ZO if it has scrolled off). For example code 20 (JR NZ,) will continue to the
next instruction if the Z flag is 1, but will do a relative jump if the Z flag is 0.
Calls
A call instruction (CD) goes to the address specified in the next two bytes in the same way
as a jump instruction (C3). However when a return (C9) instruction is subsequently
encountered, control NORMALLY returns to the opcode following the CD instruction, by
fetching the address from the top of the stack It is not difficult to program alterations to the
stack so the less well-behaved programs may return to another location. For this reason
when you reach a return instruction it is always safer to check the new address by typing
Z2, and then displaying the contents of memory at the address in SP using the T
instruction. This shows the contents of the stack, and the program jumps to the address in
the first two bytes.
Loops
Tracing a loop can become very tedious even when there is a fairly low loop count, and
the risk of mistyping rises rapidly with the count. If there is a single exit condition, skipping
out of the loop is easy. With multiple exit points it may still be possible to work out a
common meeting location for all exits, but if one exit is a conditional return and the other is
a jump, it may be necessary to try each in turn and restart if the guess is wrong. Loops are
the most difficult code to trace but in practice the constraints of assembly language
programming frequently lead to short loops with long blocks of code placed in subroutines
and long loops are not very common.
Conclusion
Single-stepping through code is necessary to follow register changes, but it is far too slow
for working through a whole program. Faster progress can be made by counting through
the displayed code to find the opcodes and setting a breakpoint at the next instruction
which could produce a jump. Further improvements in speed will always n e e d some
degree of guesswork: for example skipping subroutine calls will make good progress
through the code, but you will eventually come to a routine which does not return. In the
best programs this would indicate that the program had detected a fatal error condition,
but there are many other possible causes which can only b e identified by tracing the
subroutine.
Always keep a note of addresses and important values when tracing, so that if you make a
mistake (which is only too easy) you c a n readily return to the same position by restarting.
Programs selected for tracing should preferably have been written in assembly language.
Compiled programs usually generate highly convoluted code, particularly if an intermediate
code is produced. This is a list of machine code subroutines with a very short loop
selecting each entry point. I find with this type of program (and in fact any file-using
program) it is best to start by using E 05 repeatedly. This traps the DOS calls and allows
you to follow what is being done to the files at each stage and provides a useful outline of
the program. Of course you need to read the return address from the stack and set a
breakpoint at it in order to get to the next DOS call. Some programs have a sign on
message output by single character DOS calls and you may have to work up through
several subroutines to find the loop exit point.
It has only been possible to mention a few of the many code variations, with the aim of
showing newcomers how to get started and providing some new ideas for regular users,
but in this type of programming there is no substitute for experience.

