home *** CD-ROM | disk | FTP | other *** search
/ No Fragments Archive 10: Diskmags / nf_archive_10.iso / MAGS / ST_NEWS / STN_02_4.MSA / DATA_DOC13 < prev    next >
Text File  |  1987-06-11  |  17KB  |  441 lines

  1. üTRAP IN AND TRAP OUT Çby Ronald van der Kamp
  2.  
  3. How to fiddle with some low-level stuff
  4.  
  5.  
  6. 1. Introduction
  7.  
  8.  You readers, will all have heard by now about TRAPs that are  in 
  9. use on our ST machines. Some of us, Modula programmers, will have 
  10. wondered what these are used for,  because we do not have to know 
  11. in  detail  about our traps,  the library modules  take  care  of 
  12. difficult stuff like that.  So in this artikel I will explain the 
  13. use  of  the traps by our operating system and how you  can  make 
  14. your own traps,  why you should do it and for what kind of things 
  15. to use them.
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23. 2.Traps and the operating system.
  24.  
  25.   Let us take for example the action of writing a string  to  the 
  26. midi  port.  After  some  searching you will  find   a  procedure 
  27. 'MIDIWS' in the extended BIOS module (with the name 'XBIOS').  In 
  28. the  definition module the description is  "PROCEDURE  MIDIWS(VAR 
  29. string: ARRAY OF BYTE; length: CARDINAL);" so that this action is 
  30. very simple for us.
  31.   On another (lower) level we can see what kind of things  should 
  32. be done in the implementation of this call.  The manuals tell  us 
  33. that   we  can write a string to the midi port by  doing  a  trap 
  34. number 14,  function number 12, with the parameters on the stack. 
  35. But what does this mean?
  36.  To explain this,  I have first to tell you about our CPU  ,  the 
  37. 68000 and some of its workings.
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45. 3. The 68000 and Exception Processing.
  46.  
  47.   When  you walk undisturbed for 50.000 miles and  then  suddenly 
  48. falls into a trap, that is something very exceptional to you.
  49.   While the CPU is executing instructions,  there can  come  from 
  50. outside  (the I/O chips for instance) an interrupt;  an  external 
  51. exception.  When  a division  by  zero  is attempted (and this is
  52. impossible)  then an internal generated exception is  done.  When 
  53. the instruction the CPU executes is a TRAP instruction,  also  an 
  54. internal  exception is generated.  What the CPU then does is  the 
  55. following (as far as is needed to know at this point):
  56.  
  57. a.  the contents of the CPU status register ( 16 bits) is put  on 
  58. the supervisor stack;
  59. b.  the  value of the CPU program counter register (32  bits)  is 
  60. placed on the supervisor stack;
  61. c.  a memory location is calculated as  follows:  128+(trapnumber 
  62. times 4); so for trap number 11 the address is 172(decimal);
  63. d.  the CPU goes into supervisor mode (if he was not already) and 
  64. fetches  the  address (of 4 bytes length),  that  stands  in  the 
  65. calculated   memory position,  into the program counter  register 
  66. and start executing the code from this point.
  67.   By  using  traps it is possible to  write  software  that  will 
  68. function o.k.  with different versions of the TOS.  (I know of  4 
  69. versions  now,  but maybe there are more) Although  the  routines 
  70. that  cater  for the traps may be in different places  in  memory 
  71. (depending  on your TOS version),  the address of the start of  a 
  72. traphandler  code is always to be found in the same (low)  memory 
  73. location. ( see point c. above)
  74.  
  75. 4. Inheritance of Traps.
  76.  
  77.  When the TOS system is booted,  the following traps are set  and 
  78. usable:  Trap #1 for GEMDOS calls,  Trap #2 for GEM/GSX, trap #13 
  79. for  the BIOS and trap #14 for the XBIOS.  These correspond  with 
  80. our  TDI  library  modules with the names  GEMDOS,(  nothing  for 
  81. #2),BIOS and XBIOS.  As soon as a modula program starts,  he will 
  82. find these traps available (he inherits them).
  83.   As you know,  any modula program needs at least one other  link 
  84. file  with  the  name GEMX.  (and accessories  need  a  different 
  85. version of this LNK file). This GEMX  module contains the Modula-
  86. run-time-system that has some initial actions to perform,  before 
  87. your own code is executed.  One of its actions is installing  the 
  88. traps  numbers 5 (for IOTRANSFER),  number 7 (for  TRANSFER)  and 
  89. number  8 for the runtime-errors.  (For version 2 of  TDI  Modula 
  90. there is an error in the manual;  not trap #6 but #5 is used  for 
  91. IOTRANSFER.)
  92. So,   as   long   as   there  are   no   accessories   or   other 
  93. 'concurrent' programs in the machine (as for instance programs in 
  94. the vertical blank list) that meddle with traps, all goes well.
  95.  
  96. 5.Trap installation and use.
  97.  
  98. Doing a Trap is very simple in principle.  Say, want want our own 
  99. Trap  #11  handling.  Activating the trap can be  done  with  the 
  100. statement:
  101.  
  102.  "CODE(4E40h + 11);"
  103.  
  104. What we need is also a procedure as:
  105.  
  106. PROCEDURE NewTrap11;
  107. BEGIN (* do your trap actions here*) 
  108. ....
  109. CODE(4E73h); (* RTE=ReTurn from Exception*)
  110. END NewTrap11;
  111. to perform our actions when the trap is activated.
  112. Now things become less simple.  First we shall want to pass  some 
  113. values  to a traphandling procedure,  just as you pass values  to 
  114. every  normal  procedure with a  parameter  list.  The  parameter 
  115. passing  as  done  in the Modula code is not  compatible  to  the 
  116. mechanism of the exception processing of the CPU.
  117. The  accepted  method  for passing values  to  procedures  is  by 
  118. putting  them 'on the stack' before the procedure call  and  have 
  119. the procedure 'read them from the stack'.
  120. The activation of our trap with one parameter of WORD size (  the 
  121. value 15 in this case) would be done with:
  122.  
  123. SETREG(D7,15)(* with CONST D7=7; according to TDI convention*)
  124. CODE(3F07h);(* MOVE.W d7,-(A7) =put word on stack*)
  125. CODE(4E4Bh);(* TRAP #11 =do the trap now*)
  126. CODE(548Fh);(* ADDQ.L #2,A7 = clean the stack (2bytes=1word)*)
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.   This  way of passing parameters makes things for  the  reciever 
  134. (the traphandling procedure) rather complicated.
  135.  First of all the traphandler should not have any entry code with 
  136. him.  So use the compiler option (*$P- *)  to stop the generating 
  137. of entry and exit code by the compiler.
  138. The procedure header should not have a parameter list,  and there 
  139. should also be no variables declared local within the handler (No 
  140. VAR  statement).  Remember  that by using the (*$P-  *)  compiler 
  141. option,  no exit code is generated for the procedure, so the last 
  142. executed  statement should be ( in our case ) a RTE.(and  not  an 
  143. RTS (ReTurn from Subroutine) as would be normal.)
  144.   As told before,  the CPU puts itself into supervisor mode  when 
  145. the  exception  processing  is  initiated.   So  from  the  first 
  146. statement of the traphandler to the last ( the RTS) the CPU is by 
  147. default  in  supervisor  mode.  In our case  this  means  trouble 
  148. because the stackpointer register A7 is a little bit 'dual'.
  149.   When  we  talk  about address register A7 and  the  CPU  is  in 
  150. supervisor  mode,  the register A7 denotes the  Supervisor  Stack 
  151. Pointer, and when in user mode this register A7 contains the User 
  152. Stack  Pointer.  When  the CPU is in supervisor  mode  there  are 
  153. privileged  instructions like 'MOVE USP,A0' that let you get  the 
  154. value of the User Stack Pointer, while A7 contains the Supervisor 
  155. Stack pointer.  It stands to reason that in user mode you  cannot 
  156. get at the supervisor stack.
  157.   We have said that it is a nice thing to be able to pass  values 
  158. to the traphandler; values that are 'put on the stack'. Now it is 
  159. considered   a  good  programming  technique  not  to  make   any 
  160. restrictions  on the status of the CPU when a trap is  initiated. 
  161. The  program that activates the traphandler will normally  be  in 
  162. user mode,  but it is not right to suppose that it is always  the 
  163. case.  So how can the traphandler know in what state the  program 
  164. was that activated him?
  165.  As told before,  when the CPU does an  exception processing, the 
  166. contents  of the status register is put on de  supervisor  stack. 
  167. And this stack is pointed to by register A7 when the  traphandler 
  168. becomes active.
  169.   Before  we illustrate this article with some  lines  of  Modula 
  170. statements, a small remark first.
  171.  
  172.   In  the traphandling procedure there will always be a   use  of 
  173. some  CPU  registers.  When  we exit the  traphandler  we  should 
  174. restore  the  registers  to the values they  contained  when  the 
  175. traphandler was activated.  (and also the stackpointers should be 
  176. set o.k.)
  177. 6.How we make a traphandler.
  178.  
  179.   First  we  take care of the saving of used  register  from  the 
  180. traphandler. We define:
  181.  
  182. TYPE regStack = RECORD
  183.                  heap: ARRAY [0..127] OF ADDRESS; (*stackspace*)
  184.                  SP : ADDRESS (* our own StackPointer*) END;
  185. and declare:
  186.  
  187. VAR regSaved : regStack;
  188.  
  189. then there is some initialisation needed:
  190.  
  191. WITH regSaved DO SP := ADR(SP) END;
  192.  
  193. and so our own stack is now ready to be used.
  194. The traphandler now starts with:
  195.  
  196.  
  197.  
  198.  
  199. (*$P- *)
  200. PROCEDURE NewTrap11;
  201. BEGIN (* in super mode*)
  202.   SETREG(A0,regSaved.SP); (* set our own stackpointer*)
  203.   CODE(48E0h,047Eh);(* MOVEM.L D5/A1-A6 -(A0) *) 
  204.   (* these registers will  be used in this handler *)
  205.   regSaved := REGISTER(A0); (* set our own stackptr *)
  206.  
  207. Where  A0 equals 8 according to the TDI convention  of  numbering 
  208. the registers for the REGISTER and SETREG functions.  In this way 
  209. the register contents we are going to destroy,  are kept safe  by 
  210. copying them to our own stack, so we are able to reconstruct them 
  211. later on.
  212.  
  213. We know that there is an exception frame on the supervisor stack. 
  214. If we define 
  215.  
  216. TYPE exceptionFrame = RECORD
  217.                         Stat  :   statusRegister;
  218.                         PC : ADDRESS END;
  219.                        
  220.  
  221. with
  222.  
  223. TYPE statusRegister = SET OF status;
  224.  
  225. whereby
  226.  
  227. TYPE status = (carry,overflow,zero,negative,extend,
  228.                r1,r2,r3,i1,i2,i3,r4,r5,supervisor,trace);
  229.  
  230. Further on we declare:
  231.  
  232. VAR toExceptionFrame : POINTER TO exceptionFrame;
  233.  
  234. and now we can get the pointer to the exception frame with:
  235.  
  236. toExceptionFrame := REGISTER (A7); (* A7=15 according to TDI *)
  237.  
  238. If we now ask:
  239.  
  240. IF supervisor IN toExceptionFrame^.stat THEN
  241.  
  242.  
  243. we know if the function frame,  containing the parameters we pass 
  244. to the traphandler, is on the supervisor stack, or in other words 
  245. :  if the activator was in super mode or user mode and so  placed 
  246. its info on de super- or userstack. 
  247. If the last  statement is true then we perform the actions:
  248.  
  249.   CODE(204Fh);(* MOVE.L A7,A0 *)
  250.   CODE(0D0FCh,TSIZE(exceptionFrame));(* ADDA.W size,A0 *)
  251.  
  252. because the stack grows downward, we add (6 bytes) to the copy of 
  253. the super stack pointer in CPU register A0.  In the other case we 
  254. can get the function frame more simple:
  255.  
  256. ELSE
  257.   CODE(4E68h);(* MOVE USP,A0 a priveleged instruction *)
  258. END; (*if*)
  259.  
  260. Now we have to save the pointer to the function frame:
  261.  
  262. toFunctionFrame := REGISTER(A0);
  263.  
  264.  
  265. In our example the function frame is:
  266.  
  267. TYPE functionFrame = RECORD fNr : CARDINAL END;
  268.  
  269. and a declaration of
  270.  
  271. VAR toFunctionFrame : POINTER TO functionFrame;
  272.  
  273. to keep the address of the frame.
  274.   Now that we have the pointer to the functionFrame we  can  read 
  275. our parameters from the function frame with statements like:
  276.  
  277. yyy := toFunctionFrame^.fNr;
  278.  
  279. By having the values of the given parameters to our disposal, all 
  280. needed actions of the traphandler can be done,  but take care not 
  281. to  change A7 (SSP) or USP.  Do not suppose there is  much  space 
  282. left  on either of these two stacks.  Every call to  a  procedure 
  283. will  use  up  at  least 4 bytes on the  stack  (for  the  return 
  284. addres), so do not nest too many calls!
  285.  
  286.  
  287.   You  are strongly adviced to take some actions to set  all  the 
  288. stacks  o.k.   before  exiting  the  traphandler  (with  an   RTE 
  289. instruction). The following path should be taken:
  290.  
  291. in case the function frame is on the supervisor stack,  it should 
  292. be  removed  and the exception frame should be  shifted  'upward' 
  293. with the needed amount (= the length of the function frame). With 
  294. a copy of the exception frame available, the actions are simple:
  295. declare:
  296.  
  297. VAR savedExcFrame : exceptionFrame;
  298.  
  299. and the stack is cleaned with the statements:
  300.  
  301. If supervisor IN toExceptionFrame^.stat THEN
  302.   savedExcFrame := toExceptionFrame^;
  303.   CODE(0DEFCh,TSIZE(functionFrame));(* ADDA.W size,A7 *)
  304.   toExceptionFrame := REGISTER(A7);
  305.   toExceptionFrame^ := savedExcFrame;
  306.  
  307.  
  308.  
  309. ELSE
  310.   CODE(4E68h);(* MOVE USP,A0 *)
  311.   CODE(0D0FCh,TSIZE(functionFrame));(* ADDA.W size,A0 *)
  312.   CODE(4E60h);(* MOVE.L A0,USP *)
  313. END;(*if*)
  314.  
  315. All kind of other actions can be done now,  but do not end simply 
  316. with and RTE;  restore first all the registers we had in  use.  ( 
  317. see the start of the traphandler) with:
  318.  
  319. SETREG(A0,regSaved.SP);
  320. CODE(4CD8h,7E20h);(* MOVEM.L (A0)+,D5/A1-A6 *)
  321. savedD5 := REGISTER(D5);(* sorry, next action destroys him*)
  322. regSaved.SP := REGISTER(A0);
  323. SETREG(D5,savedD5);
  324. CODE(4E73h); (* RTE *)
  325. END NewTrap11;
  326.  
  327.  
  328.  
  329.  
  330.  
  331. Do not let your understanding of this piece of code be clouded by 
  332. the D5 stuff;  a habit of the generation of code by the  compiler 
  333. is   to  use  D5  as  intermediate  for  even  the  most   simple 
  334. assignments.  The  central  point  of the code  is  to  fill  all 
  335. registers of the CPU with values they contained when the handling 
  336. of our trap was started. A copy of these values was stored in our 
  337. own stack. The stackpointers A7 (SSP) and USP are already earlier 
  338. cleaned up, so an RTE is possible.
  339.  
  340. 7.The installation of a Trap
  341.  
  342. In  the module 'BIOS' we find a procedure to install a  trap.  Of 
  343. course, strictly spoken, we can put the adress of our traphandler 
  344. in the right memory position (172 for Trap #11), but this is very 
  345. much bad programming practice. Always use a system function call, 
  346. because you never are sure whether your idea of  trapinstallation 
  347. is what the system itself does. The statement becomes:
  348.  
  349. BIOS.SetException(trap11VectorNr,NewTrap11);
  350.  
  351. whereby
  352.  
  353.  CONST trap11VectorNr=43; (* = 172 DIV 4 *)
  354.  
  355.   Mostly  you  will want a construction in  which  a  program  is 
  356. activated  at  boot-time ( what can be done by putting  the  .PRG 
  357. file  in  the AUTO folder of the boot-disk),  then  installs  the 
  358. traphandler,  and  deactivates itself,  keeping the code  of  the 
  359. traphandler in memory.  In this case you must take care of ending 
  360. the  installation program not in the normal way ( that is with  a 
  361. call to the GEMDOS.Term ) but with a call of:
  362.  
  363. GEMDOS.TermRes(numBytes,exitCode)
  364.  
  365. whereby  numBytes  gives the number of bytes of memory  that  are 
  366. kept  (should  be  as  much as  the  program  needs  for  itself) 
  367. counting from the start of the program code. ExitCode is 0 (zero) 
  368. for no-error.(otherwise >0)
  369.   This termination call will deactivate the program and keep  the 
  370. memory  block  intact by not giving it back to  the  free  memory 
  371. space.  So  later  programs and data will  never  overwrite  this 
  372. program code. The reason for using this call is that you must not 
  373.  
  374.  
  375. end  up  in a situation where the traphandler is  noted  down  as 
  376. being available, while the code of the traphandler is gone to the 
  377. dogs.
  378.  
  379. 8. Redefinition of available (system) traps.
  380.  
  381.  When you wish to redirect all the output of the printer port  to 
  382. say the Midi port,  you will need an own traphandler,  that takes 
  383. the place of the system function GEMDOS.PrnOut.  This function is 
  384. implemented as Trap #1 with  function number 5 and the  character 
  385. to print is on the stack ( in a WORD,  because the stack serviced 
  386. by CPU register A7 works in units of WORDs).
  387.   The  program  that installs the traphandler  has  to  save  the 
  388. address  of the trap number 1 that is already in existence (  the 
  389. normal system trap). This is done with:
  390.  
  391. oldTrap1 := BIOS.GetException(trap1VectorNr);(* vector=33*)
  392.  
  393. and  in  the  traphandler we do a test after we  have  found  the 
  394. functionframe:
  395.  
  396.  
  397. IF toFunctionFrame^.fNr <> 5 THEN (*  nothing for me to do *)
  398.   SETREG(A0,regSaved.SP);(* restore the registers *)
  399.   CODE(4CD8h,7E20h);(* MOVEM.L (A0)+,D5/A1-A6 *)
  400.   savedD5 := REGISTER(D5); (* next statement uses D5 *)
  401.   regSaved.SP := REGISTER(A0);(* set stackpointer back*)
  402.   SETREG(D5,savedD5); (* D5 got smashed *)
  403.   SETREG(A0,oldTrap1);(* the normal system trap *)
  404.   CODE(4ED0h);(* JUMP(A0) *)
  405. ELSE
  406.   ......my trap handling actions
  407.  
  408. Make  sure that,  when you  trap through to the old  system  trap 
  409. handler,  the  whole  situation should be restored  that  was  in 
  410. existence when the traphandler was activated.
  411.  
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419. 9. Conclusion
  420.  
  421.   When you want to have some extra built-in functions in your  ST 
  422. so  that it looks like an extended TOS is invented,  or when  you 
  423. need one or more operating system functions to behave  different, 
  424. you  should construct your own traphandler and make a program  to 
  425. install  that  traphandler in the operating system.  There  is  a 
  426. listing  of  a  trap test program written  in  Modula  with  this 
  427. article and a ready to run version (=compiled and linked) of  the 
  428. test program will be somewhere on this disk.
  429.  When you make the traphandler and starts with the rather minimal 
  430. source code of the test program,  it will not be unduly difficult 
  431. to  make a usefull traphandler.  The TDI post-mortem debugger  is 
  432. not  a  useable tool for debugging traphandlers.  So  build  your 
  433. traphandlers 'bit by bit',  otherwise it becomes very  frustating 
  434. to develop this kind of software. 
  435.  
  436. (c) Stichting Modula Nederland (april 1987)
  437. Bakkersteeg 9 A
  438. 2311 RH LEIDEN
  439. The Netherlands
  440.  
  441.