home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Crawly Crypt Collection 1
/
crawlyvol1.bin
/
program
/
books
/
68k_book
/
arp_doc
/
chap_08.doc
< prev
next >
Wrap
Text File
|
1985-11-20
|
170KB
|
4,464 lines
Atari ST Machine Specific Programming In Assembly
Chapter 8: Seizing Control of the System
The If Onlies
I think it would be very interesting to read a report
of experiments conducted to determine the mean time between
computer system purchase and the first utterance of if
only... in connection with product design, construction or
operating system. Some of my favorite ST if onlies are:
1. If only the operating system were written in
assembly language instead of C.
2. If only the cartridge port data lines were
bidirectional.
3. If only GDOS were more like G+PLUS.
4. If only the file selector function were more like
Universal II.
5. If only we were not limited to six desk accessories.
If only some manufacturer would design the perfect
computer system and sell it to me at a price that I could
afford. Well, that's not likely to happen any time soon,
say not before hell freezes over; neither are the if onlies
associated with any computer system likely to ever be
eliminated. However, to the extent that manufacturers and
programmers find it profitable to satisfy a majority of
users, a continuous stream of improvements and corrections,
in one form or another, is guaranteed. For ST users,
Universal II (a better file selector), G+PLUS (an improved
GDOS), MultiDesk (a desk accessory which eliminates the
operating system limitation of six) and a method of getting
data out of the cartridge port (See Cartridge Port
Interface, by Randy Constan, ST LOG, January 1989.) are
already available.
In this chapter I shall begin to address the subject of
if onlies elimination with user designed software. I am not
going to dwell extensively on aspects of ST system design
and construction which, in my opinion, or in the opinion of
others, constitute errors or limitations, because, however
much I might wish that certain details of that design and
construction would conform to my own ideas of perfection,
the fact is that I could not have done a better job than was
done by the manufacturer. Yet it is possible for me and for
you to redirect certain operating system activities, and/or
to design surrogate functions that effectively tailor the
ST's system controlling software so that it more accurately
responds to our individual needs or tastes. Therefore, I
shall offer suggestions that I believe are indicative of the
methodology involved in altering some aspects of the
operating system in order to effect subjective improvements.
The Second MC68000 Flaw
I discussed one of what I consider to be two serious
flaws in the design of the MC68000 microprocessor in chapter
3. I was then, as I am now, forced to remind you that the
qualification of these design characteristics as flaws is my
response to the limitations imposed upon my programming
efforts by those characteristics. In chapter 3, I spoke of
the obstacle to storing data imposed by the addressing
scheme that rejects pc-relative destination operands. Here
I am concerned with the limitations imposed by the existence
of two privilege states. I am not amused by the necessitity
of having to go through the trouble of forcing the processor
into the supervisor state in order to access system
variables. I could speak of the limitations imposed by this
attribute of the processor for hours; but I have promised
not to dwell on such unpleasant topics. Instead, I shall
propose methods of performing the transition between states
as painlessly as possible. But I want to stress that it is
an attribute that must be overcome in order to write
efficient software. Furthermore, in order to illustrate
that I know that I am not infallible, and that I realize
that I could be completely wrong about everything that I
have said concerning flaws in the design of the MC68000, I
remind you that it is my opinion that these flaws exist.
Normal Processor State Toggling
Without interference from an outside agent, whenever a
program is executed, the operating system relinquishes
processor control to that program. During the program's
execution cycle, it will probably return processor control
to the operating system many times by invoking system
functions. During these invocation cycles, through
operating system directed activities, the functions perform
services for the program. After each such invocation,
processor control is returned to the program. In this
chapter I shall discuss methods of seizing control of system
functions so that their activities may be redirected by the
invoking program. In addition, I shall discuss user defined
functions that act as surrogates for system functions.
Usually, the surrogate functions will access system
variables. Those accesses must be performed while the
processor is in the supervisor state.
Absolute Power Corrupts Absolutely
The first lesson to be learned about seizing control of
anything is that control is inherently tenuous. Ask any
deposed dictator. When you seize control of ST functions so
that you can redirect their activities, or when you seize
control of the processor state, you can remain in control as
long as you prevent vital portions of the system from
becoming corrupted. Here, the word system includes all
hardware and resident software. I will try to remember to
tell you as much as possible about maintaining compatibility
between your applications and others, between your
applications and the operating system and between your
applications and hardware. However, I feel it prudent to
cover my ass now, by saying that, even if I do manage to
tell you all that I know, it will probably not be enough.
All that I can really hope to do is to help you develop a
certain wariness about potential compatibility problems
between certain types of software/software and
software/hardware combinations.
Compounding the complexity of the task we face in
understanding each other in this chapter is the
intractability of the terminology required for the
discussions to follow. This terminology can be perplexing,
even to the most seasoned programmers. One of the reasons
that this is so is that discussants can rarely be certain
that everyone is familiar with the vocabulary involved, and
even when terminological familiarity is not a problem, the
congruency of definitions remains questionable. Some of the
terms and definitions with which we must contend are to be
found in the references under headings such as exceptions,
exception vectors, exception handlers/handling, vectors,
interrupts, interrupt structure, traps and, perhaps, some
others, depending on the particular vernacular of the
writers involved.
My task is to describe required algorithms with
lubricious phraseology so that you will understand (or at
least be able to fake an understanding of) what you are
doing, whether or not your references are comprehensible. I
suggest that you read as much as you can concerning the
topics mentioned above, but with particular concentration on
exceptions caused by the Trap instruction and those caused
by external hardware. I shall not be discussing exceptions
involving resets, bus errors, address errors, and tracing.
Accessing System Variables
The ST's operating system variables are accessible only
when the processor is in the supervisor mode. I pointed
this out when I introduced the get_time subroutine in
program 11. You can see a partial listing of system
variables on pages 236-237 and 250-257 of the Internals
book. The Peel book also contains a partial listing on
pages A-2 through A-6. Each book contains some variables
not listed by the other.
A dissonance greater than disagreement between
information contained in books exists, however. Atari does
not guarantee that system variables located at addresses
below $400 will not change in future versions of the
operating system. In chapter 1, I pointed out that
consumers must realize that the product they receive at the
time of purchase is the product they should plan to possess,
until a new purchase is made. There is no reason to expect
that any alterations to future models of the product will
have any effect on the model purchased. Certainly, there is
no reason to expect that any effects will be beneficial.
Therefore, as long as you are willing to learn to control
the product you have, instead of anticipating enhancements
by the manufacturer, you reduce the manufacturer's control
over you by alleviating the impact of future modifications
to the product. In retaliation, then, we adopt a pugnacious
attitude and thumb our noses at malicious attempts to thwart
our progress via the alteration of variables. We shall work
with what we have.
So, I continue with the mutual understanding that there
is some finite chance, however slim, that operating system
variables used in this book might not match those of your
system, and, if such circumstances should occur, you will be
forced to seek out knowledge about such differences from
other references or by searching through system memory
yourself. We do begin with at least one constant. The
behavior of the MC68000's supervisor mode is not likely to
change.
In addition to permitting access to system variables,
the supervisor mode permits the execution of certain
instructions which cannot be executed in the user mode.
Although it may not be immediately obvious, there need be no
compelling reason not to execute ordinary instructions in
the supervisor mode. (Instructions which must be executed
in supervisor mode are called privileged instructions.)
However, assuring that one is in user mode before exiting a
program is prudent, to say the least.
Forcing the Processor Into Supervisor Mode
As you will learn from reading the references, there is
only one method by which the MC68000 can be forced into the
supervisor state. An exception generating instruction must
be executed. Exception handlers usually return processor
control to the exception generating source with the
processor in the user state. The Atari operating system
provides GEMDOS function $20 as an orderly way of locking
the processor state in supervisor mode until the invoking
program forces the processor back into user mode. Getting
back into user mode can be accomplished in several ways
(Refer to Privilege State Changes in the Motorola manual.),
however, if the supervisor mode has been entered via GEMDOS
function $20, then the return to user mode is usually
accomplished via the same function.
I remind you that the easiest way to force the
processor into the supervisor state is by executing a trap
instruction. You have seen this done in the program which
installs custom traps. Once you are in a trap routine, the
processor is in supervisor mode and it will remain in that
state for the duration of the trap invocation unless bit 13
of the status register is reset to zero. Furthermore, if
bit 13 of the stack word that is the content of the status
register, as it was before the trap invocation, is set to
one, then the invoking program will be executing in
supervisor mode when the return from exception instruction
is executed by the trap handler.
Stack Pointer Control
When the processor is toggled between modes, one of the
most compelling concerns of a programmer is stack pointer
control. Without external interference, the processor will
use the supervisor stack when it is in supervisor mode, and
it will use the user stack when it is in user mode. The
processor accomplishes this by placing the address of the
supervisor stack in the supervisor stack pointer (SSP) and
by placing the address of the user stack in the user stack
pointer (USP). This is the default stack pointer control.
Neglecting the issue of desirability, for the moment, it is
important to realize that GEMDOS $20 transfers the address
of the user stack to the supervisor stack pointer (SSP) when
it is used to toggle the processor from the user state to
the supervisor state. GEMDOS $20 expects that the address
of the supervisor stack will be saved and returned to it
when it is invoked to toggle back to user mode. At that
time the function will reinstate the address from whence it
came. During all of this, the content of the USP remains
constant.
If a programmer decides to force the processor into the
supervisor state by some other method, perhaps by using a
custom trap similar to trap #0, which is installed by
CUSTOM.PRG, then the programmer assumes responsibility for
stack control. The programmer can chose to continue
execution with the SSP pointing to the supervisor stack, or,
as is accomplished via GEMDOS $20, the address of the user
stack may be loaded into the SSP. In any case, in addition
to fulfilling the program's stack needs, the active stack
must be large enough to accommodate all supervisor mode
activity, which includes, but is not limited to, the storage
of the program counter and status register during exception
handling.
I have designed three programs to assist me during this
discussion. Program 42 installs a custom trap that
immediately prints the content of A7 = the address of the
supervisor stack, then sets bit 13 of the invoking program's
status register so that the processor will be in the
supervisor state when processor control is returned to the
trap invoking program. During the course of its execution,
program 43 invokes the custom trap installed by program 42.
In addition, it also prints the content of A7 during other
stages of its execution. Program 43 is divided into two
parts. Part one is executed before a user stack is assigned
within the program. This means that the USP contains the
address of the system assigned default user stack. The
content of A7 is printed three times during the execution of
part one, as follows:
1. Before the custom trap is invoked.
2. During trap invocation, by the custom trap.
3. After trap invocation.
Part two is executed after a user stack has been assigned.
From that point on, the USP contains the address of the
program assigned stack. The content of A7 is printed four
times during the execution of part two, as follows:
1. After the processor has been toggled to user mode
and the user stack has been assigned.
2. During trap invocation, by the custom trap.
3. After trap invocation.
4. After the processor has been toggled to user mode.
The structure of program 44 is similar to that of
program 43, but it does not use the custom trap installed by
program 42 to force the processor into the supervisor state.
Instead, it does this by invoking GEMDOS $20. Seven
addresses are printed by program 44 also, but since I have
no way to force the printing of A7's content during the
execution of function GEMDOS $20, the value returned by the
function in register D0 = address of supervisor stack is
printed instead. This is not quite equivalent to that which
is done in program 43 because GEMDOS $20 returns the top
address of the supervisor stack, whereas the address printed
during the custom trap invocation is the address to which
the SSP has been decremented as a result of the generated
exception. That address is 6 bytes lower than that of the
top of the supervisor stack, because a return address (4
bytes) and status register contents (2 bytes) has been
pushed onto the stack. This means that the custom trap
prints the current content of the SSP.
Program 42. A LSR program that installs custom trap #11.
This program must be executed before program 43 can be
executed.
; Program Name: PRG_6AP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Program Function:
; This is a LSR program that installs custom trap #11, which is invoked
; by PRG_6BP.TOS. This program uses traps installed by CUSTOM.PRG.
program_start: ; Calculate program size and retain result.
lea program_end, a3 ; Fetch program end address.
suba.l 4(a7), a3 ; Subtract basepage address.
enter_supervisor_mode:
trap #0 ; Sets bit 13 of status register to 1.
install_trap_11_routine: ; Note: pointer = vector = pointer.
lea trap_11_routine, a0 ; Fetch address of trap #11 routine.
move.l a0, $AC ; Store trap address at pointer address.
enter_user_mode:
andi.w #$DFFF, SR ; Sets bit 13 of status register to 0.
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; Exit code.
move.l a3, -(sp) ; Program size.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
trap_11_routine:
; When trap #11 is invoked, the CPU pushes the calling program's return
; address onto the supervisor stack, then it pushes the calling program's
; status register contents onto the supervisor stack.
; This custom trap #11 handler sets bit #13 of the calling program's status
; register. The bset instruction can be used to set a bit in the first
; byte of the word on the top of the stack. Bit #13 of the status register
; is bit #5 of that byte. Note: to set a bit means to make it equal to 1.
; When the rte instruction is executed, the CPU will return to the calling
; program with the altered copy of its status register, and the calling
; program will now be executing in supervisor mode.
; Register A7 will contain the address of the supervisor stack. The calling
; program now has complete control of the system. But a decision about
; which stack is to be used must be made.
; The calling program can choose to continue with A7 pointing to the
; supervisor stack, or it can save the contents of A7 = SSP and load the
; address of a user stack into A7. Processing can continue in that manner
; until it is necessary to return to user mode. The return to user mode
; can be accomplished by reloading A7 with the SSP, then by resetting bit
; #13 of the status register. Note: resetting a bit means to make it equal
; to 0.
print_current_SSP: ; SSP = address of supervisor stack.
lea header_1, a0
bsr.s print_string
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
bset #5, (sp) ; The invoking program will be executing in
rte ; supervisor mode upon return.
;
; SUBROUTINES
;
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
print_newline:
lea newline, a0
bsr.s print_string
rts
data
newline: dc.b $D,$A,0
header_1: dc.b ' During trap 11 invocation: ',0
bss
align
program_end: ds.l 0
end
Program 43. This program prints the content of register A7
during various stages of execution. Program 42 must be
executed prior to the execution of this program.
; Program Name: PRG_6BP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Execution Instructions:
; Before this program is executed, trap 11 must be installed via the
; execution of program PRG_6AP.TOS. In addition, this program uses traps
; installed by CUSTOM.PRG. Execute from the desktop or via SPAWN.TTP.
; Program Function:
; Prints the content of register A7 during various stages of execution
; while the state of the processor is varied from user mode to supervisor
; mode, from supervisor mode back to user mode, from user mode to supervisor
; mode a second time and from supervisor mode back to user mode a second time.
; In this program, custom trap #11 is used to toggle the state of the
; processor.
calculate_program_size:
lea -$102(pc), a1 ; Fetch basepage start address.
lea program_end, a0 ; Fetch program end address.
trap #6 ; Return unused memory to op system.
print_heading:
lea heading, a0
bsr.s print_string
; NOTE: During this section of the program, no user stack has yet been
; assigned, therefore, the user stack address is that of the default
; user stack assigned by the system when execution commences.
print_address_of_default_user_stack:
lea header_1, a0
bsr.s print_string
move.l a7, d1 ; Convert address to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
invoke_custom_super_mode_trap:
trap #11 ; Sets bit 13 of SR to 1 and prints SSP.
print_current_SSP: ; SSP = address of supervisor stack.
lea header_2, a0 ; Did invocation of trap #11 alter the address
bsr.s print_string ; in the supervisor stack pointer?
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
bsr.s print_newline
enter_user_mode:
andi.w #$DFFF, SR ; Sets bit 13 of status register to zero.
; NOTE: During this section of the program, a user stack is assigned. The
; user stack address is that of the label "stack".
print_address_of_assigned_user_stack:
lea header_3, a0
bsr.s print_string
lea stack, a7
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
invoke_custom_super_mode_trap_again:
trap #11 ; Sets bit 13 of SR to 1 and prints SSP.
print_current_SSP_again: ; Did invocation of trap #11 alter the address
lea header_4, a0 ; in the supervisor stack pointer?
bsr.s print_string
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
enter_user_mode_again:
andi.w #$DFFF, SR ; Sets bit 13 of status register to zero.
print_address_of_assigned_user_stack_again:
lea header_5, a0
bsr.s print_string
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
terminate:
trap #8
;
; SUBROUTINES
;
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
print_newline:
lea newline, a0
bsr.s print_string
rts
data
newline: dc.b $D,$A,0
heading: dc.b 'PRG_6BP Execution Results => Content of A7:',$D,$A,$D,$A,0
header_1: dc.b ' NO USER STACK ASSIGNED',$D,$A
dc.b ' Start of program: ',0
header_2: dc.b ' After trap 11 invocation: ',0
header_3: dc.b ' USER STACK ASSIGNED',$D,$A
dc.b ' After force to user mode: ',0
header_4: dc.b ' After trap 11 invocation: ',0
header_5: dc.b ' After force to user mode: ',0
bss
align
ds.l 96
stack: ds.l 0
program_end: ds.l 0
end
PRG_6BP Execution Results => Content of A7:
NO USER STACK ASSIGNED
Start of program: F7FF8 = address of system assigned user stack
During trap 11 invocation: 4DB2 = current content of SSP
After trap 11 invocation: 4DB8 = address of supervisor stack
USER STACK ASSIGNED
After force to user mode: 1E254 = address of program assigned user stack
During trap 11 invocation: 4DB2 = current content of SSP
After trap 11 invocation: 4DB8 = address of supervisor stack
After force to user mode: 1E254 = address of user stack
Of special interest, in reviewing the data generated by
program PRG_6BP is the content of A7 after the first and
second trap 11 invocations. In both cases, the invoking
program is executing in supervisor mode, therefore, the
supervisor stack pointer is in use. Note, further, that the
supervisor stack is active. Any stack activity generated by
the program will affect that stack, as will any stack
activity generated by the operating system, therefore, the
stack must be large enough to accommodate both. If it is
not, then a system crash is virtually assured. If extensive
program stack activity is to take place during this period,
the only way to insure that the active stack is large enough
is to load the address of a program assigned stack into
register A7. On the other hand, if the program is to
generate little or no stack activity, then execution can
continue with the active supervisor stack.
Program 44. This program also prints the content of register
A7 during various stages of execution, but it does not use
the custom trap installed by program 43.
; Program Name: PRG_6CP.S
; Version: 1.002
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Execution Instructions:
; Execute from the desktop or from SPAWN.TTP. This program uses traps
; installed by CUSTOM.PRG.
; Program Function:
; Prints the content of register A7 during various stages of execution
; while the state of the processor is varied from user mode to supervisor
; mode, from supervisor mode back to user mode, from user mode to supervisor
; mode a second time and from supervisor mode back to user mode a second time.
; In this program, GEMDOS function $20 is used to toggle the state of the
; processor. Refer to pages 117 and 118 of the Internals book, under the
; $20 SUPER heading. Aside from the fundamental methodology of function
; invocation, two important items of information are discussed there.
; 1. GEMDOS $20 transfers the address of the user stack to the supervisor
; stack pointer (SSP). This means that while the processor is in the
; supervisor state, if that state was forced by invocation of GEMDOS $20,
; then the user stack is active and the supervisor stack is inactive. This
; means that the user stack must be large enough to accomodate supervisor
; state activity. Among other things, this activity includes the storage of
; the program counter and status register during exception processing.
; 2. Invocation of GEMDOS $20 can corrupt, and usually does as far as I can
; determine, registers A1 and D1. Information in that section of the Internals
; book indicates that only this GEMDOS function can alter the values in those
; registers.
calculate_program_size:
lea -$102(pc), a1 ; Fetch basepage start address.
lea program_end, a0 ; Fetch program end address.
trap #6 ; Return unused memory to op system.
print_heading:
lea heading, a0
bsr print_string
; NOTE: During this section of the program, no user stack has yet been
; assigned, therefore, the user stack address is that of the default
; user stack assigned by the system when execution commences.
print_address_of_default_user_stack:
lea header_1, a0
bsr print_string
move.l a7, d1 ; Convert address to ASCII hexadecimal.
trap #5
bsr print_string
bsr print_newline
invoke_gemdos_super_mode_function:
move.l #0, -(sp) ; The zero turns on supervisor mode.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Supervisor stack pointer (SSP) returned in D0.
addq.l #6, sp ; SSP = address of supervisor stack.
movea.l d0, a5 ; Save SSP in scratch register.
print_SSP_returned_by_GEMDOS_20:
lea header_6, a0
bsr print_string
move.l a5, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr print_string
bsr print_newline
print_current_SSP: ; Did GEMDOS $20 alter the address in the
lea header_2, a0 ; supervisor stack pointer?
bsr print_string
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr print_newline
bsr print_newline
enter_user_mode:
pea (a5) ; Restore supervisor stack pointer.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1
addq.l #6, sp
; NOTE: During this section of the program, a user stack is assigned. The
; user stack address is that of the label "stack".
print_address_of_assigned_user_stack:
lea header_3, a0
bsr.s print_string
lea stack, a7
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
invoke_gemdos_function_again:
move.l #0, -(sp) ; The zero turns on supervisor mode.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Supervisor stack pointer (SSP) returned in D0.
addq.l #6, sp
movea.l d0, a5 ; Save SSP in scratch register.
print_SSP_returned_by_GEMDOS_20_again:
lea header_7, a0
bsr print_string
move.l a5, d1
trap #5
bsr print_string
bsr print_newline
print_current_SSP_again:
lea header_4, a0
bsr.s print_string
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
enter_user_mode_again:
pea (a5) ; Restore supervisor stack pointer.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1
addq.l #6, sp
print_address_of_assigned_user_stack_again:
lea header_5, a0
bsr.s print_string
move.l a7, d1 ; Convert to ASCII hexadecimal.
trap #5
bsr.s print_string
bsr.s print_newline
terminate:
trap #8
;
; SUBROUTINES
;
print_string:
pea (a0)
move.w #9, -(sp)
trap #1
addq.l #6, sp
rts
print_newline:
lea newline, a0
bsr.s print_string
rts
data
newline: dc.b $D,$A,0
heading: dc.b 'PRG_6CP Execution Results => Content of A7:',$D,$A,$D,$A,0
header_1: dc.b ' NO USER STACK ASSIGNED',$D,$A
dc.b ' Start of program: ',0
header_2: dc.b ' After GEMDOS $20 invocation: ',0
header_6: dc.b ' SSP returned by GEMDOS $20: ',0
header_3: dc.b ' USER STACK ASSIGNED',$D,$A
dc.b ' After force to user mode: ',0
header_4: dc.b ' After GEMDOS $20 invocation: ',0
header_7: dc.b ' SSP returned by GEMDOS $20: ',0
header_5: dc.b ' After force to user mode: ',0
bss
align
ds.l 96
stack: ds.l 0
program_end: ds.l 0
end
PRG_6CP Execution Results => Content of A7:
NO USER STACK ASSIGNED
Start of program: F7FF8 = address of system assigned user stack
SSP returned by GEMDOS $20: 4DB8 = address of supervisor stack
After GEMDOS $20 invocation: F7FF8 = address of user stack
USER STACK ASSIGNED
After force to user mode: 1E308 = address of program assigned user stack
SSP returned by GEMDOS $20: 4DB8 = address of supervisor stack
After GEMDOS $20 invocation: 1E308 = address of user stack
After force to user mode: 1E308 = address of user stack
The data generated by PRG_6CP illustrates the stack
address transfer effected by GEMDOS $20. After the function
invocation, the program is executing in supervisor mode, but
the supervisor stack pointer does not contain the address of
the supervisor stack; it contains the address of the user
stack. During this period, the supervisor stack is inactive
and the user stack is active. Because the SSP no longer has
access to the supervisor stack, the address of that stack
must be restored to register A7 before the processor is
forced back into user mode, therefore, the value returned in
D0 by GEMDOS $20, which is that address, must be stored by
the invoking program. The invoking program can force the
processor into user mode with GEMDOS $20, passing the
supervisor stack address as a parameter, or it may simple
store the address in register A7 and reset bit 13 of the
status register to zero.
Since the operating system provides a way to force the
processor into the supervisor mode, the question of why a
user defined function to accomplish the same task is
desirable is certainly relevant. Well, a programmer may
decide to dispense with the overhead involved when the
GEMDOS $20 function is invoked. Also, it is not actually
necessary to design a specific function to place the
processor in the supervisor state. It enters that state
whenever an exception occurs. One might choose to redirect
an exception handler so that, in addition to its normal
duties, it also returns processor control to the invoking
program in supervisor mode rather than in user mode.
Altering a System Variable
The next program illustrates the manner in which a
system variable may be altered while the processor is in
supervisor mode. The byte length variable to be altered is
composed of a group of flag bits, each of which controls one
function. Therefore, the byte is composed of eight
variables, each of which is one bit long, compressed into
one byte of data. The address of this byte is $484. Only
the first four bits of the byte are assigned. The bit
assignments given in the Internals book is correct; those
given in the Peel book are not.
The particular bit to be altered is bit #0, that which
controls the emission of a clicking sound when a key is
pressed on the keyboard. I happen to find this noise
extremely disturbing, therefore, I was very upset when I
learned that the clicking sound emission was a default
attribute after power up. I was even more distraught when I
discovered that the only way I could disable this function
was by altering a button in the window available via
CONTROL.ACC, a desk accessory, my version of which consumes
20,080 bytes (decimal).
Program 45 is one of several that accomplish the same
goal. Each of the programs may be executed from the
desktop, or they may be executed from the AUTO folder on a
boot disk. The ability to execute programs of this type
from the AUTO folder is a powerful convenience, however,
there is a payment extracted in the form of the
inconvenience associated with the debugging stage of
programs that are being prepared for this type of service.
You should, if at all possible, confirm the bug-free
operation of programs before placing them in the AUTO
folder, just as you should verify the validity of desk
accessories before placing them into service. We have very
little control over programs that are executed during the
initial stages of power up, therefore, if a malfunction
exists, the system may not be able to complete the boot
cycle because it will be stuck in a mode from which it can't
escape until the defective program (which can't be executed)
is executed.
Fortunately, there are programs available on public
domain and commercial disks that permit us to deselect desk
accessories and programs in AUTO folders during the very
first stages of the boot cycle. From ST Informer, there is
DESKMNG2.ARC on their PDM disk 688, discussed in the June
issue of the newspaper. From MichTron, there is STSELECT,
one of the programs on their STuff disk; this is the
selector that I use. Using one of these utilities, when you
discover that your new program is obstructing the boot
cycle, you can deselect the offending program so that the
system will stop trying to execute it.
Program 45. Altering a System Variable.
; Program Name: PRG_6DP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a PRG extension.
; Execution Instructions:
; Execute from the desktop.
; Program Function:
; Turns off keyclick sound. Refer to page 254 of the Internals book. The
; system variable at address $484 is a byte length variable. The bits of
; this variable have the meanings as indicated in the Internals book. The
; bit of interest is #0. When this bit is a one, the computer emits a
; click each time a key is pressed. When the bit is a zero, these clicks
; are not emitted. A zero is placed in this bit by replacing the content
; of the byte at $484 (which is 7 before the replacement, if key click is
; enabled) with $6.
; If the object code for this program has a PRG extension, it may be placed
; in the AUTO folder of a boot disk or hard disk partition so that it will
; be executed during power up. The reason for doing this would be to
; disable the key click without the presence of the desk accessory CONTROL,
; thereby conserving the memory required by CONTROL.ACC.
; Of course, the program need not be executed at power up. It may be
; executed from the desktop whenever you wish to do so.
; Program Purpose:
; 1. Illustrate the use of GEMDOS function $20.
; 2. Illustrate the manner in which a system variable may be altered.
; 3. Show how the key click may be disabled without the presence of
; CONTROL.ACC as a desk accessory.
; 4. Show how to save a few lines of code by not repositioning the stack
; pointer after the first GEMDOS $20 call, then by using address register
; indirect with displacement addressing to store the SSP within the stack.
mainline:
lea stack, a7 ; Point A7 to this program's stack.
; NOTE: For many programs, an extensive initialization routine is not
; necessary nor desirable. Here, for instance, since this program
; will simply execute, remaining in memory for a very brief period,
; and since no other program will be executed in the mean time, there
; is no reason to calculate the program's size and return excess
; memory to the operating system.
enter_supervisor_mode:
move.l #0, -(sp) ; The zero turns on supervisor mode.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Content of SSP returned in D0.
move.l d0, 2(sp) ; Store returned value in stack.
; Do not reposition stack pointer.
disable_key_click:
move.b #6, $484 ; Refer to page 254 of the Internals book.
return_to_user_mode: ; Stack is already setup.
trap #1
addq.l #6, sp ; Now reposition stack pointer to top.
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
ds.l 24 ; Stack.
stack: ds.l 0 ; Address of stack.
program_end: ds.l 0
end
Altering Variables With XBIOS Function $26
Actually, as if anticipating the ST user's
unwillingness to be content to operate in the user mode,
Atari has provided us with a method of executing a
subroutine in supervisor mode without having to bother with
the transference thereto. The extended bios function
(XBIOS) $26 (dec 38), called superx or superexec by name,
accepts the address of an executable block of code that is
terminated by the rts instruction, and the operating system
executes that block of code in the supervisor mode. Program
46 is an example which illustrates the correct use of the
superexec function. The example given in the Internals book
is flawed (See page 203.). The block of code used in the
example is not executable, and the single instruction
algorithm is not followed by the rts instruction.
Program 46. Execution in supervisor mode via XBIOS function
$26. As always, the SSP uses the program's stack after the
switch to supervisor mode has been accomplished.
; Program Name: PRG_6EP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a PRG extension.
; Execution Instructions:
; Execute from the desktop.
; Program Function:
; Turns off keyclick sound. Refer to PRG_6DP.S for documentation.
; Program Purpose:
; 1. Illustrate the use of XBIOS function $26. This function forces a
; subroutine to be executed in the supervisor mode. The subroutine
; may be located at any address in memory. Function $26 expects the
; address of the subroutine to be on the stack.
; 2. Point out error in Internals book (The example in the Internals
; book is garbage). The "address" which must be pushed on the stack
; is the address of a subroutine that is terminated with an RTS
; instruction. See page 3-13 of the Peel book.
; 3. Illustrate the confusion created by writers of reference books when
; they are not consistent in their usage of number bases. To wit:
; in the Internals book, GEMDOS function numbers are presented in
; hexadecimal. The index of these functions are immediately followed
; by the BIOS and XBIOS functions, which are numbered in decimal. I
; will try remember to use the following notation to express the BIOS
; and XBIOS in both number bases so that it will be easier for you to
; look them up in the Internals book:
; Function = superexec = XBIOS $26 (dec 38).
; Where (dec 38) indicates that the digits 38 are the decimal
; equivalent of the hexadecimal number 26.
mainline:
lea stack, a7 ; Point A7 to this program's stack.
execute_subroutine_in_supervisor_mode:
pea turn_key_click_off ; Push address of subroutine onto stack.
move.w #$26, -(sp) ; Function = superexec = XBIOS $26 (dec 38).
trap #14 ; XBIOS call.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
turn_key_click_off: ; Subroutine to be executed in supervisor
move.b #6, $484 ; mode.
rts
ds.l 24 ; Stack.
stack: ds.l 0 ; Address of stack.
program_end: ds.l 0
end
Of course, you need a program to toggle the keyclick
function on so that you can verify the correctness of those
that turn it off. Program 47 will do that for you. Just
execute PRG_6FP.PRG from the desktop each time you want to
active the keyclick function.
Program 47. This program turns on the keyclick sound.
; Program Name: PRG_6FP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a PRG extension.
; Execution Instructions:
; Execute from the desktop.
; Program Function:
; Turns on keyclick sound so that the programs that turn it off can be
; tested. See programs PRG_6DP and PRG_6EP for further documentation.
mainline:
lea stack, a7 ; Point A7 to this program's stack.
execute_subroutine_in_supervisor_mode:
pea turn_key_click_on ; Push address of subroutine onto stack.
move.w #$26, -(sp) ; Function = superexec = XBIOS $26 (dec 38).
trap #14 ; XBIOS call.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
turn_key_click_on: ; Subroutine to be executed in supervisor
move.b #7, $484 ; mode.
rts
ds.l 24 ; Stack.
stack: ds.l 0 ; Address of stack.
program_end: ds.l 0
end
The error in the Internals book, concerning the
construction of the subroutine, is not the only source of
false information pertaining to the XBIOS function $26
usage. In another reference, the Peel book (page 3-13), a
note appended to functions parameter data declares that the
subroutine must not contain BIOS nor GEMDOS calls.
Strangely enough, the same note is included in Atari's
description of the function. Program 48, the function of
which is to dispel those rumors, is included, for your
perusal, below.
Program 48. The impossible made trivial.
; Program Name: PRG_6GP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Execution Instructions:
; Execute from the desktop.
; Program Function:
; Within a subroutine addressed by XBIOS function $26, does the following:
; 1. Turns off keyclick sound.
; 2. Prints a string with GEMDOS function $9.
; 3. Prints a string with BIOS function $3.
; See programs PRG_6DP and PRG_6EP for further documentation.
; Program Purpose:
; Illustrate the use of XBIOS function $26 to execute a subroutine in
; the supervisor mode, even though the subroutine contains GEMDOS and
; BIOS function calls. This example shows that these function calls
; can be executed within the subroutine, a contradiction to information
; contained in some of the references (See page 3-13 of the Peel book,
; for example.).
mainline:
lea stack, a7 ; Point A7 to this program's stack.
execute_subroutine_in_supervisor_mode:
pea subroutine ; Push address of subroutine onto stack.
move.w #$26, -(sp) ; Function = superexec = XBIOS $26 (dec 38).
trap #14 ; XBIOS call.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
subroutine:
move.b #6, $484
pea message ; Push address of first string.
move.w #$9, -(sp) ; GEMDOS function $9 = c_conws.
trap #1 ; Print first string.
addq.l #6, sp
lea message_2, a3 ; Load address of second string.
print_string:
move.b (a3)+, d3
beq.s wait_for_keypress
move.w d3, -(sp)
move.w #2, -(sp)
move.w #3, -(sp) ; BIOS function $3 = bconout.
trap #13
addq.l #6, sp
bra.s print_string ; Branch until NULL detected.
wait_for_keypress:
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp ; Reposition stack pointer at top of stack.
rts
data
message: dc.b 'This string printed with GEMDOS function $9.',$D,$A,0
message_2: dc.b 'This string printed with BIOS function $3.',$D,$A,0
align
bss
ds.l 48 ; Stack. Must be large enough for system
; use when the switch to supervisor mode
; is accomplished by GEMDOS $26.
stack: ds.l 0
program_end: ds.l 0
end
Execution Results:
This string printed with GEMDOS function $9.
This string printed with BIOS function $3.
An Unfortunate Operating System Bug
While I was trying to prepare a shorter version of
program 48, I discovered that the operating system corrupts
the stack when it executes the bconout function (BIOS
function #3). Were it not for this, the three stack pushes
required to print the first character of a string could be
reduced to one for each subsequent invocation to print each
of the other characters in the string. Program 49
illustrates the attempt to gain the advantage, and figure
8.1 shows how the bconout function corrupts the stack.
Program 49. Illustrates the presence of an unfortunate
operating system bug.
; Program Name: PRG_6HP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Execution Instructions:
; Execute from the desktop.
; Program Function:
; A shorter version of PRG_6GP.S. In addition this program provides a way
; to verify that the trap #13 system function corrupts the stack.
; Just before the trap #13 function is invoked to print a character, three
; words of data must be pushed onto the stack. This having been done once,
; it should thenceforth be sufficient to simply move character data into the
; appropriate location within the stack to continue printing characters, as
; long as the stack pointer is not altered by the program.
; Unfortunately, the location at which the trap #13 function number is
; pushed or stored is corrupted by the trap when it is invoked, therefore,
; it is necessary to restore the function number each time, just before the
; trap is invoked again. So we lose an advantage.
; But the device to which the character is printed is not changed, so we
; can take advantage of that to store it once, before we start printing
; characters.
; Only after all characters are printed is the stack repositioned to the
; top of the stack. In fact, in this program, I wait until after the
; wait_for_keypress algorithm has been executed before repositioning the
; stack pointer.
mainline:
lea stack, a7 ; Point A7 to this program's stack.
execute_subroutine_in_supervisor_mode:
pea subroutine ; Push address of subroutine onto stack.
move.w #$26, -(sp) ; Function = superexec = XBIOS $26 (dec 38).
trap #14 ; XBIOS call.
addq.l #6, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
subroutine:
move.b #6, $484 ; Turn keyclick off.
pea message ; Push address of first string.
move.w #$9, -(sp) ; GEMDOS function $9 = c_conws.
trap #1 ; Print first string.
addq.l #6, sp
lea message_2, a3 ; Load address of second string.
SET_UP_STACK: ; DO THIS BEFORE CHARACTER PRINTING COMMENCES.
MOVE.W #0, -(SP) ; MAKE ROOM FOR CHARACTER TO BE PRINTED.
MOVE.W #2, -(SP) ; OUTPUT DEVICE = SCREEN.
MOVE.W #3, -(SP) ; FUNCTION = BIOS #3.
MOVE.W #3, D4 ; BECAUSE TRAP #13 CORRUPTS STACK.
print_string:
move.b (a3)+, d3
beq.s wait_for_keypress
move.w d4, (sp) ; Restore BIOS function number because
move.w d3, 4(sp) ; Trap #13 corrupts stack.
trap #13
bra.s print_string ; Branch until NULL detected.
wait_for_keypress:
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #8, sp ; Reposition stack pointer at top of stack.
rts
data
message: dc.b 'This string printed with GEMDOS function $9.',$D,$A,0
message_2: dc.b 'This string printed with BIOS function $3.',$D,$A,0
align
bss
ds.l 48 ; Stack. Must be large enough for system
; use when the switch to supervisor mode
; is accomplished by GEMDOS $26.
stack: ds.l 0
program_end: ds.l 0
end
Figure 8.1. Stack corruption by the system's BCONOUT
function.
PRG_6HP stack before trap #13 invoked.
06D68C 00030002 ORI.B #2,D3
06D690 005400FC ORI.W #$FC,(A4)
PRG_6HP stack after trap #13 invoked.
06D68C 0786 BCLR D3,D6 ; This word has been corrupted.
06D68E 00020054 ORI.B #$54,D2
Exception Handler Vectors
Exceptions are interrupts. Exception handlers are
nothing more than subroutines. Vectors are pointers.
Pointers are memory locations that contain addresses.
Therefore, exception handler vectors are memory locations
that contain addresses to subroutines. The word pointer is
used to describe these memory locations because it connotes
the act of pointing to something. Thence to the word vector
is not as straightforward.
As an engineer, I can understand why the originator of
the association between vectors and memory locations that
contain addresses decided to form the association. When an
engineer hears the word vector it connotes a straight line
terminated with an arrowhead, similar to ->. In ST
vernacular, however, the association is not pure. It is
corrupted because some references apply the term vector to
simple data. For example, on page 254 of the Internals
book, the data stored in memory location $484 is called an
attribute vector.
Although they may not always be thought of as such, the
68000 registers are also locations in memory. When a
register contains an address, it can be called a pointer or
a vector. If the address that is stored in a register is a
memory location that also contains an address, then the
content of the register is a pointer or a vector also. Thus
we have pointers to pointers, vectors to vectors, pointers
to vectors, and etc. When a system variable contains data,
that variable is not a vector unless the data is an address.
I have reduced the scope of our interest in exceptions
and interrupts to the extent that I can adjust Motorola's
use of the words in the Programmer's Reference Manual to
this: exceptions are generated by the trap instruction;
interrupts are generated by hardware that is external to the
68000. Note, however, that because you and I both realize
that the words exception and interrupt have identical
meanings, I shall not have to worry about using the words
interchangeably. The only reason we need be concerned, at
all, by interpretations in the meanings of the two words is
this: trap instructions are constituents of an exception
group which has a lower execution priority than does the
exception group of which hardware interrupts are
constituents. In spite of this difference, however, both
exception groups affect the supervisor stack in an identical
manner--that's what we need to know.
The Details of Storing Data in Memory
At this point I must confirm that your knowledge of the
manner in which data is stored in a block of memory
locations to which we refer as a stack matches ST reality.
Consider this: computer memory is constructed from single
element constituents called bits. Now, the 68000
microprocessor permits bit operations to be performed on
data, however, when we store or retrieve data in ram, the
smallest section of ram with which we may deal is eight
bits, a grouping of bits for which we have a special name.
We call this conglomerate a byte.
The 68000 deals with two other conglomerates. These
are called words and longwords. A word is composed of 16
bits (two bytes), and a longword is composed of 32 bits
(four bytes). But, even though a grouping of 32 bits is
recognized as a legitimate conglomerate by the
microprocessor, as far as the interface between the 68000
and ram is concerned, only byte size and word size
operations are permissible. The incongruency is resolved by
separating a longword operation that involves ram into two
word operations.
As you probably know, we can even manipulate a single
bit in ram, as long as we restrict the area containing the
bit to a particular byte. That's what the Motorola manual
means where it states that a bit operation is performed
using the bit number, modulo 8, with zero referring to the
least significant bit. See the btst and bset instructions,
for example.
Now, considering the relationship between permissible
operations and hardware configuration, in order that we be
able to manipulate the data stored in ram, we must know
precisely how it is stored, bit by bit. Assume that you had
the eight bits of data in the 8-bit register shown in figure
8.2. Do you suppose that the microprocessor would place the
data in ram according to method 1, where the data contained
in each bit position of the ram byte is a replica of that
contained in the register, or according to method 2, where
the data in ram has been reversed so that what is in bit
position seven of the register is in bit position zero, and
so on, of the ram byte?
Figure 8.2. Storing the content of a register in ram.
I agree that method 1 seems to be the most logical, but
suppose that an engineer found method 2 to be the least
expensive, or the faster, or the more reliable. Method 2 is
at least a possible storage implementation. Then, when we
consider word size and longword size operations, the
discussion can be extended to possibilities involving the
placement of bytes rather than bits. Will the highest byte
of a register's word or longword content go into the highest
byte of the ram word or longword, or will it go into the
lowest? We must know the answer to this question because,
very soon, we will be involved in the manipulation of one
bit of word of data that will be located within a stack.
Figure 8.3 illustrates two possible implementations,
involving the storage of a data word.
Figure 8.3. Some methods of storing a data word.
Using the first method, the system would store the
least significant byte of register content in the next
available byte of ram, then it would store the most
significant byte of register content in the following ram
byte. Using the second method, the system would store the
most significant byte of register content, then it would
store the least significant byte of register content. Note
that we assume each byte of data to be stored in ram at the
next lowest addressable ram byte, at all times; that is
sequentially from byte 0 to the highest available location.
To eliminate the necessity for further speculation
concerning the method of storage used in the ST, I have
provided program 50, which is followed by three figures that
illustrate the debugger output field for the entire program.
These figures show precisely how the ST stores data in ram.
Specifically, the example illustrates the manner in which a
stack pointer is pre-decremented by the number of bytes
necessary to provide the necessary ram space, and the manner
in which the stored data occupies that space.
Program 50. Storing data in a stack.
; Program Name: PRG_6IR.S
; Version: 1.001
; Function:
; Illustrate the manner in which numbers are placed in the stack.
; Assembly Instruction:
; Assemble in Relocatable mode, go to the debugger and click on the
; relocate button.
; Execution Instructions:
; In the debugger, use the Single step button to execute only the
; instructions which load a stack address into register A7 and those which
; store values into the stack. Move the PC cursor manually to jump over
; the stack space declarations.
text
push_word_1:
lea stack_1, sp
move.w #5, -(sp)
ds.l 1
stack_1: ds.l 0
text
push_longword_1:
lea stack_2, sp
move.l #5, -(sp)
ds.l 2
stack_2: ds.l 0
text
push_word_2:
lea stack_3, sp
move.w #1770, -(sp)
ds.l 1
stack_3: ds.l 0
text
push_longword_2:
lea stack_4, sp
move.l #1234567890, -(sp)
ds.l 2
stack_4: ds.l 0
end
In figure 8.4, you can see the assembled program. The
declared stack spaces, filled with zeroes, are highlighted
by reversed video. The very last line in the output field
is not a part of the program. Note that the Symbolic button
has been clicked off, so that the entire program can fit
within the field.
Figure 8.4. Debugger output field with PRG_6IR.PRG ready for
execution. Do not attempt to execute the statements which
are declarations.
The results of the execution are shown in figure 8.5.
You can see that the ST stores the most significant byte of
a data word in the least significant byte of a memory word.
Then it stores the next data byte into the most significant
memory byte. For example, when it stored the data word
0005, it placed the 00 in memory location $069C3C and the 05
in memory location $069C3D. When it stored the data
longword $499602D2, it placed the most significant byte,
$49, in memory location $069C70. Then it placed the next
data byte, $96, into memory location $069C71. From there,
it placed decreasingly significant data bytes into
increasingly significant memory bytes.
Figure 8.5. Debugger output field showing PRG_6IR.PRG after
execution.
Figure 8.6 is simply the normal disassembled display of
the program. It is included to give you another view of the
data, as it appears in ram. From the evidence presented in
the three figures, we are able to compile the following
assessment:
1. Before the ST stores data in a stack, the stack
pointer is decremented by the number of bytes
required to contain the data.
2. Then the data is stored, most significant byte
first, into the space in memory that is highlighted
by the decrementation.
3. The content of the stack pointer is the address of
the most significant byte of the data. This
knowledge is especially relevant whenever we want to
examine particular bits of the data.
Figure 8.6. PRG_6IR.PRG in normal disassembled format.
Of course, I have a handy example with which to begin.
During my study of various computer languages, I found that,
although the bulk of the output produced by programs was
directed to the video screen, there were times when I wanted
that output to be replicated on my printer. In addition, I
often wanted a hardcopy of my keyboard inputs to a program.
At first, I merely inserted the appropriate instructions in
the program to direct the output as required. There came a
time, however, when I began to find the task of providing
temporary printer output instructions in my programs to be
tedious.
After careful study, I realized that all operating
system controlled output to the video screen and the printer
had to eventually be processed by BIOS (trap #13) functions.
Therefore, I decided to design a program that would trap
screen directed output, and, if the printer was on, redirect
that output to both the printer and the screen. When it was
completed, I used the program to provide hardcopy results at
the end of program listings for myself and for programming
class assignments. Later, I found that it was also useful
during compilations because it provided a printer copy of
compilation errors.
Custom Trap Handlers
Assume that the user stack is located within some block
of memory and that the user stack pointer is pointing to
(contains the address of) the top byte (the first 8 bits) of
this stack. We will call this first byte of the stack
relative location 0. We will refer to the second byte of
the stack as relative location 1, the next as relative
location 2, and so on. Further, assume that the processor
is in user mode.
To generate a BIOS function call, the bconout function,
for example, we push the character (or its code) to be
printed onto the user stack. Because we are stacking word
data, the content of the user stack pointer (register A7) is
pre-decremented by 2 (once for each byte of the word), then
the pushed word (first byte equals zero, second byte equals
the ASCII code for the character) will be stored at relative
locations 0 and 1. The USP now points to relative location
0, the new top of the user stack.
Next, we push the code for the device which is to be
the recipient of the character onto the stack. Assume that
the device is the screen, and the code is $2. After the
push, the character code is located at relative locations 2
and 3, and the device code is located at relative locations
0 and 1. As you can deduce, the stack pointer always points
to relative location zero. All other stack data is
referenced by an offset value, which is, precisely, the
location of the data within the stack, relative to the
location of the data byte that is stored at the address
contained in the stack pointer register.
Now, we push the BIOS function code ($3) onto the
stack. The character is now at relative locations 4 and 5,
the device code is at relative locations 2 and 3, and the
function code is at relative locations 0 and 1. We now
invoke the BIOS call with the trap #13 instruction.
When this instruction is executed, by definition, an
exception is forced. The exception is processed by the
system in the following manner:
l. An internal copy of the status register is
generated.
2. The supervisor state is entered by setting (set to
1) the S bit of the status register.
3. The trace state bit is reset (set to 0).
4. The exception vector number is generated.
5. The content of the program counter (PC) register,
which is the address of the next program instruction
to be executed, is pushed onto the supervisor stack
by the SSP.
6. The saved copy of the status register is pushed onto
the supervisor stack by the SSP.
7. Using the vector number, the address of the trap
handler is fetched and stored in the PC.
8. Execution continues at the content of the PC.
Now, suppose that we want to handle such an exception
ourselves, preempting the system's responsibility. Here is
what must be accomplished:
1. Our trap handler must be memory resident.
2. The address of our trap handler must be that which
is loaded into the PC during the system's processing
of the exception.
3. Our trap handler must execute the code necessary to
satisfy the expectations of the source that
generated the exception.
4. Our trap handler must return control to the source
generating the exception, if that is the normal
expectation, otherwise it must relinquish control in
whatever manner it is expected to do so by the
exception generating source.
Program 51. Installing a custom trap handler.
; PROGRAM NAME: PRG_6JC.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode and save with a TOS suffix.
; Execution Instructions:
; Execute from the desktop. Some versions of the ST operating system
; may require the printer to be turned on for proper operation of this
; trap handler.
; Program Function:
; This program establishes itself in memory as a trap #13 handler. While
; the program is resident, it intercepts all trap #13 calls. If an
; intercepted call's function is $3, and if the device involved is the video
; screen, then the custom trap handler redirects the call to include output
; to the printer as well as to the video screen. In this manner all text
; output to the screen, which is accomplished by BIOS function $3 calls
; (GEMDOS calls $2 and $9 are included because these functions rely on
; BIOS function $3.) is sent to both the printer and the video screen.
; All redirected ASCII codes below $1B, except those for a carriage
; return and a linefeed, are filtered out of the data sent to the printer.
; This prevents certain ASCII codes that are suitable for the screen but
; which are undesirable for the printer from reaching the printer.
; MAJOR NOTE:
; If this program is to be used when a software print buffer is to be
; simultaneously resident, then the software print buffer program MUST be
; executed first. That is, the software print buffer must already be
; resident when this program is executed.
; Program Features:
; 1. Produces a hardcopy of program input and output that is sent to the
; screen. This program eliminates the necessity of providing statements
; in the program to accomplish that task. It permits the printer listing
; of a program to be followed by a printer listing of user/program
; interaction. Especially useful for providing the results of an
; execution for homework problems, or for programs under development.
; 2. When compiling, compiler output to the screen will be sent to the
; printer. Especially useful when debugging the error messages
; that appear on the screen. The printer output lets you go back to
; the editor with the error list hardcopy.
; 3. If the Show button is selected from the Show/Print/Cancel Desktop
; dialog box, when this program is resident, the data which appears
; on the video screen will also be sent to the printer.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a3 ; Fetch program end address.
movea.l 4(a7), a4 ; Fetch basepage address.
suba.l a4, a3 ; Program size is in A3.
lea stack(pc), a7 ; Provide a user stack.
install_new_trap_13_vector:
pea custom_trap_handler(pc) ; Push new trap handler address onto stack.
move.w #$2D, -(sp) ; Push trap 13 vector number.
move.w #5, -(sp) ; Function = setexec = BIOS $5.
trap #13 ; Current trap handler vector returned in D0.
addq.l #8, sp
move.l d0, preempted_handler_address
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
; NOTE:
; The custom trap #13 handler is entered each time an application invokes
; the trap #13 call. If the call does not involve printing a character to the
; screen, then a jump is performed to the preempted trap #13 handler.
; If the call involves printing an escape sequence (An escape sequence
; is a two-character code, the first of which is $1B or 27 decimal. Escape
; sequences provide screen control--see section 3.6 "The Atari VT52 Emulator",
; pages 245-249 of the Internals book.) for screen control, then the sequence
; is sent to the screen via the preempted trap #13 handler.
; Otherwise, for each trap #13 call that is also a bconout invocation
; with device code #2, the custom_trap_13 routine is entered three times.
; The first time that the handler is entered, a variable is initialized, then,
; when it is entered subsequently, a jump, over the initialization sequence,
; to the preempted trap handler is performed. The custom trap handler is
; entered three times because the custom handler prints to both the screen
; and the printer by invoking its own trap #13 calls; those calls are also
; intercepted by the custom handler.
; The custom handler must be able to handle trap #13 calls made while
; the processor is in supervisor mode or user mode.
custom_trap_handler:
tst.b initialization_flag
bne skip_initialization
; Processing the stack data:
; The location of the stack data that must be processed by this subroutine
; depends on the state of the processor when the exception occurs. If it was
; in the supervisor state, then the data will be stacked, as indicated, at the
; following relative locations:
; location - 11 = character to be printed, byte length
; Old Top of Stack: location - 10 = character to be printed, word length
; location - 8 = device to which character is to sent
; location - 6 = bios command to be executed
; location - 4 = program counter low word
; location - 2 = program counter high word
; SSP -> location - 0 = invoking program's status register content
; In each case, above, the location listed is the location of the most
; significant byte of the data listed. For example, relative location 10
; contains the most significant byte of the word which specifies the
; character to be printed. Relative location 11 contains the least
; significant byte of that word. Actually, relative location 11 is
; "the least significant byte" in computer vernacular, but, in fact, it
; is that byte which contains the character code. The byte at relative
; location 10 contains only zeroes.
; The supervisor stack pointer (SSP) will be pointing to the new top of
; stack; the data there will be the content of the most significant byte
; of the status register, as it was when the exception occurred. Now, as
; it turns out, this byte will be the one of significance, as far as the
; trap handler is concerned.
; If the processor was in the user state, then the data will be stacked,
; as indicated, at the following relative locations:
; Old Top of Stack: location - 4 = character to be printed
; location - 2 = device to which character is to sent
; USP -> location - 0 = bios command to be executed
; The user stack pointer (USP) will be pointing to the top of the stack;
; the data there will be the bios command to be executed.
; In order to process the stack data without regard to the processor state
; before invocation, if the processor was in user mode, then the offsets used
; to access the stack data with the USP as reference must be adjusted so that
; they match the offsets used to access the stack data with the SSP as
; reference. Therefore, if the processor was in user mode when the trap #13
; call was made, the value six is subtracted from the register that is used
; to access the data.
; Then, common offset values, which will access the data correctly regardless
; of the stack pointer used as reference can be used.
; When we begin, we know that the processor is now is supervisor mode,
; however, we must determine its state at the time of the exception. That
; processor state must be checked by testing bit 13 of the status register.
; We know that the SSP is now pointing to the content of the status register
; as it was at exception time. In fact, it is pointing to the most significant
; byte of the status register word.
; There are two ways a bit can be tested with the 68000 BTST instruction.
; 1 - If the destination operand is a data register, then any of 32 bits
; may be tested.
; 2 - If the destination operand is not a data register, then only 1 of
; 8 bits may be tested.
; In either case, the bit to be tested may be specified in a source data
; register or as immediate data.
; Since the bit we want to test is on the stack, we can only test a bit
; of a single memory byte. The bit we must test (status register bit 13) is
; bit 5 of the byte that is being addressed by the SSP.
get_processor_status:
movea.l sp, a0 ; Fetch address of current top of stack.
btst #5, (sp) ; Supervisor mode test.
bne.s supervisor_mode ; No adjustment is necessary if the
; processor was in supervisor mode.
move.l usp, a0 ; Fetch address of current top of user stack.
subq.l #6, a0 ; Adjust user data access address.
user_mode:
supervisor_mode: ; Processing for either mode follows.
cmpi.w #3, 6(a0) ; Writing a character to a device?
bne not_bconout_call
cmpi.w #2, 8(a0) ; Is device the screen?
bne.s not_screen
; NOTE:
; The information desired, at this point, is the ON/OFF status of the
; printer. My printer, the star NX-10 provides this information on pin 13
; of its parallel interface. Unfortunately, the ST does not permit the
; utilization of this data. This is an example of hardware/hardware
; incompatibility.
; Because of this ST deficiency, the printers BUSY/NOT BUSY signal,
; on pin 11 of the interface is forced into double duty. In addition to its
; normal function, this signal is used by the ST to determine if the printer
; is ON or OFF.
; We would like to be able to direct output to the printer, at will,
; simply by manually turning the printer ON or OFF.
; The problem here is this: because the response of the printer interface
; is so much slower than the video screen interface, we can't use the bcostat
; function (BIOS #8) to determine the printer status (before attempting to
; write to the printer) in between outputs to the screen. When we attempt to
; do that, we find that pin 11 indicates that the printer is busy (same as OFF
; signal) most of the time, even when the printer is turned on. Thus, the
; printer receives only some of the data sent to the screen.
; Therefore, the trap handler must just assume that the printer is on.
; This is no problem with some versions of the ST operating system. If the
; printer is off, nothing will be sent to it. But some versions of the
; operating system will wait (forever, it seems) for someone to turn on the
; printer. If this happens, then you must turn on the printer while this
; trap handler is resident.
esc_sequence_test:
tst.b esc_sequence_flag
bne.s reset_esc_sequence_flag
cmpi.w #$1B, 10(a0)
bne.s not_esc_sequence
move.b #1, esc_sequence_flag
bra.s use_preempted_handler
reset_esc_sequence_flag:
move.b #0, esc_sequence_flag
use_preempted_handler:
movea.l preempted_handler_address(pc), a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
not_esc_sequence:
move.b #1, initialization_flag
move.w 10(a0), character ; Store character for printer.
write_character_to_screen:
move.w 10(a0), -(sp) ; Push character onto stack.
move.w #2, -(sp) ; Device = screen.
move.w #3, -(sp) ; Function = bconout = BIOS $3.
trap #13
addq.l #6, sp
ascii_code_test: ; Filter out undesirable codes.
move.w character(pc), d0
cmpi.w #$1B, d0
bgt.s write_character_to_printer
cmpi.w #$A, d0
beq.s write_character_to_printer
cmpi.w #$D, d0
bne.s undesirable_ascii
write_character_to_printer:
move.w d0, -(sp) ; Push character onto stack.
move.w #0, -(sp) ; Device = printer.
move.w #3, -(sp)
trap #13
addq.l #6, sp
undesirable_ascii:
move.b #0, initialization_flag
rte
not_screen:
not_bconout_call:
skip_initialization:
movea.l preempted_handler_address(pc), a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
bss
character: ds.w 1
preempted_handler_address: ds.l 1
esc_sequence_flag: ds.b 1
initialization_flag: ds.b 1
align
ds.l 48 ; Stack
stack: ds.l 1 ; Address of stack.
program_end: ds.l 0
end
Program 52. Another version of program 51.
; PROGRAM NAME: PRG_6KC.S
; VERSION: 1.001
; The function of this program is identical to that of PRG_6JC.S. I am
; including it because, in PRG_6JC's trap handler, I used an algorithm to
; access stack data which you are most likely to see in references. In this
; program I show you another method, the method that I prefer.
program_start: ; Compute program size and retain result.
lea program_end(pc), a3 ; Fetch program end address.
movea.l 4(a7), a4 ; Fetch basepage address.
suba.l a4, a3 ; Yields size of memory that must remain
; resident.
load_stack_address:
lea stack(pc), a7
install_new_trap_13_vector:
pea custom_trap_handler(pc)
move.w #$2D, -(sp) ; Trap 13 vector number.
move.w #5, -(sp) ; Function = setexec = BIOS $5.
trap #13 ; Current trap handler vector returned in D0.
addq.l #8, sp
move.l d0, preempted_handler_address
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Size of memory to remain resident.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
; Here I illustrate another way to adjust one of the stack reference pointers
; so that common offset values may be used to access stack data. In this
; program the value six is added to the register that is used to access the
; data if the processor was in supervisor mode before the invocation.
custom_trap_handler:
tst.b initialization_flag
bne skip_initialization
move.l usp, a0 ; Load address of current top of user stack.
get_processor_status:
btst #5, (sp) ; User mode test.
beq.s was_user_mode ; No adjustment is necessary if the
; processor was in user mode.
movea.l sp, a0 ; Load current top of supervisor stack.
addq.l #6, a0 ; Adjust SSP for user mode type data access.
was_supervisor_mode:
was_user_mode: ; Processing for either mode follows.
cmpi.w #3, (a0) ; Writing a character to a device?
bne.s not_bconout_call
cmpi.w #2, 2(a0) ; Is device the screen?
bne.s not_screen
esc_sequence_test:
tst.b esc_sequence_flag
bne.s reset_esc_sequence_flag
cmpi.w #$1B, 4(a0)
bne.s not_esc_sequence
move.b #1, esc_sequence_flag
bra.s use_preempted_handler
reset_esc_sequence_flag:
move.b #0, esc_sequence_flag
use_preempted_handler:
movea.l preempted_handler_address(pc), a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER.
not_esc_sequence:
move.b #1, initialization_flag
move.w 4(a0), character ; Store character for printer.
write_character_to_screen:
move.w 4(a0), -(sp) ; Push character onto stack.
move.w #2, -(sp) ; Device = screen.
move.w #3, -(sp) ; Function = bconout = BIOS $3.
trap #13
addq.l #6, sp
ascii_code_test: ; Filter out undesirable codes.
move.w character(pc), d0
cmpi.w #$1B, d0
bgt.s write_character_to_printer
cmpi.w #$A, d0
beq.s write_character_to_printer
cmpi.w #$D, d0
bne.s undesirable_ascii
write_character_to_printer:
move.w d0, -(sp) ; Push character onto stack.
move.w #0, -(sp) ; Device = printer.
move.w #3, -(sp)
trap #13
addq.l #6, sp
undesirable_ascii:
move.b #0, initialization_flag
rte
not_screen:
not_bconout_call:
skip_initialization:
movea.l preempted_handler_address(pc), a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER
bss
character: ds.w 1
preempted_handler_address: ds.l 1
esc_sequence_flag: ds.b 1
initialization_flag: ds.b 1
align
ds.l 48 ; Stack
stack: ds.l 1 ; Address of stack.
program_end: ds.l 0
end
The intricacy of many exception handlers is greater
than that of programs 51 and 52. However, I want the
example given to help you to realize that complexity is not
an exception handler requirement. Once you are able to
handle even such rudimentary handlers, you will be able to
force the computer to perform at your command. In the next
chapter, I will present a more powerful handler, after the
discussions about some of the microprocessor's peripheral
hardware.
I will now introduce a group of programs that are
designed to provide information which is indispensable to
the effort required when designing efficient programs,
especially programs that function as desk accessories and
exception handlers. Each program in this group reports
alterations made to the contents of the 68000 data and
address registers during an experiment. While the results
of each experiment can only be considered valid for the
conditions imposed during the experiment, the methods used
can be applied to any set of conditions that you may wish to
impose on your own experiments.
Data and Address Register Corruption
All applications which are executed on the Atari ST
conduct most of their operations via the 68000 data and
address registers. When several applications are resident
and sharing system resources, the contents of the registers
that are being used by one application must not be corrupted
by another beyond the expectations of the first application.
Furthermore, when designing our applications, we must be
cognizant of the extent to which system calls inflict
register corruption, so that we can avoid the consequences
of using contaminated registers.
I have developed a naming pattern for a new group of
programs, which is to be used in a series of register
contamination experiments, that is slightly different than
that which I have been using. Each program in the group has
the appellation REG_TSTn, where n is a digit. Some of the
experiments to be performed require the execution of more
than one program, and at least one experiment requires the
installation of a previously introduced program in the root
directory of a specific hard disk partition. If no hard
disk is available, the path used in that experiment must be
edited so that a new directory path is specified.
This group of programs has been designed to provide the
following information:
1. The extent to which the registers being used
in a desk accessory are protected from corruption
by the operating system, and the
effect that such accessories have on the
registers of an executing application.
2. The extent to which system exception handlers alter
the registers of an executing application.
3. The extent to which the registers that are used in a
LSR program that is not a desk accessory are
protected from corruption by the operating
system, and the effect that such programs have on
the registers of an executing application.
In a later chapter, I will present similar program which
determine the extent to which AES and VDI arrays are
corrupted by AES and VDI calls.
Program 53. Experiment 1: System trap #13, BIOS
function #$8 register contamination.
; PROGRAM NAME: REG_TST1.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode and save assembled program with a TOS suffix.
; Program Function:
; This program is one of a group that is used to determine the extent of
; register alteration by exception handlers and to test the volatility
; of desk accessory registers.
; EXPERIMENT 1:
; This program will call a trap #13 handler. Before the call, values
; that were stored in a register array while the program was being loaded
; into ram are placed in each register.
; Immediately after the call, the content of each register is stored in
; a second register array. Then the content of each register, as it was
; before the call, is printed. Next, the content of the after_call array
; is compared to the content of the before-call array, and the values in
; the after-call array are printed; however, register values in the
; after-call array which do not match the before-call value for each
; corresponding register are printed in reverse colors on the video screen.
; In addition, if the trap #13 handler which redirects screen output to
; to both the printer and the screen is resident, and if the printer is on,
; then a hardcopy will be obtained, upon which the non-matching after_call
; values will be printed in boldface. To place the redirecting trap #13
; handler in residence, execute PRG_6JC.TOS or PRG_6KC.TOS.
; If you are going to execute this program with the printer on, the results
; you obtain will probably more closely match those I obtained if you turn on
; your printer before booting the ST and executing the redirecting handler
; mentioned above.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a0
movea.l 4(a7), a1
suba.l a1, a0
return_memory: ; Return unused memory to operating system.
move.l a0, -(sp) ; Store total program length in stack.
move.l a1, -(sp) ; Store basepage address in stack.
move.w d0, -(sp) ; Dummy value, can be anything.
move.w #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; GEMDOS call.
adda.l #$C, sp ; Reset stack pointer to top of stack.
place_values_in_registers:
movem.l registers_before(pc), d0-d7/a0-a6
lea stack(pc), a7
move.l a7, reg_A7_before
make_trap_call: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
store_after_call_contents:
movem.l d0-d7/a0-a7, registers_after
print_before_call_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea before_msg(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #24, a3
lea hexadecimal(pc), a0
bsr print_string ; Print register content.
bsr print_newline
dbra d4, print_register
bsr print_newline
_wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
print_after_call_contents:
lea after_msg(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l (a4)+, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
movea.l a3, a0
bsr print_string
adda.l #24, a3
lea hexadecimal(pc), a0
bsr print_string
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, compare_and_print
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp)
trap #1
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proven so it must be cleared before use.
discard_leading_zeroes:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
cmpi.b #0, d0 ; Is it zero?
bne.s store_digit
dbra d2, discard_leading_zeroes
move.b 0(a1,d0.w), (a0)+ ; If we get here, last nibble has been
; processed, so store digit in buffer.
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
continue:
rol.l #4, d3 ; Rotate most significant nibble.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, continue ; Continue looping until D2 = -1.
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 1: System Trap #13 Corruption',$D,$A
dc.b ' Function = BIOS $8',$D,$A,$A,0
before_msg: dc.b ' Register contents before call',$D,$A,0
after_msg: dc.b ' Register contents after call',$D,$A,0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0
dc.b ' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0
dc.b ' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0
dc.b ' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0
dc.b ' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0
dc.b ' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0
dc.b ' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0
dc.b ' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0
dc.b ' Add Reg 7: $',0
align
;bss MAJOR NOTE: Do not use the bss assembler op here. If you do, the
; value $44415441 will NOT be loaded into the elements
; of the registers_before array.
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15, $44415441
reg_A7_before: ds.l 1
registers_after: ds.l 16 ; Array for register contents after call.
ds.l 48 ; Program stack.
stack: ds.l 1 ; Address of program stack.
program_end: ds.l 0
end
Figure 8.7. Program 53 Execution Results.
Figure 8.7A. Register Contents before call.
Figure 8.7B. Register contents after call.
A register preservation guide can be found on page 3-2
of the Peel book. There, it implies that registers d0-
d2/a0-a2 are subject to contamination during the execution
of trap #13 (BIOS) calls. This guide does not provide any
suggestion concerning possible function differentiations.
From the information given, we can only assume that all BIOS
functions corrupt with equal impudence. Page 152 of the
Internals book (under the heading 3.2 The BIOS Functions)
provides the same information.
The results of our first experiment, however, indicate
that only the registers d0/a0-a1 are contaminated during a
BIOS $8 (bconout) call. But there is one other parameter
imposed on the experiment. The device specified for the
function call was the printer. The results might not be the
same if another device were specified. Yet it is not my
intent to explore the results of every conceivable system
call. That, itself, would be material for a book. It is
the method of obtaining experimental results and the
contradiction between reference material and experimental
evidence that I wish to bring to your attention.
I prepared program 53 in as straight forward a manner
as possible, in order to alleviate your comprehension of the
algorithms involved. The program is rather long, when
compared to its accomplishments, and the manner in which the
results are displayed is not harmonious. Therefore, the
rest of the programs in the group are shorter, referring to
program 23 when necessary, and they present their
information in a more compact display. Program 54 provides
the same data as does program 53.
Program 54. Experiment 1: System trap #13, BIOS function #$8
register contamination.
; PROGRAM NAME: REG_TST2.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode and save the assembled program with a TOS suffix.
; Program Function:
; Same as REG_TST1, except this one is designed to print the before and
; after values side by side. Also, the leading zero portion has been
; removed from the binary to hexadecimal conversion routine, so that
; numerical alignment is achieved on the output.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a0
movea.l 4(a7), a1
suba.l a1, a0
return_memory: ; Return unused memory to operating system.
move.l a0, -(sp) ; Store total program length in stack.
move.l a1, -(sp) ; Store basepage address in stack.
move.w d0, -(sp) ; Dummy value, can be anything.
move.w #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; GEMDOS call.
adda.l #$C, sp ; Reset stack pointer to top of stack.
place_values_in_registers:
movem.l registers_before(pc), d0-d7/a0-a6
lea stack(pc), a7
move.l a7, reg_A7_before
make_trap_call: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
store_after_call_contents:
movem.l d0-d7/a0-a7, registers_after
; The following algorithm prints the before value for a register, then,
; on the same line, it prints the after value. This provides a more
; compact display, and one in which it is easier to compare the before
; and after values.
print_all_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea heading(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
move.l d3, d5 ; Save for comparison with after-value.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #16, a3
lea hexadecimal(pc), a0
bsr print_string ; Print before-call register content.
lea space(pc), a0
bsr print_string
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l d5, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
bsr print_hex_sign
lea hexadecimal(pc), a0
bsr print_string ; Print after-call value.
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, print_register
bsr print_newline
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp)
trap #1
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zero portion has been removed from this routine.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proved, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
print_hex_sign:
move.w #$24, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 1: System Trap #13 Corruption',$D,$A
dc.b ' Function = BIOS $8',$D,$A,$A,0
heading: dc.b ' Before Call After Call',$D,$A,0
space: dc.b ' ',0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0,' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0,' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0,' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0,' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0,' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0,' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0,' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0,' Add Reg 7: $',0
align
;bss MAJOR NOTE: Do not use the bss assembler op here. If you do, the
; value $44415441 will NOT be loaded into the elements
; of the registers_before array.
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15, $44415441 ; Store this value in each before
reg_A7_before: ds.l 1 ; array element during loading.
registers_after: ds.l 16 ; Array for register contents after call.
ds.l 48 ; Program stack.
stack: ds.l 1 ; Address of program stack.
program_end: ds.l 0
end
Figure 8.8. Program 54 Execution Results.
Program 55. Experiment 2: System trap #1, GEMDOS
function #$4B register contamination.
; PROGRAM NAME: REG_TST3.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode. Save the assembled program with a TOS suffix.
; Program Function:
; Similar to REG_TST2, except this one calls a system trap #1 handler.
; MAJOR NOTES:
; 1. PRG_6KC.TOS must be in the root directory of partition D of your
; hard disk, if you are to execute the program as prepared. But
; you can alter the variable "program_name" to suit your
; requirements.
; 2. Since this program loads and executes PRG_6KC.TOS, which installs
; a custom trap #13 handler, you should execute this program only
; once, otherwise each letter sent to the video screen will be
; printed twice on the printer.
; 3. PRG_6KC.TOS functions best when the printer is on line during
; system boot.
; 4. After executing this program, you should reset your system,
; preferably with a cold start, to clear the custom trap handler
; from RAM, unless you want it there for other purposes.
; 5. Do not execute this program from within AssemPro.
; EXPERIMENT 2:
; This program will call a system trap #1 handler. Since some
; trap #1 handlers call trap #13 handlers, as with the previous program,
; we specify a handler which is not processed by the custom trap handler
; of PRG_6KC.TOS, which may be resident. We do this because we want to
; avoid any possible complications imposed by a custom trap handler at
; this time.
; GEMDOS function $4B (aka p_exec) was chosen for the experiment.
; This function is discussed in the Internals book. Mode 0 is used in
; this program to cause the system to load and execute PRG_6KC.TOS.
; A couple of warnings: you would not want to execute this program if
; PRG_6KC.TOS were already resident. That program contains no provisions
; for determining its residency, therefore, it is possible for you to
; execute it repeatedly, thereby obtaining multiple copies of it in ram.
; When using GEMDOS function $4B to execute programs from within an
; executing application, you must set up both of the programs so that
; neither of them consumes all ram. That is, you must use one of
; the functions which releases excess memory back to the operating system.
; Note that this program uses GEMDOS function $4A to return unused
; memory to the operating system, while PRG_6KC.TOS uses GEMDOS function
; $31. In addition, of course, both programs require a legitimate
; termination algorithm. That is, they must relinquish processor control.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a0
movea.l 4(a7), a1
suba.l a1, a0
return_memory: ; Return unused memory to operating system.
move.l a0, -(sp) ; Store total program length in stack.
move.l a1, -(sp) ; Store basepage address in stack.
move.w d0, -(sp) ; Dummy value, can be anything.
move.w #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; GEMDOS call.
adda.l #$C, sp ; Reset stack pointer to top of stack.
get_printer_status: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
place_values_in_registers:
movem.l registers_before(pc), d0-d7/a0-a6
lea stack(pc), a7
move.l a7, reg_A7_before
make_the_trap_call: ; This is Experiment 2.
pea environmental_string(pc)
pea command_line(pc)
pea program_name(pc) ; Must be the complete path to the file.
move.w #0, -(sp)
move.w #$4B, -(sp) ; Function = GEMDOS $4B = p_exec.
trap #1 ; Load and execute program PRG_6KC.TOS.
add.l #$10, sp
store_after_call_contents:
movem.l d0-d7/a0-a7, registers_after
print_all_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea heading(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
move.l d3, d5 ; Save for comparison with after-value.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #16, a3
lea hexadecimal(pc), a0
bsr print_string ; Print before-call register content.
lea space(pc), a0
bsr print_string
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l d5, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
bsr print_hex_sign
lea hexadecimal(pc), a0
bsr print_string ; Print after-call value.
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, print_register
bsr print_newline
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp)
trap #1
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zero portion has been removed from this routine.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proved, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
print_hex_sign:
move.w #$24, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 2: System Trap #1 Corruption',$D,$A
dc.b ' Function = GEMDOS $4B',$D,$A,$A,0
heading: dc.b ' Before Call After Call',$D,$A,0
space: dc.b ' ',0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0,' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0,' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0,' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0,' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0,' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0,' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0,' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0,' Add Reg 7: $',0
program_name: dc.b 'D:\PRG_6KC.TOS',0
environmental_string: dc.b 0
command_line: dc.b 0
align
;bss MAJOR NOTE: Do not use the bss assembler op here. If you do, the
; value $44415441 will NOT be loaded into the elements
; of the registers_before array.
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15, $44415441 ; Store this value in each before
reg_A7_before: ds.l 1 ; array element during loading.
registers_after: ds.l 16 ; Array for register contents after call.
;MAJOR NOTE: Note that this program requires a larger stack than does
; REG_TST1 and REG_TST2. If the stack size is left at 48 words
; here, 4 bombs will appear when this program is executed, just
; it attempts to print to the printer.
ds.l 96 ; Program stack.
stack: ds.l 1 ; Address of program stack.
program_end: ds.l 0
end
Figure 8.9. Program 55 Execution Results.
The results of experiment 2 agree with the assessment
given in the Internals book, under the heading 3.1 The
GEMDOS, on page 106. They do not even approach congruency
with the information given in the Peel book on page 3-2.
Performing these experiments will give us the verified data
that we need in order to realize maximum speed via judicious
register usage.
Program 56. Experiment 3: System trap #14, XBIOS function
#$2 register contamination.
; PROGRAM NAME: REG_TST4.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode. Save the assembled program with a TOS suffix.
; Program Function:
; Similar to REG_TST2, except this one calls a system trap #14 handler.
; EXPERIMENT 3:
; This program will call a system trap #14 handler.
; XBIOS function $2 (aka physbase) was chosen for the experiment.
; This function is discussed in the Internals book, however, at the
; site of the function description, no mention is made of the register
; in which the desired address is returned. We will be able to see the
; address in one of the registers when this program is executed.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a0
movea.l 4(a7), a1
suba.l a1, a0
return_memory: ; Return unused memory to operating system.
move.l a0, -(sp) ; Store total program length in stack.
move.l a1, -(sp) ; Store basepage address in stack.
move.w d0, -(sp) ; Dummy value, can be anything.
move.w #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; GEMDOS call.
adda.l #$C, sp ; Reset stack pointer to top of stack.
get_printer_status: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
place_values_in_registers:
movem.l registers_before(pc), d0-d7/a0-a6
lea stack(pc), a7
move.l a7, reg_A7_before
make_the_trap_call: ; This is Experiment 3.
; We're going to use XBIOS function $2 to determine the register in
; which the ram address of the physical video screen is returned. I
; already know that it is returned in D0, but this program will verify
; that information.
get_system_screen_address:
move #2, -(sp) ; Function = XBIOS $2 = physbase.
trap #14 ;
addq.l #2, sp
store_after_call_contents:
movem.l d0-d7/a0-a7, registers_after
print_all_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea heading(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
move.l d3, d5 ; Save for comparison with after-value.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #16, a3
lea hexadecimal(pc), a0
bsr print_string ; Print before-call register content.
lea space(pc), a0
bsr print_string
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l d5, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
bsr print_hex_sign
lea hexadecimal(pc), a0
bsr print_string ; Print after-call value.
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, print_register
bsr print_newline
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp)
trap #1
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zero portion has been removed from this routine.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proved, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
print_hex_sign:
move.w #$24, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 3: System Trap #14 Corruption',$D,$A
dc.b ' Function = XBIOS $2',$D,$A,$A,0
heading: dc.b ' Before Call After Call',$D,$A,0
space: dc.b ' ',0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0,' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0,' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0,' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0,' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0,' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0,' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0,' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0,' Add Reg 7: $',0
align
;bss MAJOR NOTE: Do not use the bss assembler op here. If you do, the
; value $44415441 will NOT be loaded into the elements
; of the registers_before array.
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15, $44415441 ; Store this value in each before
reg_A7_before: ds.l 1 ; array element during loading.
registers_after: ds.l 16 ; Array for register contents after call.
ds.l 48 ; Program stack.
stack: ds.l 1 ; Address of program stack.
program_end: ds.l 0
end
Figure 8.10. Program 56 Execution Results.
The Internals book does not provide register
contamination information for XBIOS calls. The Peel book
implies that registers d0-d2/a0-a2 are subject to
contamination during such system calls. The results of
experiment 3 suggest that, at least when the function
specified is XBIOS $2, only registers d0/a0-a1 are corrupted
during the call.
Program 57. Experiment 4: Desk accessory register corruption
by BIOS function $8.
; PROGRAM NAME: REG_TST5.S
; VERSION: 1.001
; Desk Accessory (No trap handler; no interrupt handler).
; Assembly Instructions:
; Assemble in Relocatable mode and save with a PRG extension. From the
; desktop, click on REG_TST5.PRG, click on Show Info under the File menu,
; backspace over the PRG on the name line, then type in ACC, thereby
; changing the object code file's name to REG_TST5.ACC. Finally, place the
; file, REG_TST5.ACC in the root directory of your boot disk. During the
; next power-up cycle, the program will be installed in memory as a desk
; accessory.
; Program Function: EXPERIMENT 4.
; Similar to REG_TST2, but this one is a desk accessory. It does not
; call a trap handler, as does the first 4 programs of the group.
; Furthermore, it does not copy values from the registers_before array
; into the registers. Instead, processor control is relinquished via the
; AES $17 (aka evnt_mesag) function, with values in the registers that are
; placed there during the desk accessory initialization procedure.
; You should exercise this experiment by selecting REG_TST5 from within
; a running application which allows access to the Desk Accessory menu;
; you can do this from within the AssemPro editor. Or you can simply
; select REG_TST5 as an accessory from the desktop.
; First, you can convince yourself that all registers are altered by
; executing REG_TST1, REG_TST2, or REG_TST3 from within AssemPro. Then
; select REG_TST5 from the Accessory menu.
; When this program is selected thus, a message will be sent to the
; program by the operating system, thereby forcing the message handling
; algorithm to execute. The message handler will print the register
; contents, as does the other programs in this group.
; Since this program does not produce its own screen, its output will
; obliterate the screen of the application from which it is selected. To
; recover, press the Return key to force this program to relinquish
; processor control, then move the mouse arrow around, near the spot where
; the applications menu line should be. When you do this, you will see
; items appear as they normally would. You can then select any function
; that will cause the screen to be drawn, such as Debugger, if you are
; working within AssemPro. Or you may be able to find the window close box
; in the upper left hand corner of the window. If you can't get the hang
; of it, just reset the computer manually (a cold reset is best most often).
; The knowledge we wish to gain from the results of this experiment is
; an answer to the question "Does the operating system protect desk
; accessory registers from corruption while the accessory is waiting to be
; activated?". That is, if we store the address of the control array in
; register a4 during the accessory initialization procedure, for example,
; will that address still be in register a4 when the accessory is activated.
; If the desk accessory registers are protected from corruption while
; it is waiting for a message, then we can store values in the registers
; during the initialization procedure and use them in the message handler,
; without taking the time to store the values during the execution of the
; message handler. That will permit faster message handler response.
program_start:
lea stack(pc), a7 ; A7 points to program's stack address.
move.l a7, reg_A7_before ; Store stack address in register array.
lea aes_pb(pc), a3 ; aes_pb = AES parameter block.
lea control(pc), a4 ; A4 is pointer for array "control".
move.w #$C8, d3 ; *** AES call number in D3.
; I am interested in the volatility of the above registers. Do they
; maintain their values until I alter them in this program, or are they
; corrupted when the desk accessory is entered via the evnt_mesag
; function?
initialize_application:
move.w #$A, (a4) ; Function = appl_init = AES $A.
move.w #1, 4(a4) ; Return one int_out parameter.
move.l a3, d1 ; A3 contains address of aes parameter block.
move.w d3, d0 ; D3 contains AES call number.
trap #2 ; apid returned in int_out[0] and global[2].
menu_installation:
move.w #$23, (a4) ; Function = menu_register = AES $23.
move.w #1, 2(a4) ; Pass one int_in parameter.
move.w #1, 6(a4) ; Pass one addr_in parameter.
move.w int_out(pc), int_in ; Application identification to int_in[0].
move.l #menu_text, addr_in ; Menu text address to addr_in[0].
move.l a3, d1 ; Address of aes parameter block to D1.
move.w d3, d0 ; D3 contains AES call number.
trap #2 ; Menu identification number returned
; in int_out[0].
move.w int_out, d4 ; Store menu identification number in D4.
; MAIN ACCESSORY LOOP
wait_for_message: ; Relinquish processor control.
move.w #$17, (a4) ; Function = evnt_mesag = AES $17.
move.w #0, 2(a4)
move.l #message, addr_in ; Address of message array to addr_in.
store_register_contents:
movem.l d0-d7/a0-a6, registers_before
move.l a3, d1 ; Address of aes parameter block to D1.
move.w d3, d0 ; AES call number to D0.
trap #2 ; Message received is placed in array
; "message".
; Enters here only when a message is received.
message_handler: ; Entrance point when message is received.
cmpi.w #$28, message ; Compare AC OPEN code with message[0].
bne.s wait_for_message ; Execute the evnt_mesag function.
move.w message+8, d0 ; id_selected (message[4]) = menu ID of
; item user selected.
cmp.w d4, d0 ; Was this application selected.
bne.s wait_for_message ; Execute the evnt_mesag function.
store_after_entrance_register_contents:
movem.l d0-d7/a0-a7, registers_after
get_printer_status: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
; The following algorithm prints the before value for a register, then,
; on the same line, it prints the after value.
print_all_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea heading(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
move.l d3, d5 ; Save for comparison with after-value.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #16, a3
lea hexadecimal(pc), a0
bsr print_string ; Print before-call register content.
lea space(pc), a0
bsr print_string
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l d5, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
bsr print_hex_sign
lea hexadecimal(pc), a0
bsr print_string ; Print after-call value.
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, print_register
bsr print_newline
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
; Now we have to restore the desk accessory registers because our printing
; algorithm has corrupted them. Also, we have learned that the system
; corrupts our stack pointer. We restore all registers, except A7, to the
; values they had when the accessory was activated. Register A7 retains
; the original stack address. During successive activations of the
; accessory, we will be able to see any changes.
movem.l registers_after(pc), d0-d7/a0-a6
bra wait_for_message
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zero portion has been removed from this routine.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proved, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
print_hex_sign:
move.w #$24, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 4: Desk Accessory Corruption By',$D,$A
dc.b ' Function = BIOS $8',$D,$A,$A,0
heading: dc.b ' Before Call After Call',$D,$A,0
space: dc.b ' ',0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0,' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0,' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0,' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0,' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0,' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0,' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0,' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0,' Add Reg 7: $',0
menu_text: dc.b ' REG_TST5 ',0
align ; Align storage on a word boundary.
;
; AES PARAMETER BLOCK
;
aes_pb: dc.l control,global,int_in,int_out,addr_in,addr_out
;
; AES CONTROL TABLE
;
bss
control: ds.w 5
global: ds.w 3
ds.l 6
int_in: ds.w 1 ; Input parameter.
int_out: ds.w 1 ; Output parameter.
addr_in: ds.l 1 ; Input address.
addr_out: ds.l 1 ; Output address.
;
; OTHER VARIABLES
;
message: ds.w 8
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15
reg_A7_before: ds.l 1 ; Keep stack separate and constant.
registers_after: ds.l 16 ; Array for register contents after call.
ds.l 48
stack: ds.l 0
program_end: ds.l 0
end
Figure 8.11. Program 57 Execution Results.
The results obtain via experiment 4 are significant.
It is easy to see that the ST's limited multi-tasking
environment includes the code required to protect accessory
registers from contamination. Therefore, when the accessory
is activated, the values placed in registers before the
evnt_mesag function is executed are still valid. The AES
functions used in program 57 are explained in the Abacus GEM
and the COMPUTE! AES books.
Program 58. Experiment 5A: Custom trap #13 handler
corruption of calling application registers, and
contamination of handler registers by the calling
application.
; PROGRAM NAME: REG_TST6.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode and save with a TOS extension.
; Program Function: EXPERIMENT 5A.
; Similar to REG_TST2, but this one installs a custom trap #13 handler.
; The handler intercepts all BIOS calls, passing all but one custom function
; on to the system trap #13 handler. Since there is no BIOS function $C, I
; use that number for the function to be processed by the custom handler.
; In this program, I do not copy values from the registers_before
; array into the registers. Instead, processor control is relinquished,
; via GEMDOS function $31 (aka ptermres), with values in the registers that
; are placed there during the load and stay resident (LSR) initialization
; procedure.
; Execute this program from the desktop. Then execute program REG_TST7,
; the program that is used to exercise this program.
; As with the desk accessory, REG_TST5, you may want to execute one of
; the first three programs of the group from within AssemPro to alter all
; registers. Then, from within AssemPro, you can execute REG_TST7, which
; calls the custom BIOS function $C.
; Since this program does not produce its own screen, its output will
; obliterate the screen of the application from which it is selected. To
; recover, press the Return key to force this program to relinquish
; processor control, then move the mouse arrow around, near the spot where
; the applications menu line should be. When you do this, you will see
; items appear as they normally would. You can then select any function
; that will cause the screen to be drawn, such as Debugger, if you are
; working within AssemPro. Or you may be able to find the window close box
; in the upper left hand corner of the window. If you can't get the hang
; of it, just reset the computer manually (a cold reset is best most often).
; The knowledge we wish to gain from the results of this experiment is
; an answer to the question "does the operating system protect the registers
; of custom traps handlers that are installed by a program that is resident
; via the ptermres function. This is the same kind of data we wanted to
; obtain with REG_TST5 for desk accessories.
; If the trap handler registers are protected from corruption, then we
; can store values in the registers during the initialization procedure and
; use them in the trap handler, without taking the time to store the values
; during trap interceptions. That would permit faster trap handler
; response.
program_start: ; Compute program size and retain result.
lea program_end(pc), a3
movea.l 4(a7), a4
suba.l a4, a3 ; Yields size of memory that must remain
; resident.
load_stack_address:
lea stack(pc), a7
install_new_trap_13_vector:
pea custom_trap_handler(pc) ; Push new trap handler address onto stack.
move.w #$2D, -(sp) ; Trap 13 vector number.
move.w #5, -(sp) ; Function = setexec = BIOS $5.
trap #13 ; Current trap handler vector returned in D0.
addq.l #8, sp
move.l d0, preempted_handler_address
store_register_contents:
movem.l d0-d7/a0-a7, registers_before
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Size of memory to remain resident.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
custom_trap_handler:
move.l usp(pc), a0 ; Load address of current top of user stack.
get_processor_status:
btst #5, (sp) ; User mode test.
beq.s was_user_mode ; No adjustment is necessary if the
; processor was in user mode.
movea.l sp, a0 ; Load current top of supervisor stack.
addq.l #6, a0 ; Adjust SSP for user mode type data access.
was_user_mode: ; Processing for either mode follows.
cmpi.w #$C, (a0) ; Calling the custom BIOS function?
bne not_desired_call
store_after_entrance_register_contents:
movem.l d0-d7/a0-a7, registers_after
get_printer_status: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
print_all_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea heading(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
move.l d3, d5 ; Save for comparison with after-value.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #16, a3
lea hexadecimal(pc), a0
bsr print_string ; Print before-call register content.
lea space(pc), a0
bsr print_string
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l d5, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
bsr print_hex_sign
lea hexadecimal(pc), a0
bsr print_string ; Print after-call value.
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, print_register
bsr print_newline
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
; Now we have to restore the LSR's registers because our printing
; algorithm has corrupted them. Of course, we will want to know
; if this values are received by the calling source. If they are, then
; the trap handler will have corrupted the calling source's registers.
movem.l registers_before(pc), d0-d7/a0-a6
rte
not_desired_call:
movea.l preempted_handler_address(pc), a0
jmp (a0) ; JUMP TO PREEMPTED TRAP #13 HANDLER
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zero portion has been removed from this routine.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proved, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
print_hex_sign:
move.w #$24, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 5A: System Trap #13 Corruption',$D,$A
dc.b ' Function = BIOS $C = Custom',$D,$A,$A,0
heading: dc.b ' Before Call After Call',$D,$A,0
space: dc.b ' ',0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0,' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0,' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0,' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0,' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0,' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0,' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0,' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0,' Add Reg 7: $',0
align
preempted_handler_address: ds.l 1
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15, 0 ; Store this value in each before
reg_A7_before: ds.l 1 ; array element during loading.
registers_after: ds.l 16 ; Array for register contents after call.
ds.l 48 ; Stack
stack: ds.l 1 ; Address of stack.
program_end: ds.l 0
end
Figure 8.12. Program 58 Execution Results.
Program 59. Experiment 5B: Custom trap #13 handler
corruption of calling application registers, and
contamination of handler registers by the calling
application.
; PROGRAM NAME: REG_TST7.S
; VERSION: 1.001
; Assembly Instructions:
; Assemble in Relocatable mode and save with a TOS extension.
; Program Function: EXPERIMENT 5B.
; Similar to REG_TST2, but this one calls a custom trap #13 handler.
; The handler intercepts all BIOS calls, passing all but one custom function
; on to the system trap #13 handler. Since there is no BIOS function $C, I
; use that number for the function to be processed by the custom handler.
; Execution Instructions:
; Execute program REG_TST6 from the desktop. Then execute this program
; from the desktop or from within AssemPro.
; This program will call the custom trap #13 handler that must be
; already resident in ram, having been established by REG_TST6. Before the
; call, values that were stored in a register array, while the program was
; being loaded into ram, are placed in each register. The custom trap
; handler will alter all registers. We want to see if those alterations
; are passed back to this program.
; The custom handler will print its before and after register contents,
; the relinquish processor control back to this program.
; Immediately after the call, the content of each register is stored in
; a second register array. Then the content of each register, as it was
; before the call, is printed. Next, the content of the after_call array
; is compared to the content of the before-call array, and the values in
; the after-call array are printed; however, register values in the
; after-call array which do not match the before-call value for each
; corresponding register are printed in reverse colors on the video screen.
program_start: ; Calculate program size and retain result.
lea program_end(pc), a0
movea.l 4(a7), a1
suba.l a1, a0
return_memory: ; Return unused memory to operating system.
move.l a0, -(sp) ; Store total program length in stack.
move.l a1, -(sp) ; Store basepage address in stack.
move.w d0, -(sp) ; Dummy value, can be anything.
move.w #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; GEMDOS call.
adda.l #$C, sp ; Reset stack pointer to top of stack.
get_printer_status: ; Get and save printer status.
move.w #0, -(sp) ; Device = printer.
move.w #8, -(sp) ; Function = BIOS $8 = bcostat.
trap #13 ; If printer is on, -1 will be returned
addq.l #4, sp ; in D0, 0 will be returned otherwise.
move.b d0, printer_status
place_values_in_registers:
movem.l registers_before(pc), d0-d7/a0-a6
lea stack(pc), a7
move.l a7, reg_A7_before
make_trap_call:
move.l #0, -(sp)
move.w #$C, -(sp)
trap #13
addq.l #6, sp
store_after_call_contents:
movem.l d0-d7/a0-a7, registers_after
; The following algorithm prints the before value for a register, then,
; on the same line, it prints the after value. This provides a more
; compact display, and one in which it is easier to compare the before
; and after values.
print_all_contents:
bsr print_newline
lea experiment_head(pc), a0
bsr print_string
lea heading(pc), a0
bsr print_string
lea register_id(pc), a3
lea registers_before(pc), a4
lea registers_after(pc), a5
move.l #15, d4
print_register:
move.l (a4)+, d3 ; Convert content from binary to hex.
move.l d3, d5 ; Save for comparison with after-value.
bsr bin_to_hex
movea.l a3, a0
bsr print_string ; Print register number.
add.l #16, a3
lea hexadecimal(pc), a0
bsr print_string ; Print before-call register content.
lea space(pc), a0
bsr print_string
compare_and_print:
move.l (a5)+, d3 ; Load after_call value.
cmp.l d5, d3 ; Compare before_call to after_call.
beq.s no_change ; Branch if no change occurred.
tst.b printer_status ; Find out if printer is on or off.
beq.s printer_off
bsr bold_on ; Turn on printer bold print.
printer_off:
bsr reverse_on ; Turn on video reverse print.
no_change:
bsr bin_to_hex
bsr print_hex_sign
lea hexadecimal(pc), a0
bsr print_string ; Print after-call value.
bsr print_newline
tst.b printer_status ; Find out if printer is on or off.
beq.s _printer_off
bsr bold_off ; Turn printer bold print off.
_printer_off:
bsr reverse_off ; Turn video reverse print off.
dbra d4, print_register
bsr print_newline
wait_for_keypress:
move.w #8, -(sp)
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp)
trap #1
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D3. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zero portion has been removed from this routine.
bin_to_hex: ; Converts a 32-bit binary number in D3 to
; ASCII hexadecimal.
lea hexadecimal(pc), a0 ; A0 is pointer to array "hexadecimal".
lea hex_table(pc), a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's what we have
; proved, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d3 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d3, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
print_string: ; Expects address of string to be in A0.
move.l a0, -(sp) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline(pc) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
print_hex_sign:
move.w #$24, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_on:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$47, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
bold_off:
move.w #$1B, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$48, -(sp) ; Your printer's code may be different.
move.w #0, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_on:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$70, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
reverse_off:
move.w #$1B, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
move.w #$71, -(sp)
move.w #2, -(sp)
move.w #3, -(sp)
trap #13
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
experiment_head: dc.b 'EXPERIMENT 5B: Custom Trap #13 Corruption',$D,$A
dc.b ' Function = BIOS $C = Custom',$D,$A,$A,0
heading: dc.b ' Before Call After Call',$D,$A,0
space: dc.b ' ',0
printer_status: dc.b 0
register_id: dc.b ' Data Reg 0: $',0,' Data Reg 1: $',0
dc.b ' Data Reg 2: $',0,' Data Reg 3: $',0
dc.b ' Data Reg 4: $',0,' Data Reg 5: $',0
dc.b ' Data Reg 6: $',0,' Data Reg 7: $',0
dc.b ' Add Reg 0: $',0,' Add Reg 1: $',0
dc.b ' Add Reg 2: $',0,' Add Reg 3: $',0
dc.b ' Add Reg 4: $',0,' Add Reg 5: $',0
dc.b ' Add Reg 6: $',0,' Add Reg 7: $',0
align
;bss MAJOR NOTE: Do not use the bss assembler op here. If you do, the
; value $44415441 will NOT be loaded into the elements
; of the registers_before array.
trap_vector: ds.l 1 ; Store for custom trap handler's vector.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
registers_before: ds.l 15, $44415441 ; Store this value in each before
reg_A7_before: ds.l 1 ; array element during loading.
registers_after: ds.l 16 ; Array for register contents after call.
ds.l 48 ; Program stack.
stack: ds.l 1 ; Address of program stack.
program_end: ds.l 0
end
Figure 8.13. Program 59 Execution Results.
Conclusion
We have begun our journey into the belly of the beast.
Learning to redirect the activities of the operating system
is one of the more rewarding programming experiences. In
the next chapter, we will become more involved with this
process, and I am sure that you will be delighted with the
examples I have chosen. However, I must impress you, not
with the expanse of my knowledge, but, rather, with its
limitations.
I encourage you to perform your own experiments, using
reference material when you can, exploring the unknown when
you can't. The ST specific magazines are an especially
valuable source of fresh ideas and information. In
addition, the AssemPro debugger is a valuable tool that you
can use to explore programs written by others. Furthermore,
do not neglect the other programming languages as sources
for ideas. Remember, no matter how much information I am
able to pass on to you, it is merely a droplet.