home *** CD-ROM | disk | FTP | other *** search
/ Amiga Elysian Archive / AmigaElysianArchive.iso / prog / utils / trapsnap.lha / Trap.doc < prev   
Text File  |  1990-07-11  |  16KB  |  306 lines

  1. (The following article about trap handling and "TrapSnapper" appeared
  2. in The Transactor, Volume 8 Issue 01)
  3.  
  4.  
  5.        TrapSnapper: Adding A Trap Handler to Your C Programs
  6.  
  7.        by Chris Zamara and Nick Sullivan
  8.  
  9. That bug in your program could cost you a visit from the Guru...
  10. TrapSnapper helps keep the old geezer at bay!
  11.  
  12.  
  13. If you've spent any amount of time with the Amiga, you are probably
  14. not unfamiliar with the following message: "Software Error - Task Held.
  15. Click on Cancel to Reset/Debug". This is an annoying message. If you accept
  16. the invitation to "Click on Cancel", you'll reset the machine, bringing
  17. down not only the task that failed but any others you may have going at the
  18. time. You can ignore the message, if you want (providing you have Workbench
  19. up or access to an extra CLI), but the "Software Error" requester keeps
  20. coming relentlessly back every time you swap disks or shuffle windows.
  21. Furthermore, all visible traces of the program that died - like its windows
  22. and screens - will still be around, getting in your way and using up
  23. memory.
  24.  
  25. There's a way to deal with software errors, at least in your own programs,
  26. but before we get into that let's look at what these errors are and why the
  27. Amiga handles them as it does.
  28.  
  29.  
  30.                 Tasks, Traps and Exceptions
  31.  
  32. As you probably know, the programs that you run on the Amiga are treated as
  33. separate tasks under Exec, which is the name given to the set of operating
  34. system routines that are responsible for multitasking, device, library and
  35. I/O management. Only one task can have control of the CPU at any one
  36. instant of course; that task is said to be in the "running" state. Other
  37. tasks will at the same time be either "waiting" for their turn at the CPU,
  38. or lying dormant ("sleeping") until they are awoken by an external event
  39. such as a mouse movement, keypress, or timer signal. Sleeping tasks require
  40. only a tiny proportion of processor time to service. If its wake-up event
  41. never comes, such a task could sleep for ever without perceptibly affecting
  42. the operation of the machine.
  43.  
  44. One difficulty with a multitasking system is that any task in the running
  45. state can crash due to a bug in the program. Since there may be other,
  46. viable, tasks in the system when this occurs, it is important that they
  47. should be allowed to continue unhindered if at all possible. This is where
  48. a Software Error differs from a Guru Meditation Error - Software Errors are
  49. polite bugs that don't step on outside tasks; Gurus are barbarian bugs that
  50. destroy everyone else along with themselves.
  51.  
  52. Software Errors are actually detected by the microprocessor itself,
  53. not by the operating system. In 68000 jargon, they are called "exceptions",
  54. for they are conditions that the processor cannot handle by ordinary means.
  55. An example is the undefined result of a division by zero, using one of the
  56. 68000 divide instuctions DIVS or DIVU. If this operation is requested
  57. (usually by accident), the 68000 does not know how to proceed, and invokes
  58. an exception.
  59.  
  60. At this stage, several things happen. The 68000 goes into "supervisor"
  61. mode, which enables the status register (SR) and several privileged
  62. opcodes, and switches over to the supervisor stack from the user stack. Six
  63. bytes of data are pushed onto the supervisor stack: a word containing a
  64. copy of the Condition Code Register (CCR), and a long word containing the
  65. value of the program counter, which for most exceptions is the
  66. address of the instruction following the one that caused the exception. (In
  67. the case of certain exceptions, other data are also pushed, but we'll get
  68. to that later.) Finally, control is turned over to an exception-handling
  69. routine that is accessed via a table of vectors keyed to the "exception
  70. number" of the exceptional condition. For example, division by zero is
  71. exception 5; hence, its exception routine is entered through vector number
  72. 5 of the table. It is at this point that Exec takes over.
  73.  
  74. Exec pushes a long-word on the stack corresponding to the exception vector
  75. number - this is called the trap number (In Amiga jargon, the term
  76. "exception" has been appropriated to another purpose, and exceptions are
  77. known as "traps", from the exception-generating TRAP and TRAPV instructions
  78. of the 68000 - see the list of trap numbers at the end of this article). It
  79. then branches through a vector that is specific to the task that caused the
  80. exception. Unless this vector has been changed by the task itself, it
  81. points to the default trap handler. Since Exec cannot on its own know what
  82. actions would be appropriate to take to rescue a given task from a given
  83. exception, its default handler does nothing more than to put the task to
  84. sleep (permanently), and put up the "Software Error" requester to tell the
  85. user what is going on and provide him or her with the opportunity to reset
  86. if desired. Any resources (primarily RAM) allocated to the errant task
  87. remain allocated, and there is no reasonable way of getting them back.
  88.  
  89. However, it is not very difficult to create and enable your own
  90. trap-handling routine in a program that you write, and there are
  91. considerable advantages to doing so. For one thing, you can provide a
  92. graceful exit in the event that your program bombs out; more importantly,
  93. you can hand back your allocated RAM to the system, to be made available to
  94. other tasks. You can close any windows or screens that the program may have
  95. opened so that they don't continue to get in the way and use up memory. You
  96. also avoid the "Software Error" message that will otherwise be haunting you
  97. until you reset your machine. There is a slight risk involved: if the event
  98. that caused the trap had trashed your handler code before the 68000
  99. intervened, you're off to Guru City; this risk is small enough to be
  100. acceptable.
  101.  
  102. We now have to consider how to write a handler routine that will get you
  103. through the trap and into your deallocation code, and how to link this
  104. routine into the system so that it will be invoked when a trap occurs. We
  105. will outline what we believe to be the simplest approach to this problem in
  106. the following sections.
  107.  
  108.  
  109.                     Writing a Trap-Handler
  110.  
  111. As described above, the 68000's behaviour during a trap involves entering
  112. supervisor mode, pushing the CCR and the program counter, and jumping into
  113. the system handler code through a vector. If this were the Commodore 64
  114. rather than the Amiga, the obvious approach would be to change that vector
  115. to point to our own code. In a multitasking system, however, we can't take
  116. that kind of liberty; if we did, ANY task that encountered a trap
  117. would end up using the handler written for our task alone. (Besides, we'd
  118. have to change not just one, but ALL the vectors in the table -
  119. one for each kind of trap.) Instead, we have to use another vector, one
  120. that is specific to our task. This is provided in the "Task Control Block",
  121. a structure that Exec maintains for each task in the system. A task can get
  122. a pointer to its task control block (a "Task" structure) by calling the
  123. Exec function "FindTask()" with a parameter of zero. Once we have the
  124. pointer to our task control block, we can change the member called
  125. "tc_TrapCode" to point to the code to be executed when an exception
  126. occurs.
  127.  
  128. Changing tc_TrapCode to point to our own routine is very simple.
  129. Unfortunately, we can't leave it at that, because our trap code will be
  130. executed from within a CPU exception, and trap routines, like interrupt
  131. routines, are limited in their capabilites. For example, trap or interrupt
  132. code can't call any function that requires multitasking to do its job -
  133. this includes "printf()", commonly used in C programs to print text to the
  134. console. Another problem is that during an exception, the stack pointer
  135. (A7) points to the supervisor stack, not our task's private user stack, and
  136. we don't want to go messing with the supervisor stack (usually). Finally,
  137. an exception handler must end with an RTE instruction (ReTurn from
  138. Exception), but we probably want to finish execution of our cleanup routine
  139. with an 'Exit()', to remove our process (AmigaDOS's higher-level view of
  140. our task) from the system.
  141.  
  142. As you may have guessed, now comes the fun part where you get to find the
  143. solution to these problems. What we have to do is exit from our exception
  144. code with an RTE instruction, and THEN have control passed to our
  145. special cleanup and exit code. To do that, we have to "mess with the system
  146. stack", which, as you may have heard before, you usually don't want to do.
  147. In this case, the address of the program counter on the stack can be
  148. modified so that when the RTE instruction is executed, the CPU will run the
  149. program of our choice (the cleanup routine) instead of continuing with the
  150. nasty code that caused the exception in the first place. Before the RTE, we
  151. have to pull the trap number from the stack, which the system trap handler
  152. put there for us. This tells us why the CPU generated the exception - the
  153. list of possible trap numbers appears at the end of this article.
  154.  
  155. A small complication is that the exceptions for "bus error" (trap number 2)
  156. and "address error" (trap number 3) push an extra 8 bytes of data onto the
  157. supervisor stack. One way to handle this in the trap code is by checking
  158. the trap number and advancing the stack pointer past the 8 bytes for
  159. trap numbers less than 4.
  160.  
  161. Another issue that should be addressed by a truly general trap handler is
  162. the fact that other members of the 68000 microprocessor family arrange
  163. their stacks differently on an exception. Some Amiga users are replacing
  164. their 68000s with a 68010 or even a 68020 for added speed. The Amiga's
  165. operating system is designed to work with these CPUs, and so should
  166. application software, if possible. The trap code could check the CPU type
  167. (Exec provides a field for this purpose in the ExecBase structure) and
  168. adjust the stack pointer accordingly.
  169.  
  170. So, to recap the above, the following steps are required to have your task
  171. clean up and leave gracefully when a CPU exception occurs:
  172.  
  173. 1) Call FindTask() to get a pointer to the task's "Task" structure.
  174. 2) Change the "tc-TrapCode" member of the Task structure to point to a short
  175.    machine language routine that does the following:
  176. 2 (i)   Change the program counter on the stack to point to your
  177.         "clean up and exit" routine.
  178.   (ii)  pull the trap number from the stack
  179.   (iii) perform an RTE instruction to exit the exception handler and pass
  180.         control to the clean up function.
  181.  
  182. Your "clean up and exit" function should free any memory your task
  183. allocated, close any screens, windows, fonts, libraries, devices, etc. that
  184. it opened, and then call "exit()" (from C) or the equivalent to end
  185. and kill the task.
  186.  
  187.  
  188.                   Our Solution: TrapSnapper
  189.  
  190. Lucky for you, all of this has been done for you in the short program
  191. presented here called "TrapSnapper". TrapSnapper is set up for C-language
  192. trap handling, which takes a bit more set-up than doing it in assembler,
  193. since there is some code that HAS to be in assembler.
  194.  
  195. All you need to do to put a decent trap-handler in your program is:
  196. 1) Put a structure template declaration at the start of your file or in
  197.    a header file that you #include (see listing)
  198. 2) Declare an instance of that structure at the top of your file and
  199.    initialize it with 15 words making up a short machine language routine.
  200.    (see listing)
  201. 3) Call the function SetTrap() early in the program's execution
  202.    to initialize the trap handler.
  203.  
  204. The C code presented here is a simple program that shows how to do
  205. this, and demonstrates the effectiveness of the trap handler by generating
  206. two kinds of CPU exceptions that would normally result in a Software Error.
  207. The program itself just takes the argument supplied on the command line
  208. (when the program is invoked from the CLI) and converts it to an integer.
  209. If the value is 1, the program forces an address error by attempting to
  210. read a word (16 bits) from an odd address. If the value is not 1, the
  211. program tries to divide the value into 100 - causing a divide by zero
  212. exception, of course, if the value entered was zero.
  213.  
  214. If this program was compiled without the call to SetTrap(), it would cause
  215. a software error if it was run and given zero or one as an argument. As it
  216. stands, with the TrapSnapper in place, it just prints a warning message and
  217. the trap number, and exits gracefully, removing itself from the
  218. system without a trace.
  219.  
  220. Here's how the trap code works in C. The intial trap code must be in
  221. assembler so that we can access the stack pointer A7 directly and perform
  222. an RTE instruction. To do this in C, the machine code is set up as a static
  223. array of words and a pointer to this array is put into the Task structure's
  224. "tc_TrapCode" member. The machine code needs a pointer to the clean up code
  225. (the CleanUpAndExit() function in this example), and a place to store the
  226. trap number. These long-words are stored immediately before the machine
  227. code itself through the use of the "TrapData" structure defined at the
  228. beginning of the program. Include this structure template declaration at
  229. the top of your file or in a header file that you #include.
  230.  
  231. The assembly code appears as comments in the C listing for TrapSnapper. The
  232. code is fairly straightforward: it pulls the trap number from the stack and
  233. stores it in the MyTrap structure where the C code can get at it; adjusts
  234. the stack pointer if the trap number was less than 4 to allow for bus and
  235. address errors; then replaces the program counter on the stack with the
  236. address of the clean-up routine (which was put in the MyTrap structure by
  237. the SetTrap() function). Finally, it ends with an RTE instruction. As you
  238. may recall, we mentioned that the exception stack frame was CPU-dependent,
  239. and a general trap-handler should work for the 68010 and 68020 as well as
  240. the 68000. Well, we're good at giving advice, but this trap-handler isn't
  241. that general. Sorry, 68010/68020 users, if this routine doesn't work on
  242. your machine.
  243.  
  244. An instance of a TrapData structure (MyTrap) is then declared and
  245. initialized - this is where the machine language is created. The pointer to
  246. the clean-up code will be put into this structure by the SetTrap()
  247. function.
  248.  
  249. The SetTrap() function finds the pointer to the task's Task control block,
  250. then puts a pointer to the trap machine code into the "tc_TrapCode" member.
  251. Finally, it puts a pointer to the function called CleanUpAndExit() into the
  252. TrapData structure called MyTrap.
  253.  
  254. The CleanUpAndExit() function is where all of the clean-up code for the
  255. program goes. In this case, it just prints a message and performs the C
  256. function exit() (The DOS Exit() function could be used instead). It also
  257. prints the exception that was encountered by looking at the TrapNum member
  258. of the MyTrap structure. In a more typical program, the CleanUpAndExit
  259. function would close any Intuition Screens and Windows that the program had
  260. opened, free any memory it had allocated (including graphics memory like
  261. rasters), and close open fonts, devices, and libraries. In short, anything
  262. that the program would do to clean up after itself before it exits should
  263. be done in CleanUpAndExit().
  264.  
  265. Whether you fully understand the details of the trap handler or not, you
  266. can easily add it to your own C or assembler programs. Every program should
  267. have its own trap handler to spare the user from the plague of the Software
  268. Error when things go wrong. You might not be able to get all the bugs out
  269. of your programs, but with TrapSnapper, at least you can make them less
  270. harmful.
  271.  
  272.  
  273.                     List of Trap Numbers:
  274.  
  275.  2 - Bus Error
  276.       externally generated signal from Amiga Hardware
  277.  
  278.  3 - Address Error
  279.       word or longword instruction attempted at odd address
  280.  
  281.  4 - Illegal Instruction
  282.       a meaningless op-code was encountered
  283.  
  284.  5 - Division by zero
  285.       the source operand of a DIVS or DIVU instruction was zero
  286.  
  287.  6 - CHK instruction
  288.       operand of a CHK instruction fell outside of specified bounds
  289.  
  290.  7 - TRAPV instruction
  291.       overflow (V) set when a TRAPV instruction was executed
  292.  
  293.  8 - Privilege violation
  294.       a supervisor-state operation was attempted in user state
  295.  
  296.  9 - Instruction trace
  297.       generated after each instruction while in trace mode
  298.  
  299. 11 - 1010 Emulator
  300.       an op-code starting with the bit-pattern 1010 was encountered
  301.  
  302. 12 - 1111 Emulator
  303.       an op-code starting with the bit-pattern 1111 was encountered
  304.  
  305. 32 through 47 - TRAP instructions
  306.