COURS 7.TXT

From Atari Wiki
Revision as of 22:37, 16 December 2023 by Olivier.jan (talk | contribs)
Jump to navigation Jump to search
   ******************************************************************
   *                                                                *
   *              68000 ASSEMBLER COURSE ON ATARI ST                *
   *                                                                *
   *                    by The Fierce Rabbit (from 44E)             *
   *                                                                *
   *                            Lesson number 7                     *
   *                                                                *
   ******************************************************************

   We now approach the seventh lesson in the series. The entire 
   course is in 2 series (well, at the time I'm typing these lines, 
   that's what is planned!), this one is the last of the first!

   At the end of this one and if you have very carefully and very 
   scrupulously followed the previous 6 lessons, you should be 
   able to display images, save files etc...

   But first let's go back to our stack and the question from the 
   previous lesson. Did you find the error?

   Well look at the value of A7 before stacking $12345678 and
   $23456, and compare it to the value at the end of the program. Alas!
   it is not the same! Normal, if we count the stackings and 
   the unstackings, we realize that we have stacked 8 
   bytes more than we have unstacked. Indeed, since we have
   retrieved our 2 numbers by first saving A7 in A0,
   we did not touch A7 at the time of recovery.
   Fortunately, though, because the routine return would have been modified!

   Based on the principle of unstacking in reverse order, we must
   therefore correct the stack once back from the subroutine. As we
   have stacked by doing -(SP) we must add so that the stack becomes
   as before. Having stacked 2 numbers of 4 bytes each, 
   we must add 8 bytes to the stack address to correct it
   as it should be. We have already seen how to increase an 
   address, with ADDA.

   It is therefore appropriate to add right after the line BSR AJOUTE an
   addition on SP, by doing ADDA.L #8,SP (which reads ADD ADDRESS 
   LONG 8 STACK POINTER)

   A call to a subroutine by passing parameters on the
   stack will therefore typically be of the kind:

          MOVE.W     #$1452,-(SP)
          MOVE.L     #$54854,-(SP)
          MOVE.L     #THING,-(SP)
          BSR        TINKERING
          ADDA.L     #10,SP

   We pass the word of value $1452 in the stack (modified thus
   of 2 bytes), the long word of value $54854 in the stack (modified
   of 4 bytes), the address spotted by the label THING in the stack
   (modified of 4 bytes) then we go towards our subroutine. Upon 
   return correction of 2+4+4=10 bytes of the stack pointer to return
   to the original state.

The stack has a small peculiarity. We saw in the previous lessons that the 68000 is a 16/32-bit microprocessor. It has a hard time accessing odd addresses. However, if we start stacking bytes instead of just words or long words, the Stack Pointer can very easily point to an odd address, which could crash our machine.

Type the following program:

         MOVE.L     #$12345678,D0
         MOVE.L     D0,-(SP)
         MOVE.B     D0,-(SP)
         MOVE.L     #$AAAAAAAA,D1

Assemble and then step through in MOnst, carefully observing the address of the SP (the one visible in A7).

We notice that the stack pointer changes by 4 when we do MOVE.L D0,-(SP) but changes by 2 when we do MOVE.B D0,-(SP), even though we might expect a change of 1! Therefore, errors caused by odd addresses are avoided with the stack. Thank you, Mr. MOTOROLA!

(Note: this is a peculiarity of the A7 and A7' registers. If we had worked with A3 instead of SP, it would have had an odd address. It's the type of usage made of the stack that led the people at MOTOROLA to create this difference.)

Now let's approach the final chapter of this first series:

                              THE 'TRAP'

A TRAP instruction is comparable to a BSR instruction. It acts as a branch to a routine. However, unlike the BSR instruction, which requires a label to find the routine, the TRAP instruction is satisfied with a number. This number can vary from 0 to 15. When the 68000 encounters a TRAP instruction, it looks at its number and acts accordingly. Remember the very first lessons, where we talked about the principle used by the 68000 when it found the T bit (trace mode) of the SR (status register) at 1? Jump to the first kilobyte of memory (exception vector table), search for address $24, look in the tube at this address, find a long word, this long word is the address of the routine, and go to this address to execute the routine.

Look at the sheet that lists the exception vectors, and take a look at vectors 32 to 47. There are our TRAP vectors!!! When the 68000 encounters, for example, the instruction TRAP #8, it rushes to address $0A0 to find the address of the routine it must execute.

At first glance, this seems quite complicated for not much! Indeed, you have to plan your routine, put it in memory, and then place its address in the vector. More complicated than a BSR, especially since BSR KEYBOARD_ADJUSTMENT is more telling than a TRAP #5 or a TRAP #12!!!

Here, we go back again (I told you that EVERYTHING was important in these courses!!!!!) to remember the concept of User mode and Supervisor mode. The Supervisor has access to all memory and all instructions, not the User.

If it's about forbidding the User assembler instructions such as RESET, our User will not be too bothered. On the other hand, it's concerning memory that everything will get seriously complicated. Do you want to know the resolution in which your machine is? It's easy, it's noted at address $FF8260.

You want to change the color palette? Nothing simpler, it is noted at $FF8240. Print a small text? Easy, just use the communication registers to the outside of the sound chip (surprising, isn't it!). It's located at $FF8800 and $FF8802.

Sorry??? What??? You are a User??? Ah well.... Because it's embarrassing... All these addresses are located in the memory area only accessible to the Supervisor.....

The User is quite stuck and the possibilities are greatly reduced. Fortunately, the TRAPs are there!!! Thanks to this system, the user will have access to areas that are normally forbidden to him. Not directly, of course, but thanks to the supervisor. The supervisor has indeed created routines that he placed in memory and whose addresses are in the TRAP vectors. These routines are executed in supervisor mode and attack the protected memory areas at will. When the User wants to use them, he calls them through the TRAPs. The protection is therefore well assured because the User only triggers a routine of which he generally only knows the parameters to pass and the type of message he will receive in response. This is how we can access the operating system of our Atari!!!

xA quick reminder: what is an operating system?

The first one who says it's GEM gets a slap. GEM is the user interface, not the operating system.

The operating system (or Operating System) in our case is TOS. The confusion between User Interface and Operating System comes from the fact that some operating systems also integrate a user interface: for example, this is the case with MS DOS on PC.

The operating system is a set of routines that allow the machine to be exploited. These multiple routines allow, for example, to display a character on the screen, open a file, format a floppy disk track, send a byte to the MIDI port, etc... In fact, all the basic 'stuff', but never complicated things. An operating system routine will not, for example, allow you to read the content of a file on the floppy disk. Indeed, this requires several operations with tests each time:

File opening: does it exist, is the floppy disk not damaged, etc...
Positioning the pointer in the file: did the positioning go well?

Reading: Haven't we tried to read too many bytes, etc, etc....

So, we often need several calls to different routines to achieve what we want.

It is always possible to do without the operating system, especially when programming in assembler. Indeed, all the routines of the OS (abbreviation of Operating System) are intended for common use, just like the routines of the User Interface.

This often explains the rewriting of very small parts of the system to use only what is strictly necessary. For example, GEM's mouse management routine must take care of the mouse, but also the keyboard, MIDI, and joystick. For a game, it might be interesting to rewrite this routine to manage only the joystick and thus have a routine that 'sticks' more to the need.

We will see much later how to look into the operating system in order to be able to create our own routines. Before that, let's simply use this system!

We will therefore call it using TRAPs.
4 TRAPs are 'normally' accessible in the ST:

TRAP #1      GEMDOS routines
TRAP #2      GEM routines
TRAP #13     BIOS routines
TRAP #14     Extended BIOS routines (eXtended Bios, so XBIOS)

GEMDOS   = Graphic Environment Manager Disk Operating System
GEM      = Graphic Environment Manager (subsequently divided into AES, VDI, etc.. A chapter of the second series will be dedicated to this)
BIOS     = Basic Input Output System
XBIOS    = Extended Basic Input Output System

The other TRAP vectors (0, 3 to 12, and 15) are, of course, active but have no routines assigned to them. We can use them as long as we put our routines in them before, which will be the subject of the first course of the second series.

We notice that TRAP #1 allows us to call GEMDOS. However, there is not just one GEMDOS routine but a good quantity. Moreover, these routines sometimes require parameters. How to pass them? Simply through the stack!!!

Type the following program:

         MOVE.W     #65,-(SP)
         MOVE.W     #2,-(SP)
         TRAP       #1
         ADDQ.L     #4,SP

         MOVE.W     #7,-(SP)
         TRAP       #1
         ADDQ.L     #2,SP
         MOVE.W     #0,-(SP)
         TRAP       #1
         ADDQ.L     #2,SP

Assemble this program but don't debug it, launch it by Alternate+X. You will see an A appear on your ST screen. Press a key and voila, you return to GENST! Let's analyze what we did because a lot of things happened and let's admit it, we didn't see anything!!!!

First of all, we called the Cconout() function of Gemdos. We called Gemdos with TRAP #1, but this instruction sent us to a set of routines, all belonging to Gemdos. To indicate to this main routine which Gemdos subroutine we want to go to, we passed the number of this subroutine in the stack. Always starting from the principle of the last entered first out, it is evident that this number must be stacked last so that it can be unstacked first by the main Gemdos routine, so that it can orient itself towards the subroutine that interests us. The Cconout function having the number 2, we therefore did MOVE.W #2,-(SP). (see above to remember that 2 can very well be coded on a byte but, as we work towards the stack, it will be taken as a word anyway).

Now, having found 2 as a parameter, Gemdos heads towards this barbarically named routine, whose function is to display a character on the screen. Once reaching this routine, Gemdos will seek to know which character to display. That's why we placed the ASCII code of this character on the stack with MOVE.W #65,-(SP).

Note: For the assembler, the ASCII code can be replaced by the letter itself. We could have written MOVE.W #"A",-(SP) without forgetting the quotes!

Returning from the TRAP, we need to correct the stack to avoid the problem that was the subject of the beginning of this course. We had stacked a word, so 2 bytes, and then another word, making a total of 4 bytes. We are therefore going to add 4 to the SP. We take advantage here of an operation faster than ADDA, ADDQ which reads add quick. This addition is allowed up to 8 inclusive. For example, it is not possible to do ADDQ.L #12,D1

Then we do the same kind of thing again, with function 7 of GEMDOS (named Crawcin) which expects no parameter, that's why we just pass its number on the stack. This function waits for a key press. Having passed a parameter on a word, we correct the stack by 2 after returning from the TRAP.

The program ends with function 0 of GEMDOS (Ptermo) which frees the memory occupied by our program and ends it for good. This routine does not expect a parameter, we only pass its number in the stack, so a correction of 2. Note: the stack correction for the Ptermo function is there only for educational purposes. This function ending the program, our last instruction ADDQ.L #2,SP will never be reached!

Several things now. First, don't be surprised by the strange names of the GEMDOS, BIOS, or XBIOS functions. These are the real names of these functions. In assembler, we will not use them directly since the call is made by a number, but in C, for example, this is how these functions are called. In the ST MAG assembler courses (whose educational virtues are more than questionable), we can read that the names of these functions were chosen randomly and that the Malloc() function, for example, could have been called Mstroumph(). This is ridiculous! Each of the names is, as always in computing, the abbreviation of an Anglo-Saxon expression that concretely indicates the purpose or function. Thus Malloc means Memory Allocation, this GEMDOS function is therefore for reserving a part of memory!!! Unfortunately, many books pass over this 'detail' and only provide the abbreviation.

This does not prevent you from needing a list of all the functions of GEMDOS, BIOS, and XBIOS. These functions are described in the Developer's Book, in the Bible, but also in the last pages of the GFA 3 documentation.

Note: in the GFA documentation, the GEMDOS 32 function is missing, which allows switching to Supervisor mode. This mode is of limited interest to you for now, so don't panic, we will describe all this in the second series.

Let's continue for now with small examples.
Let's display a sentence on the screen instead of a letter.
This will be done with the following program:

         MOVE.L     #MESSAGE,-(SP)        address of the text
         MOVE.W     #9,-(SP)   number of the function
         TRAP       #1         call gemdos
         ADDQ.L     #6,SP      stack correction

* waiting for a key press

         MOVE.W     #7,-(SP)   number of the function
         TRAP       #1         call GEMDOS
         ADDQ.L     #2,SP      stack correction

* end of the program

         MOVE.W     #0,-(SP)
         TRAP       #1

         SECTION DATA

MESSAGE   DC.B       "SALUT",0


Back to ASM_Tutorial