------ TrapIt by David M. Balean, 1993. ------ About CPU Exceptions ==================== In the "Amiga ROM Kernal Reference Manual LIBRARIES", 3rd edition, there is a small section on trap handlers on page 475 under "Exec Tasks". It is totally inadequate. It states: "To return from trap processing, remove the exception number from the stack (note that this is the supervisor stack, not the user stack) and then perform a return from exception (RTE)." This is precisely what the example code in the book does and of course it works for the example. Try it for any other exception and you could enjoy a mini disaster. My first introduction to this type of code was a short program which appeared in The Transactor in July 1987 Issue 01, pages 68 to 70, titled "TrapSnapper" by Chris Zamara and Nick Sullivan. The idea appealed to me because one of the problems when developing a program is that it can be difficult to find out exactly where this type of error actually occured. Unfortunately "TrapSnapper" only works with a 68000 machine. I spent a long time trying different approaches to programming the trap code. Because Commodore Amiga advised using an RTE this was how I started but I couldn't get it to work in every case. Address errors in particular seemed to freeze on me even though I inserted my own return address. I don't know why it failed. I don't have enough documentation on the 680X0 processors, so perhaps there is an idea for someone to write an article. I had previously come across what appeared to be a rather complicated program called "EXCPTION" on a Fred Fish disk. The technique used was to change to user mode having corrected the supervisor stack and this is how I have programmed "TrapIt". An environment is saved when the trap is set and this information contains the return address for an RTS as well as all the other registers and the CCR. Much of the information is not actually valid (e.g. the CCR) but it is there anyway. This is somewhat similar to the C library "setjmp" but it forms part of my trap code and it doesn't give a return because the D0 register contains whatever was in it when the trap was set. Exec provides the ExecBase structure which has a field for the purpose of determining which processor is running the software. This is at word $128 (AttnFlags) in the lowest 4 bits. If zero it is a 68000 otherwise it is one of the later processors (68010, 68020, 68030, 68040). Byte $129 also contains information about which FPU (68881, 68882) if any is being used (bits 4 and 5). For a 68000, note that "bus error" (trap 2) and "address error" (trap 3) push an extra two long words (8 bytes) onto the stack and these have to be skipped over. This is in addition to the normal status word and the return address. For more recent processors the format has to be read off the supervisor stack and the total number of bytes calculated from that. The format word is at position $06 on the supervisor stack after the long word containing the Amiga OS trap number has been removed. The format contains a number in it's upper 4 bits (i.e. value 0 to 15) which which determines the total number of bytes, including the RTE, to be removed to correct the supervisor stack. I have created a table of 16 values and the appropriate value is added to the A7 register. If this is done incorrectly disaster can occur! A format error (CPU exception #14 decimal) can occur if an RTE is performed which always gives the dreaded guru. As far as I can see a format exception only occurs if you interfere artificially with the supervisor stack. ---------------------------------- 68010, 68020, 68030, 68040 FORMAT ---------------------------------- Format Number Bytes (Decimal Values) 0 8 1 8 2 12 3 0 4 0 5 0 6 0 7 0 8 58 9 20 10 32 11 92 12 0 13 0 14 0 15 0 ---------------------------------- FORMAT OF Supervisor Stack (68010) ---------------------------------- WORDS WORDS to be removed from the system stack WORD FORMAT WORD for a 68010: BITS 15 14 13 12 = format, 11 10 = zero, rest = vector offset 0 0 0 0 = short, 4 words to be removed (8 bytes) 1 0 0 0 = long, 29 words to be removed (58 bytes) OTHER the processor takes a format exception when performing rte. LONG WORD PROGRAM COUNTER WORD STATUS REGISTER LONG WORD Amiga OS Exception Number ---------------------------------- FORMAT OF Supervisor Stack (68000) ---------------------------------- "Normal" Exception: ==================== LONG WORD PROGRAM COUNTER WORD STATUS REGISTER LONG WORD Amiga OS Exception Number "Bus" or "Address" error Exceptions: ==================================== LONG WORD PROGRAM COUNTER WORD STATUS REGISTER WORD Instruction Register (WORD) that was being executed LONG WORD The address which was being used WORD Type of cycle in prograss at time of error LONG WORD Amiga OS Exception Number --------------------------- RETURNING FROM AN EXCEPTION --------------------------- The normal way is to perform the RTE instruction (ReTurn from Exception). = = = In binary this is the same whether the processor is a 68000, 68010 or above. It is: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Bit # 0 1 0 0 1 1 1 0 0 1 1 1 0 0 1 1 RTE Insruction There is a considerable difference in the way that the 68000 handles RTE compared with the later processors. To perform this artficially will depend on the format word in the later processors or on whether or not it is a normal exception on the 68000. Having tried several approaches to this problem, it seems to me that the best method to implement a trap handler is as follows:- 1) Add 4 to the stack pointer to remove the Exception Number. 2) If it's a 68000 bus or address error, add 8 to the stack pointer. ------------------------------------------------------------- NOTE: The stack pointer should now point to the status word. ------------------------------------------------------------- 3) If desired, change the status word on the stack. (e.g. ensure 'S' bit and 'T' bit not set) 4) Change the PC long word to point to your own program code OR TO THE ORIGINAL TRAP CODE IF IT IS FOR AN UNIMPLEMENTED TRAP. 5) PERFORM an RTE which will automatically correct the stack pointer if the CPU is a 68010 or above. The RTE takes the status word from the stack and then the program counter from the stack and operation continues at that PC address (supposedly...) Unless things are severely up the creek this should work! BUT I couldn't get that to work in every case and it drove me bonkers. === Hence this program which inspects the format word! -------------------------- Reset Exception Processing -------------------------- This is a special form of exception and is caused by an external RESET signal. The "S" bit in the status register is set and the "T" bit is cleared. This means "Supervisor Mode" with TRACE disabled... All three interrupt bits in the status word are set (priority at least 7). The stack pointer is loaded from memory at address 0, the program counter is loaded from address 4 and instruction execution commences at whatever that address was in supervisor non-trace mode. --------------------------------------- 68881 Floating Point Maths Co-Processor --------------------------------------- This uses the F-Line exception for it's instruction set. On the 68000, this gets inspected by software at the F-Line exception vector and if it is invalid then a normal F-Line exception occurs. On later CPU's such as the 68030 the F-Line instruction is inspected in microcode in the chip itself. If it is a valid 68881 instruction then that is performed otherwise a normal F-Line exception is generated. -------------------- Motorola exceptions -------------------- (multiply the exception number by 4 to get the the vector offset) 0 Reset initial interrupt stack pointer 1 Reset initial program counter 2 Bus error 3 Address error 4 Illegal instruction 5 Divide by zero error 6 CHK instruction 7 TRAPV instruction 8 Privelege violation 9 Trace 10 Line 1010 emulation 11 Line 1111 emulation 12 RESERVED Originally(68000) 12 to 23 were reserved by Motorola... 13 Co-processor protocol violation 14 Format error 15 ??? (Can't find out what this is! Could be reserved also) 16 to 23 RESERVED 24 Spurious interrupt 25 to 31 Level X interrupt autovector, X is (26 - exception number) (Exec use) 32 to 47 TRAP #0 to TRAP #15 48 FPCP branch/set on un-ordered condition (Floating Point Co-Processor) 49 FPCP inexact result 50 FPCP divide by zero 51 FPCP underflow 52 FPCP operand error 53 FPCP overflow 54 FPCP signalling NAN (Not-A-Number) 55 RESERVED Originally(68000) 48 to 63 were reserved by Motorola... 56 MMU configuration error 57 68851 illegal operation 58 68851 access level violation 59 to 63 RESERVED 64 to 255 User defined vectors (computer designer, not programmer!) On later processors the address of the start of these vectors is contained in the vector base register. On the 68000, they start at address 0. Simply multiply the exception number by 4 to get it's vector address and add VBR if 68010 or higher. The Amiga operating system places the exception number as a long word onto the stack after the central processor has gone into supervisor mode, so there is always an extra long word on the system stack in addition to anything placed there by the CPU. -------------------------- RE-CAP of 68000 Exceptions -------------------------- 68000 Internally generated exceptions: TRACE, TRAP, ILLEGAL, UNIMPLEMENTED OPCODE, PRIVELEGE VIOLATION Status register is copied. S-Bit of status register is set putting the 68000 into supervisor mode. T-Bit of status register is cleared to permit continuous execution. PC is pushed onto the supervisor stack (long word). Previously copied status register is pushed onto the supervisor stack (word). PC is taken from the exception vector table and execution begins at PC. NOTE 6 bytes were pushed onto the stack in this case. RTE first grabs the STATUS register off the stack (a word) then transfers control to the address which is the next LONG WORD pulled off the stack. In this case there are no extra words on the stack to worry about. 68000 Bus and Address errors Address errors consist of an instruction found at an odd memory address or accessing words or long words at an odd memory address. Note that 68020 and above can access words at odd addresses and for those processors this is not an error. Status register is copied. S-Bit is set. T-Bit is cleared. PC is pushed onto the supervisor stack(long word). Previously copied status register is pushed onto the supervisor stack (word). MC68000 INSTRUCTION register is pushed onto the supervisor stack (word). 32-bit address being executed is pushed onto the supervisor stack(long word). Type of cycle in progress is pushed onto the supervisor stack (word). PC is taken from the exception vector table and execution begins at PC. NOTE 14 bytes were pushed onto the stack in this case. 8 byte must be removed from the stack before it is safe to execute the RTE instruction. The TrapIt Code =============== It seems expected that programmers will write their own trap handlers to suit their own needs. Of course for professional programmers that might be the case. I am quite sure that most amateurs, me included, ignore the problem. The need became obvious after chasing imaginary insects and I decided to venture into exception programming. The exception handler (Trap4.s) was itself difficult to debug because debuggers use the trace mode and take over the traps as well. The following is a summary of the available functions. trapit ------ This is the actual trap handler. User exceptions (64 to 255) and auto-vectors (25 to 31) are not trapped because they are the province of the computer designer. I know that Exec uses the auto-vectors but I don't know what uses exceptions 64 and above. The exception number get stored in "errnum" so that it can be checked later. If the CPU is a 68000 then if the exception number is less than 4 (i.e. 3 or 2) then 8 is added to the stack pointer. The exception number has already been removed with suitable correction. A value of 6 (status word and return address = 6 bytes) is placed in d1 for later addition to the stack pointer. If the CPU is 68010 or above, the format word is read from the stack and the appropriate byte from the correction table is placed in d1. The status word is read from the stack and supervisor mode and trace mode are turned off in it. Before moving it to the status register, d1 is added to the stack pointer thus correcting the suprvisor stack pointer before swapping to user mode when the user stack pointer takes over. TrapIt then checks whether there is an inline user function. If so the number of user function calls is incremented (up to a maximum of $ffffffff) and the user function is called as a subroutine. This function should not be stack intensive as at this stage the environment hasn't been corrected. On return, the environment is corrected and execution continues from the instruction immediately after the inline trap was set. If there was no inline function, the initial environment that was created when the initial trap was set is re-created but execution continues at whatever function was passed as the parameter. This is expected to be a total clean-up which not only closes windows and returns memory but also does a final correction of the stack pointer such as the C exit() function. restore expects the address of the environment to be restored to be ------- on the stack. restore_a0 expects the address of the environment to be restored to be --------- in the a0 register. An environment consists of a structure which contains the program counter the CCR, registers d0 to d7, registers a0 to a7 and registers fp0 to fp7. Also I have included a long word (res_num)which gets incremented every time this environment is restored up to a maximum of $ffffffff. As it restores everything (only up to a point) there is no return value. It's probably not really necessary to bother with the CCR, a0, a1, d0 or d1 as most of the operating system functions assume they will be scratch anyway. save_env -------- This saves an environment to the address of an environment structure supplied on the stack. I have tried to make it return exactly as it was. save_env_a0 ----------- This saves an evironment to an address contained in register a0. It assumes that register a0 has been placed on the stack and like save_env it is supposed to return exactly is it was. set_init_trap ------------- This sets an initial trap. It expects the address of the cleanup function to be on the stack. The environment is saved in TrapIt's initial environment and the process tc_TrapCode pointer is changed to "trapit" so that the CPU exceptions get intercepted by the trapit code. The environment program counter (what a7 points to) is changed to the clean-up function so that when the environment is restored execution will continue there and not at the next instruction after whatever called "set_init_trap". set_iltrap ---------- The aim of this function is to save the current environment and insert the address of the function supplied on the stack at "ilfunc". "numcalls" which is the number of times the inline trap has executed, is cleared. If the trap isn't already operational the tc_TrapCode is pointed to "trapit". The initial trap doesn't have to be set unless required. put_trap -------- "put_trap" saves the existing tc_TrapCode of the currect process and inserts "trapit" in it's place. It assumes that there is a suitable environment saved in either "initenv" or "inlineenv" and if it's "inlineenv" then "ilfunc" must also be initialized properly. The a0 register is not restored by this function. It expects to be called from either "set_iltrap" or "set_init_trap". set_ilnumber ------------ If desired, a number "ilnumber" can be placed into trapit for reading by the inline function or the clean-up function. It could also be inspected in exception code in C using something like the "ILTRAP" #define in my example. clear_iltrap ------------ This disables inline traps. If there is no initial trap, the whole trap is disabled and control returns to the original tc_TrapCode. clear_init_trap --------------- This disables the initial trap. If there is no inline trap, the whole trap is disabled and control returns to the original tc_TrapCode. free_trap --------- The whole trap is disabled and control returns to the original tc_TrapCode. ----------------------------- Using TrapIt functions from C ----------------------------- #define NUMTRAPFUNCS 9 VOID (*TrapFunc[NUMTRAPFUNCS])(...) = { (VOID (*)(struct TrapEnvironment *))MyTrap.restore, /* restore env */ (VOID (*)(struct TrapEnvironment))MyTrap.save_env, /* save env */ (VOID (*)(APTR))MyTrap.set_init_trap, /* set initial trap */ (VOID (*)(APTR))MyTrap.set_iltrap, /* set inline trap */ (VOID (*)(ULONG))MyTrap.set_ilnumber, /* set user trap number */ (VOID (*)(VOID))MyTrap.clear_iltrap, /* clear inline trap */ (VOID (*)(VOID))MyTrap.clear_init_trap, /* clear initial trap */ (VOID (*)(VOID))MyTrap.free_trap /* free the whole trap */ }; What this does is to provide the functions in the form of an array. This makes them easier to use. In the example, I have placed the code within a data structure. The C language wasn't designed with this in mind and as a result it is rather cumbersome. The code has to be entered as words of data and a cast has to be used in order to execute it. It is possible to use the code in the normal manner from a library file if you prefer. Bear in mind that it isn't designed to be used as "resident" or "re-entrant" code. For that it would have to be copied each time the program is executed. ------- defines ------- #define RESTORE_ENV(x) (*TrapFunc[0])(x) #define SAVE_ENV(x) (*TrapFunc[1])(x) #define SET_INIT_TRAP(x) (*TrapFunc[2])(x) #define SET_ILTRAP(x) (*TrapFunc[3])(x);\ if(MyTrap.numcalls==0){ #define ILTRAP }else{ #define END_ILTRAP } #define SET_TRAPNUM(x) (*TrapFunc[4])(x) #define RESET_ILTRAP(x) }SET_ILTRAP(x) #define CLEAR_ILTRAP() (*TrapFunc[5])() #define CLEAR_INIT_TRAP() (*TrapFunc[6])() #define FREE_TRAP() (*TrapFunc[7])() These merely provide easier ways of using the above functions from the C programming environment. If you use them note that the number of END_ILTRAP's must match the number of SET_ILTRAP's. The use of ILTRAP is optional as the code provides for an inline function anyway. The file "trapit.c" also contains definitions for "struct TrapEnvironment" and "struct TrapIt" as well as their declarations and shows how they can be used. I have called the TrapIt stucture "MyTrap" but it could be whatever you prefer as long as references to it are changed appropriately. The file "Exceptions.s" contains examples of assembly code to force CPU exceptions. If the CPU is anything other than a 68000 the A-Line exception may not work and a guru could result, so be warned. So far I haven't succeded in generating this on my 2000HD Derringer accelerated machine, but it does work on a bog standard A500. The file "Trap4.s" contains the assembly source for the trap code. As usual with a program of this kind, there are no guarantees! Use at your own risk etc. Some sources of info: Amiga ROM Kernal Reference Manual, LIBRARIES, 3rd Edition, Commodore-Amiga Inc. 68000 Assembly Language Programming, Kane, Hawkins & Leventhal The C Programming Language, Kernighan & Ritchie Advanced System Programmer's Guide for the Amiga, Bleek, Jennrich & Schulz The Transactor in July 1987 Issue 01, pages 68 to 70 "Excption" on Fred Fish #179, by Gerald T Hewes. I also got a little bit of help from: the Devpac #3 user manual the MC68000/MC68008/MC68010/12 programming pocket reference guide, and the 68000 MicroProcessor HandBook. David M. Balean 44, Wyong Road Killarney Vale N.S.W. 2261 29th August 1993 --------------------------------------------------------------------------- END OF FILE ---------------------------------------------------------------------------