TUTORIAL: Assembler/Editor, pt. 2
From: Michael Current (aa700@cleveland.Freenet.Edu)
Date: 01/18/92-12:43:29 PM Z
From: aa700@cleveland.Freenet.Edu (Michael Current)
Subject: TUTORIAL: Assembler/Editor, pt. 2
Date: Sat Jan 18 12:43:29 1992
Reprinted from A.C.E.C. BBS (614)-471-8559
DEBUGGING
Your assembly language programs are bound to have bugs in them.
Asm/Ed provides a method for testing assembled object code. When
at the Asm/Ed EDIT prompt, type BUG and press return. The next
prompt will be DEBUG. The commands for the debugger are all
short one or two letter commands, some followed by an optional
hexadecimal address. To exit DEBUG type X and press return.
DR
The DR command is used to display the contents of the 6502
registers:
DR
A=BB X=10 Y=20 P=B0 S=DF
A is the accumulator, X and Y are the index registers, P is the
processor status register (which includes the carry flag, zero
flag, and etc.), and S is the stack pointer.
CR
The CR command is used to change the contents of any of the 6502
registers.
CR<,1,2,,DE
The values specified go into the registers in the same order they
are displayed by the DR command. In the above example the
accumulator is unchanged, the X register receives a 1, the Y
register a 2, the status register remains unchanged, and the
stack pointer is adjusted to DE.
D
The D command is used to display memory. D3000,0 tells the
debugger to display memory location 3000 hexadecimal. When the
second parameter is less than or equal to the first, only one
location is shown. D3000,3010 requests the debugger to display
memory from locations 3000 through 3010. Enter D by itself and
the next 8 locations (3011 through 3018) will be displayed. If
only the second parameter is omitted, a default of 8 memory
locations are displayed:
D3000
3000 10 40 20 22 34 11 12 FE
Note that the output of the debugger is always in hexadecimal.
All input addresses and register values are to be specified in
hex as well.
C
The C command is used to change memory. The format is:
C3034<21,23,,2E
The command is immediately followed by the starting hexadecimal
address to change. The values to be placed in memory, starting
at the first location, are separated by commas. Two commas in a
row tell the debugger to skip over that memory location, leaving
it unchanged. In the above example memory location 3034 receives
21, 3035 receives 23, and 3037 gets 2E. You may use the D
command to display memory just changed, to verify the new values.
M
The memory move command, M, is used to copy a block of memory
from one area to another. The command format is:
Mmmmm<yyyy,zzzz
This tells the debugger to move memory from locations yyyy
through zzzz to memory beginning at location mmmm. The
destination address (mmmm) must be less than the first source
address (yyyy) or greater than the ending source address (zzzz).
If the source and destination areas of memory overlap, you may
get unexpected results.
V
The verify memory command, V, is used to compare two blocks of
memory. You might use this to compare two slightly different
versions of the same program to see where something has changed,
for example. The format is much like the move command:
Vmmmm<yyyy,zzzz
The above command tells the debugger to compare memory from yyyy
through zzzz to memory at mmmm. Any memory locations that do not
match are shown side by side, as follows:
V7000<7100,7123
7101 00 7001 21
In the above comparison all memory from 7100 through 7123 matched
memory from 7000 through 7023, except at one location. Memory
location 7101 contained a 0 while 7001 contained a 21.
L
The list memory with disassembly, L, command is one of Asm/Ed's
most powerful. It can be used to disassemble your operating
system ROM (beginning at $C000), to see what some of the routines
look like. It can be used to disassemble object files loaded
into memory, to see how they work. The command format is:
L7000 - List a screen (20 lines of code) of memory
beginning at memory location 7000
L - List a screen of memory with disassembly
starting at the next location (pick up where the previous L
command left off)
L7000,7000 - These three forms disassemble one
instruction at memory location 7000 only
L7000,0
L7000,6000
L2300,2400 - Disassemble memory and list to the
screen from locations 2300 through 2400
When the debugger comes across data that will not disassemble
into code (such as data tables or strings, for example) it will
print a series of question marks to the display. Otherwise, the
data is shown in hexadecimal, as well as in its equivalent
assembly mnemonic form:
L5000,00 A9 8A LDA #$8A
A
You may assemble one instruction at a time into memory with the
debugger's A command. This comes in handy when you want to test
a small patch to a program. Simply type A and press return to
get into the single line assembly mode. You must first specify
an address followed by a less than character (<), and then the
assembly instruction. To assemble to successive memory
locations, subsequent entries require only the less than
character followed by the assembly instructions. For example:
A [RETURN]
5001<LDY $1234 [RETURN]
5001 AC3412 Computer's response
<INY
5004 CB Computer's response
In the above example we have assembled LDY $1234 and INY into
consecutive memory, starting at 5001 hex. Note that here your
assembly instructions must use the dollar sign to indicate
hexadecimal. Press return on an empty line to exit the mini
assembler. You cannot refer to labels in the program, since the
debugger doesn't keep track of them. If you do not know the
absolute address of a label required for reference, then it is
time to go back to the source code, make the changes, and
reassemble.
G
The debugger can be used to go to any address and begin executing
your code with the go command, G. Type the letter G followed by
the first execution address. The program will continue to run
until the system crashes, you press the break key, or a BRK
(break) instruction is executed.
T
Sometimes you need to test a few instructions at a time. This is
where the debugger's trace command, T, comes in. Type the letter
T followed by the address at which to begin execution. The
instruction will be executed, immediately followed by a dump of
the instruction (list a single line with disassembly), and the
cpu registers. This continues until a BRK instruction is
executed, or you press the break key.
S
Sometimes you need to test a single instruction at a time. The
debugger's step command, S, is used for this task. Enter S
followed by the address to begin execution. The effects are the
same as the trace command, except that the debugger stops
execution after each assembly instruction. Type S and return
repeatedly to continue "single stepping" the program.
X
The X command is used to exit the debugger and return control to
Asm/Ed's editor.
Error Codes
The error codes between 128 and 255 are the same as those in your
Atari BASIC reference manual. These are generally input, output
errors associated with CIO (central input output) utility
operations, the heart of your Atari's operating system. There
are 19 error codes that you may encounter while assembling or
debugging your programs:
1 - The memory available is insufficient for the program to
be assembled.
2 - For the command "DEL xx,yy", the line number xx cannot
be found.
3 - There is an error in specifying an address (mini-
assembler).
4 - The file named cannot be loaded (wrong file format).
5 - Undefined label reference (you probably misspelled a
label in your program).
6 - Error in syntax of a statement (missing operand, or
misspelled assembly mnemonic).
7 - Label defined more than once.
8 - Buffer overflow. (I'm not certain what this means.)
9 - There is no label or * before "=". (An equal sign was
found in the first field of a line of code. All equals must
be preceded by either a valid label or the asterisk.)
10 - The value of an expression is greater than 255 where
only one byte was required. (e.g. LDA #LABEL, where label
is an address of some memory location greater than 255.)
11 - A null string has been used where invalid.
12 - The address or address type specified is incorrect.
(e.g. LDA (PGZRO),Y would result in this assembly error if
the label PGZRO was not an address of a memory location less
than 256.)
13 - Phase error. An inconsistent result has been found
from pass 1 to pass 2. (e.g. Two bytes were reserved for
some label on the first pass, but on the second pass only
one byte was needed. This is avoided by minimizing forward
references, and defining all known labels at the top of the
file before any assembly code. You will get this error a
lot, as you learn the language.)
14 - Undefined forward reference. (e.g. Misspelled label,
or reference to a label not defined.)
15 - Line is too large.
16 - Assembler does not recognize the source statement.
17 - The line number is too large (32767 is maximum).
18 - LOMEM command was attempted after other command(s) or
instruction(s). LOMEM, if used, must be the first command
after entering the Asm/Ed editor.
19 - There is no starting address. (e.g. You forgot the *=
directive at the top of your program.)
Expressions
The assembler can perform many useful computations for you. The
operators recognized and operations they perform are as follows:
+ Addition
- Subtraction
* Multiplication
/ Division
& Logical and
Expressions may not contain parenthesis, and they are always
evaluated left to right. (There is no precedence placed on
operators). Some examples follow:
100 STORAGE = $4000
110 *= STORAGE + $10 ; Set program counter to $4010
...
200 JMP START+20
...
300 LDA #STORAGE&$0FF ; Get low byte of STORAGE in A
310 LDX #STORAGE/$100 ; Get high byte of STORAGE in X
...
320 LDA #3*15 ; Load the number 45 into A
USR Routines
The USR command of Atari BASIC allows you to call assembly
language routines. These routines can perform special functions
to vastly improve the performance of BASIC. For example,
assembly USR routines may be implemented for player missile
graphics movement, sort algorithms, or high speed disk I/O
functions.
Assembly code won't normally be loaded as part of your BASIC
program. It must be loaded using a routine in BASIC by placing
the data values into strings, or POKEing it into safe RAM, for
example. You may place up to 256 bytes of assembly code into
page 6 (beginning at memory location 1536). If you do not use
the cassette (C:) then up to 128 bytes of code can go into page 4
(beginning at memory location 1024), the cassette buffer. If
your code is "position independent" it may be loaded into a BASIC
string.
What is "position independent" assembly code? Such a program may
have no JMP or JSR instructions (with the exception of JSR's to
ROM addresses that are guaranteed not to move). So how to you
implement loops? Use branch instructions. If your code gets
much larger than 256 bytes, writing position independent code can
be very difficult. The largest routine I've ever written of this
type was 410 bytes long. You may also "relocate" your code.
This requires a foreknowledge of all the JMP and JSR instructions
in your code. You may then load the object code into a string,
determine its starting address, and then POKE adjusted address
values in for all the JMP and JSR instructions. This is no small
task, and is seldom used. Generally, your USR routines will be
fairly small and can be written in a position independent manner.
The format of a BASIC USR command is:
10 A = USR(1536, PARAM1, PARAM2, PARAM3)
The first parameter, 1536 above, must be the starting address of
the assembly code you wish to execute. The values following are
parameters which are passed to the assembly code on the system
stack, after being converted to integer. The variable A takes on
an integer from memory locations $D4 and $D5 (low byte, high
byte). This is the mechanism you would use to return a value to
BASIC.
Lets write a USR routine to add two integers, and return the
result. Our BASIC program might look like this:
10 TRAP 1000
20 OPEN #1,4,0,"D:MYUSR.OBJ":REM Our USR code in a file
30 TRAP 70
40 FOR I=1 TO 6:GET #1,A:NEXT I:REM Ignore 6 byte load
header of file
50 I=1536:REM USR routine was assembled for page 6
60 GET #1,A:POKE I,A:I=I+1:GOTO 60:REM End of file error
will terminate our entry of the program
70 CLOSE #1
80 PRINT "INPUT NUMBER 1 ";:INPUT N1
90 IF N1<0 OR N1>65535 THEN ? "OUT OF RANGE":GOTO 80
100 PRINT "INPUT NUMBER 2 ";:INPUT N2
110 IF N2<0 OR N2>65535-N1 THEN ? "OUT OF RANGE":GOTO 100
120 SUM = USR( 1536, N1, N2 )
130 PRINT "NUMBER ";N1;" PLUS ";N2;" EQUALS ";SUM
140 END
1000 PRINT "COULD NOT FIND USR ROUTINE FILE"
1010 PRINT "MYUSR.OBJ"
1020 END
Now we need to write an assembly language program with Asm/Ed
that implements this USR routine. It will accept parameters N1,
and N2 off the stack (two, two byte integers), add them, and
return the result to SUM through memory locations $D4 and $D5.
Our code might appear as follows:
0 ;LIST#D:MYUSR.ASM
10 ;ASM ,,#D:MYUSR.OBJ
11 SUM = $D4
12 NUM1 = $E0
13 NUM2 = $E2
20 *=1536 ; Assemble for PAGE 6
30 ADDTHEM PLA ; First off the stack is parameter count
40 BEQ ERROR ; Always check for no parameters ERROR
50 CMP #2 ; Did we get exactly 2 parameters?
60 BEQ AOK
70 TAX ; No, clean up stack and return safely
80 CLEANUP PLA ; Two bytes per parameter
90 PLA
100 DEX ; Get all the parameters off?
110 BNE CLEANUP ; when all gone, just the valid return addr
120 ERROR RTS ; is at the top of the stack for the RTS
130 ; We have valid input, compute the sum
140 ; The first parameter in the USR call (after the addr)
150 ; is the first parameter off the stack, high byte
160 ; low byte sequence. REMEMBER this!
170 AOK PLA ; Get NUM1, high byte
180 STA NUM1+1
190 PLA ; Get NUM1, low byte
200 STA NUM1
210 PLA ; Get NUM2, high byte
220 STA NUM2+1
230 PLA ; Get NUM2, low byte
240 STA NUM2
250 ; Now we have the data in temporary storage
260 ; and the stack is cleared of parameters.
270 ; Just the return address (to get us back to BASIC)
280 ; is at the top of the stack - which gets pulled off
290 ; into the program counter automatically by the RTS
300 ; instruction.
310 CLC ; Must clear the carry flag first
320 LDA NUM1 ; Low byte of first integer to add
330 ADC NUM2 ; Add to low byte of second integer
340 STA SUM ; And store in low byte of their SUM
350 LDA NUM1+1 ; Now add high bytes, leave carry alone
360 ADC NUM2+1 ; It "carries over" from previous add
370 STA SUM+1 ; And their summation is complete
380 RTS ; Back to BASIC
Enter this program with Asm/Ed and execute the instructions in
the first two comment lines. When you get an assembly with no
errors, your file D:MYUSR.OBJ should be ready to test with the
first BASIC program.
Work at this until it performs as expected. As you become more
adept at writing USR routines, you may wish to develop utilities
for converting OBJ files into a series of BASIC DATA statements,
so you can simply READ and POKE them without using messy file I/O
to initialize the USR routine. It takes a relatively long time
to install USR routines by poking them into memory or strings,
but once in place they execute amazingly fast.
You will find that USR routines are incredibly difficult to
debug. You need to initialize them and call them from BASIC. If
you mess up the stack or some other operation, the computer
usually crashes inexplicably. It isn't easy to debug USR
routines from DEBUG, because you will have to write sophisticated
test routines to stuff all sorts of test values on the stack.
Stand Alone Assembly
Sooner or later you will get tired of USR routines (mostly
because they are so difficult to debug). When you do, it is time
to take the plunge into writing a stand alone assembly language
program. Then you will get into the complexities of keyboard
input, screen output, disk I/O, and printer output from the
Asm/Ed environment. Complete libraries of routines, such as a
"graphics package" that performs the equivalent of BASIC's
GRAPHICS, COLOR, PLOT, and DRAWTO, will become a necessity. This
is where BOOT CAMP will help the most. In the months to come you
will learn everything from keyboard input to floating point
processing, all from the assembly language level. Most of our
listings are in Mac/65 format. With the exception of macros
(Asm/Ed is not a macro assembler), most changes to Asm/Ed
compatibility will be minor.
As an example of a stand alone assembly language program, and an
illustration of its raw speed, we present the following
demonstration. First type this BASIC program and run it. While
it executes (it will take about 12 minutes), read the remainder
of this article to see how the same functions can be performed in
assembly language:
10 DINDEX=88:REM Screen RAM pointer
20 SCREEN=PEEK(DINDEX)+256*PEEK(DINDEX+1)
30 FOR X=0 TO 255
40 A=X
50 FOR Y=0 TO 255
60 POKE SCREEN+Y,A
70 NEXT Y
80 NEXT X
At location DINDEX is a two byte "pointer". Memory locations 88
and 89 hold the address of the beginning of screen RAM. The
equation in line 20 calculates the variable SCREEN, which we use
as a direct pointer, for the POKE in line 60. In our assembly
language equivalent of the above program, this problem is even
EASIER to solve. (This is seldom the case however, most things
are much harder to do in assembly language. This demonstration
is designed purposefully to show the strengths and speed of
assembly language.) Next two loops are setup. The inner Y loop
is used to poke the current value of X into the first 256 screen
RAM locations. You will see these characters fill the top
portion of your display. All ATASCII values from 0 through 255
are poked, with the help of the X loop. The variable A was used
simply for a more symmetrical comparison with the assembly code
to follow.
Let this BASIC program run to completion. Time it carefully,
study the sweep second hand of your watch creep slowly along.
Feel the annoying impatience of this terribly slow program creep
up your spine. When you finally get the READY prompt, reboot
your computer with Asm/Ed and enter this equivalent assembly
language program:
0 ;LIST#D:SCREEN.ASM
1 ;ASM,,#D:SCREEN.OBJ
2 *=$3400
3 RUNAD=$2E0
10 DINDEX = 88 ; Screen RAM pointer
20 ; We don't have to compute SCREEN, we use post indexed
addressing
30 START LDX #0 ; Initialize variables for loops
40 LDY #0
50 STORE TXA ; Place screen character into A register
60 PUTIT STA (DINDEX),Y ; Place character on screen
70 INY ; Next screen location
80 BNE PUTIT ; Y register "wraps around" to zero after 255
90 INX
100 BNE STORE ; NEXT X
110 RTS ; Return control to DOS
120 *= RUNAD
130 .WORD START ; So we can load and run from DOS
Now execute the two commands in the first two comment lines at
the top of the listing. If you get no assembly errors then you
will have a file SCREEN.OBJ that is ready to load and run. Go to
DOS and execute a binary 'L'oad of the file SCREEN.OBJ. It will
run immediately after loading and return control back to DOS
after performing all 65,536 "POKES" of characters to screen
memory. Did you catch it? You probably didn't if you blinked.
This version of the program takes barely a second to run! If you
want to watch the show for a while, and exit to DOS when a KEY is
pressed, for example, modify your program as follows:
15 CH = 764 ; Keyboard buffer
...
101 LDA #255
102 CMP CH ; key pressed?
103 BEQ START ; Nope, loop
104 STA CH ; Yes, clear out key buffer and exit to DOS
List this version to disk and reassemble it. When loaded from
DOS, it will "poke" all those ATASCII patterns to the screen
continuously until you press any key on the keyboard. To
RANDOMIZE the show, make these changes:
16 RANDOM = 53770 ; Always a random number here
...
50 STORE
60 PUTIT LDA RANDOM ; Get a random fill character
61 STA (DINDEX),Y ; Place character on screen
Notice how I always added a meaningful label for each important
memory location. Avoid the use of code such as LDA 53770. The
proper use of labels makes it much easier to see exactly WHAT
your program does and HOW it gets the job done.
If you didn't pay much attention to ANALOG's Master Memory Map
series, I strongly recommend that you go back and read it all.
Even if you do not understand all of it, you will learn a lot. A
good memory map is the key to unleashing all the power of your
computer. Compute!'s Mapping The Atari, Revised Edition is also
a very good reference guide. As a 6502 Assembly language
reference manual, I use 6502 Assembly Language Programming by
Leventhal. This is a general reference for the 6502
microprocessor, and does not have any specifics on the Atari
computer. It does detail all the 6502 assembly mnemonics, and
provides examples of multiply, divide, and other useful routines.
When you find that Asm/Ed is too slow to suit your tastes, as you
build larger and more sophisticated programs, consider upgrading
to Mac/65. This macro assembler supports the use of INCLUDE
files, allowing you to easily import "canned routines" that have
already been debugged. It's MACRO capabilities allow you to
define high level constructs that vastly simplify the development
of assembly programs. With a good MACRO library (such as
OSS/ICD's Mac/65 Toolkit or QuickCode from Stardust Software),
your assembly source code will resemble BASIC or some other high
level language, while retaining all the power and speed of pure
assembly language. Mac/65 is the absolute FASTEST native 6502
assembler I have ever used, bar none. (Mad Mac for the Atari ST
will assemble 6502 code that blows the doors off Mac/65; but
that's a whole new ball game.)
Welcome to the fast and complicated world of assembly language
programming. I hope this guide will inspire you to put that
inexpensive Asm/Ed cartridge to work on all those fantastic ideas
that old faithful Atari BASIC never could handle.
By: Matthew J. W. Ratcliff, Ratware Softworks, 32 S. Hartnett
Ave., St. Louis, MO 63135
--
Michael Current, Cleveland Free-Net 8-bit Atari SIGOp -->> go atari8 <<--
The Cleveland Free-Net Atari SIG is the Central Atari Information Network
Internet: currentm@carleton.edu / UUCP: ...!umn-cs!ccnfld!currentm
BITNET: currentm%carleton.edu@interbit / Cleveland Free-Net: aa700
-----------------------------------------
Return to message index