HOW TO DO A FORBID() IN BCPL ============================ By !LAiRFiGHT! of fLATLiNE To disable task switching on the Amiga, a normal program written in assembler would do something like this: move.l 4.w,a6 ;Get base of exec.library jsr -$84(a6) ;Call the Forbid() function However, parts of the Amiga operating system (the DOS parts) was, in the 1.x versions, written in a language called BCPL. Now let's take a look at how a Forbid() call was implemented in that language... ;Some other code here... ;... move.l #-$84,d1 ;Line 1 moveq #$20,d0 ;Line 2 move.l $160(a2),a4 ;Line 3 jsr (a5) ;Line 4 ;...and continue with no task switching. Explanation: ------------ Line 1 move.l #-$84,d1 This is an argument to the internal dos function "ExecCall". It is the normal function offset in exec.library (see the first example). The internal ExecCall function actually takes some more arguments. They are: d1 offset in exec.library d2 what to put in d0 d3 what to put in d1 d4 what to put in a0 and the stack (the internal BCPL stack that lies in a1 and goes upwards) may contain values to put into a1 and a2. Anyway, Forbid() dosn't take any arguments so only d1 need to be set in this example. Line 2 moveq #$20,d0 Wow! It uses moveq! =) Anyway, this means that the internal BCPL stack should be increased with $20. Line 3 move.l $160(a2),a4 A2 always points to the internal Global Vector Table. This table contains addresses to the various functions to be used. The address to ExecCall just happens to be at $160, so fetch it and place it in a4... Line 4 jsr (a5) ...and jump to A5!?!? No it isn't a typo... it actually calls a5! This is because a5 points to the Function Call Routine (*sigh*), which looks like this: move.l (a7)+,a3 ;Line 1 movem.l a1/a3-a4,-12(a1,d0.l) ;Line 2 adda.l d0,a1 ;Line 3 movem.l d1-d4,(a1) ;Line 4 jmp (a4) ;Line 5 Explanation: ------------ Line 1 move.l (a7)+,a3 Get the return address. Ofcourse, a BCPL program would NEVER do a normal RTS... Line 2 movem.l a1/a3-a4,-12(a1,d0.l) As mentioned, a1 is the internal stack pointer, and d0 was set to a value to increment the stack pointer with. What this line does, is to save the old stack pointer, and the return address (in a3) and the function address (in a4) just before the new stack, so that it can be easily (?) restored later. Line 3 adda.l d0,a1 Increment the stack pointer. Line 4 movem.l d1-d4,(a1) Save the arguments to the stack. WHY??? Line 5 jmp (a4) Phew! FINALLY call the Forbid() function. Or? Eh.. wait a sec... oh NO!!!? This isn't Forbid() - This is the *ExecCall* routine which will IN TURN call Forbid()!!!! AAARGH! Hmm.. let's take a look at the ExecCall routine then. It couldn't be THAT bad, could it? movem.l a0-a2/a5-a6,-(a7) ;Line 1 move.l d1,d7 ;Line 2 move.l d2,d0 ;Line 3 move.l d3,d1 ;Line 4 move.l d4,a0 ;Line 5 move.l $14(a1),a2 ;Line 6 move.l $10(a1),a1 ;Line 7 move.l 4.w,a6 ;Line 8 jsr 0(a6,d7.w) ;Line 9 movem.l (a7)+,a0-a2/a5-a6 ;Line 10 move.l d0,d1 ;Line 11 jmp (a6) ;Line 12 Explanation: ------------ Line 1 movem.l a0-a2/a5-a6,-(a7) Hmm.. it uses a7 as a stack pointer?? What a funny idea!! Line 2 move.l d1,d7 This is the offset in exec.library to which we will jsr later. Place it in d7. Line 3 move.l d2,d0 BCPL arguments are always passed in d1, d2, d3 and d4, so take the second argument and place it in d0 (as exec (and other) routines often take their arguments in the low registers, such as d0). Line 4 move.l d3,d1 Same thing here... Line 5 move.l d4,a0 ...and here... Note that Forbid() doesn't require ANY registers to be loaded with ANYTHING... Line 6 move.l $14(a1),a2 If it isn't enough with d1, d2, d3 and d4, let's use the stack instead... Offset $14 in the internal stack seems to contain the argument to put into a2... Note that if the main program in rare circumstances actually wants to SET this value to something, it isn't just to move it into $14(a1)... It has to be moved into $14(a1,d0.l), where d0 contains the value to add to the internal stack (as mentioned). Line 7 move.l $10(a1),a1 Same thing here. This will also destroy a1 as a stack pointer. (This is why it was saved on a7... a7 isn't trashed by exec functions.) Line 8 move.l 4.w,a6 Get the pointer to exec.library. Line 9 jsr 0(a6,d7.w) AT LAST!!!!! A call to Forbid()!!! Ta-daaa!!!! Line 10 movem.l (a7)+,a0-a2/a5-a6 Restore what we preserved earlier... Line 11 move.l d0,d1 Well... exec.library (along with all the other libraries I know of) return their result in d0. However; BCPL programs want their return codes in d1 (why amn't I surprised?) so let's just move it... (NB: Forbid() doesn't return anything useful...) Line 12 jmp (a6) Ehmm... a6??? But I though we put the return address in a3 a while ago??? Yup, sure did. In BCPL, a6 points to the Return From Function Routine, which looks like this: movem.l -12(a1),a1/a3 ;Line 1 move.l -4(a1),a4 ;Line 2 jmp (a3) ;Line 3 Explanation: ------------ Line 1 movem.l -12(a1),a1/a3 Get old stackpointer (before we added d0 to it) back. Also restore a3 (the return address). Line 2 move.l -4(a1),a4 Get a4 from the OLD stack. A4 will now contain the value it had even before we changed it to contain the pointer to ExecCall. (It contained/contains a pointer to the section of code currently running...) Line 3 jmp (a3) ...and voilá! Return to the main program again! Now we just have to go through the same thing again, to do a Permit()... .-----------------------------------------------------------------------------. | Thanks to Maxwold for the disassembly! | | See you all! | | !LAiRFiGHT! of fLATLiNE | `-----------------------------------------------------------------------------' Note: They HAVE changed it in V36... =)