home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / UTILS2 / RECALL12.ZIP / INTPROTO.TXT < prev    next >
Text File  |  1991-11-09  |  21KB  |  511 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.                    IBM'S INTERRUPT-SHARING PROTOCOL
  8.                             by Chris Dunford
  9.                                  8/6/91
  10.  
  11. In the PS/2 BIOS Interface Technical Reference, IBM has suggested a
  12. protocol for the sharing of system interrupts.  Although the protocol
  13. was intended to allow sharing of hardware interrupts, it is equally
  14. usable for software interrupts.
  15.  
  16. One of the features of the interrupt sharing protocol is that it permits
  17. a resident program to "unhook" itself from an interrupt even if it is
  18. not the first interrupt handler in the chain of handlers.  The benefit
  19. of this should be immediately apparent to developers of TSRs.  It is a
  20. commonplace in TSR manuals to see verbiage along these lines:
  21.  
  22.     Program X can be unloaded from memory by typing ... at the DOS
  23.     prompt.  For this to work, however, X must be the last TSR loaded.
  24.     If other resident software is loaded after X, you cannot unload X.
  25.  
  26. The interrupt sharing protocol eliminates this restriction.
  27.  
  28. However, for the protocol to work, it must be followed by the majority
  29. of TSR writers.  To date, this has not occurred.  Because the protocol
  30. is easy to implement and inexpensive in terms of memory, I feel that at
  31. least part of the reason for this must be that the protocol has not been
  32. widely publicized; most DOS programmers are simply unaware of it.  I am
  33. offering this document as a modest attempt to let DOS programmers know
  34. that a solution exists for a longstanding problem.
  35.  
  36. Let me add as a caveat that I do not have and have not examined the
  37. primary source for this information--the PS/2 BIOS reference.  Most of
  38. the information in this paper was gleaned from other sources, augmented
  39. by my own experiences in writing TSRs.  The most recent writeup as of
  40. this date (August 6, 1991) was in the 7/91 issue of the Microsoft
  41. Systems Journal.
  42.  
  43. This document is not copyrighted.  Its distribution in any form is
  44. encouraged (please try to avoid making a profit on it).  If you make
  45. changes, please make sure they are clearly marked so that I get blame
  46. only for my own errors and credit only where it is due.  Please try to
  47. get any changes, amplifications, corrections, etc., back to me so that a
  48. clean copy can be redistributed if necessary.
  49.  
  50. The perpetrator of this document is:
  51.  
  52.     Chris Dunford
  53.     The Cove Software Group
  54.     PO Box 1072
  55.     Columbia, MD 21044
  56.     301/992-9371
  57.  
  58.     CompuServe: 76703,2002
  59.     Internet: 76703.2002@compuserve.com
  60.  
  61.  
  62. THE PROBLEM
  63. -----------
  64. The vast majority of terminate-and-stay-resident (TSR) programs need to
  65. intercept one or more hardware or software interrupts.  Shown below is
  66. typical code for accomplishing this, assuming that the interrupt to be
  67. "hooked" is the DOS service interrupt (INT 21h):
  68.  
  69.      (data)
  70.         OldInt21    dd ?
  71.  
  72.      (installation code)
  73.         ; Save current INT 21h vector
  74.         mov ax,3521h
  75.         int 21h
  76.         mov word ptr OldInt21,bx
  77.         mov word ptr OldInt21+2,es
  78.  
  79.         ; Set new INT 21h vector
  80.         mov dx,offset NewInt21
  81.         mov ax,2521h
  82.         int 21h
  83.         ...
  84.  
  85.     (interrupt handler)
  86.     NewInt21:
  87.         (perform processing as required)
  88.         jmp OldInt21
  89.  
  90. The installation code saves the current contents of the INT 21h vector
  91. in a 32-bit variable called OldInt21, which is known as a "downward
  92. link" or "downlink" because it provides a link "downward" in the chain
  93. of interrupt handlers.  The installation then sets the INT 21h vector to
  94. point to its own interrupt handler at NewInt21.  When an INT 21h call is
  95. subsequently issued, execution is routed to NewInt21, which performs
  96. whatever processing it needs to do.  It then executes a far jump to the
  97. address in OldInt21, allowing previously installed TSRs (and DOS, of
  98. course) to do their work.  The flow of control looks like this if only
  99. one TSR is loaded:
  100.  
  101.                             vector          OldInt21
  102.     application (int 21h) -----------> TSR ----------> DOS
  103.  
  104. To unload itself, the TSR simply resets the INT 21h vector to its
  105. initial contents (i.e., the address in OldInt21).  The TSR's interrupt
  106. handler is now no longer in the chain, and the TSR can be safely
  107. unloaded:
  108.  
  109.                             vector
  110.     application (int 21h) -----------> DOS
  111.  
  112. This scheme works fairly well until the situation arises where more than
  113. one program tries to hook the same vector:
  114.  
  115.           vector           OldInt21A        OldInt21B
  116.     app -----------> TSR A ---------> TSR B ---------> DOS
  117.  
  118. This also works fine--until TSR B wants to unload itself.  B cannot
  119. follow its normal procedure and replace the INT 21h vector with the
  120. contents of its OldInt21.  If it did, the interrupt chain would look
  121. like this:
  122.  
  123.           vector
  124.     app -----------> DOS
  125.  
  126. The problem, obviously, is that TSR A has been unceremoniously removed
  127. from the interrupt chain; it has been disabled without notice, even
  128. though it remains in memory.  This is obviously an unsatisfactory--and
  129. quite possibly dangerous--situation.
  130.  
  131. Nor can TSR B simply unload itself without fixing INT 21h.  TSR A would
  132. still have TSR B's address stored in its OldInt21; when it has completed
  133. its processing of an INT 21h call, it will jump to the address where TSR
  134. B was at one time--but is no longer--loaded.  The only unpredictable
  135. aspect of the result is which kind of reboot (hard or soft) will be
  136. required.
  137.  
  138. TSR B's only option is to wave its hands and notify the user that it
  139. cannot be unloaded.  This is satisfying to the programmer ("We told you
  140. that you can't do this") but not to the user.
  141.  
  142. The root of the problem is that TSR A has TSR B's address, but not vice
  143. versa.  If TSR B knew where TSR A was keeping its (B's) address, the
  144. resolution would be simple:  B could simply copy its downlink into A's
  145. downlink.
  146.  
  147. Here is a hypothetical memory map:
  148.  
  149.                 ---------------------
  150.                     VECTOR TABLE
  151.     0000:0084   INT21h vector = 1200:0240 --+
  152.                 ----------------------      |
  153.                                             |
  154.                 ----------------------      |
  155.                        TSR A                |
  156.     1200:0240   NewInt21 (int handler) <----+
  157.                         ...
  158.     1200:0642   OldInt21 = 1000:0296   ---+
  159.                 ----------------------    |
  160.                                           |
  161.                 ----------------------    |
  162.                        TSR B              |
  163.     1000:0296   NewInt21 (int handler) <--+
  164.                         ...
  165.     1000:0415   OldInt21 = 0070:1234   ---+
  166.                 ----------------------    |
  167.                                           |
  168.                 ----------------------    |
  169.                         DOS               |
  170.     0070:1234   INT 21h entry point    <--+
  171.                         ...
  172.                 ----------------------
  173.  
  174. The vector table entry for INT 21h points to TSR A's interrupt handler
  175. at 1200:0240.  TSR A's OldInt21 contains TSR B's interrupt handler
  176. address (1000:0296); when A has completed its work, it jumps to B at
  177. that address.  B has DOS's address (0070:1234) in its OldInt21; when it
  178. has finished, it jumps to DOS's address.
  179.  
  180. To take itself out of the chain, all B would have to do would be to put
  181. its downward link (DOS's address, contained in B's OldInt21) into A's
  182. downward link (which currently contains B's address):
  183.  
  184.                 ---------------------
  185.                     VECTOR TABLE
  186.     0000:0084   INT21h vector = 1200:0240 --+
  187.                 ----------------------      |
  188.                                             |
  189.                 ----------------------      |
  190.                        TSR A                |
  191.     1200:0240   NewInt21 (int handler) <----+
  192.                         ...
  193.     1200:0642   OldInt21 = 0070:1234   ---+     <=== change made here
  194.                 ----------------------    |
  195.                                           |
  196.                 ----------------------    |
  197.                        TSR B              |
  198.     1000:0296   NewInt21 (int handler)    |
  199.                         ...               |
  200.     1000:0415   OldInt21 = 0070:1234      |
  201.                 ----------------------    |
  202.                                           |
  203.                 ----------------------    |
  204.                         DOS               |
  205.     0070:1234   INT 21h entry point    <--+
  206.                         ...
  207.                 ----------------------
  208.  
  209. B is now removed from the chain; it could be safely unloaded, and A will
  210. remain active.  The problem, as mentioned, is that B doesn't know the
  211. address of A's OldInt21, so it can't make the correction.
  212.  
  213.  
  214. THE SOLUTION
  215. ------------
  216. The solution offered by the interrupt sharing protocol is simplicity
  217. itself: it requires that the downward link pointer be kept at a
  218. specific offset from the interrupt handler entry point.  The entry
  219. point for the first handler in a chain can be found in the
  220. interrupt vector table; in this manner, a chain can be traced from
  221. first handler to last.
  222.  
  223. The offset of the downward link from the entry point turns out to be 2.
  224. Thus, since TSR A's entry point (found in the vector table) is
  225. 1200:0240, his downlink must be located at 1200:0242.  The map would
  226. look like if both programs followed the protocol:
  227.  
  228.                 ---------------------
  229.                     VECTOR TABLE
  230.     0000:0084   INT21h vector = 1200:0240 --+
  231.                 ----------------------      |
  232.                                             |
  233.                 ----------------------      |
  234.                        TSR A                |
  235.     1200:0240   NewInt21 (int handler) <----+
  236.     1200:0242   OldInt21 = 1000:0296   ---+
  237.                         ...               |
  238.                 ----------------------    |
  239.                                           |
  240.                 ----------------------    |
  241.                        TSR B              |
  242.     1000:0296   NewInt21 (int handler) <--+
  243.     1000:0298   OldInt21 = 0070:1234   ---+
  244.                         ...               |
  245.                 ----------------------    |
  246.                                           |
  247.                 ----------------------    |
  248.                         DOS               |
  249.     0070:1234   INT 21h entry point    <--+
  250.                         ...
  251.                 ----------------------
  252.  
  253. The difference between this and the first map shown is that the
  254. addresses of the downlinks can be determined by any external program:
  255. they are no longer private to each TSR.
  256.  
  257. Program B can find A's downlink by simply following the chain (starting
  258. at the address contained in the INT 21h vector), examining the downward
  259. links until the link that points to B's interrupt handler is found.
  260. These links will always be located at the handler entry point + 2. When
  261. B finds a link that points to his handler (at 1000:0296), he has found
  262. what he needs.
  263.  
  264. The remainder of this document fills in the necessary details for
  265. implentation of the protocol.
  266.  
  267.  
  268. THE ENTRY STRUCTURE
  269. -------------------
  270. The protocol requires that you use a small (18-byte) block of mixed
  271. code and data at your interrupt handler's entry point.  When you take
  272. over an interrupt, you save the current vector in a specific location
  273. within this block and then set the vector to point to the start of the
  274. block.  The first item in the block is a short jump to your interrupt
  275. handler.
  276.  
  277. The block looks like this:
  278.  
  279.     intercept:  jmp short int_handler
  280.     prevhndlr   dd 0
  281.     signature   dw 424Bh
  282.     flag        db 0
  283.                 jmp short hwreset
  284.                 db 7 dup (0)            ; Reserved
  285.     int_handler:
  286.                 ; your interrupt handler starts here...
  287.  
  288. 'intercept' is the address you'll use when you do a SETVEC to intercept
  289. the interrupt vector, i.e., the entry point for your interrupt handler.
  290.  
  291. 'prevhndlr' is set to contain the initial contents of the interrupt
  292. vector at the time you take over.  (This is what we were calling
  293. OldInt21 in the previous sections.)
  294.  
  295. 'signature' must contain 424Bh ("KB") and is used to help identify one
  296. of these blocks.
  297.  
  298. 'flag' is important only if you're taking over a hardware interrupt
  299. that requires an EOI (say, INT 8 or INT 9).  For software interrupts
  300. (INT 16 or INT 21, e.g.), leave it 0. For hardware interrupts, the
  301. first installed handler should set the flag to 80h.  Only the handler
  302. whose flag is 80h is allowed to issue EOI.
  303.  
  304. The 'jmp short hwreset' is pretty much irrelevant to anything software
  305. people will use; it's primarily for hardware manufacturers (allows
  306. them to specify code to be executed to reinitialize the hardware on a
  307. system reset).  However, you must be prepared for the eventuality that
  308. this entry point will be called by someone; do this by simply defining
  309. an HWRESET label with a RETF:
  310.  
  311.     hwreset:    retf
  312.  
  313. Finally, leave the seven reserved bytes as zeroes.
  314.  
  315.  
  316. INSTALLING INTO THE CHAIN
  317. -------------------------
  318. To install into the interrupt chain, simply (a) save the address of
  319. the current interrupt handler in PREVHNDLR, and (b) set the interrupt
  320. vector to point to INTERCEPT.  Your handler is now first in the chain.
  321. This is no different from what you're probably doing now.  (NOTE:  the
  322. code shown here assumes that CS contains the segment of the interrupt
  323. handler.)
  324.  
  325.         ; Save current vector
  326.         mov al,interrupt number
  327.         mov ah,35h
  328.         int 21h                     ; Current vector in ES:BX
  329.         mov word ptr cs:prevhndlr,bx
  330.         mov word ptr cs:prevhndlr+2,es
  331.  
  332.         ; Set new vector
  333.         mov ax,cs
  334.         mov ds,ax
  335.         mov dx,offset intercept     ; DS:DX -> new intercept
  336.         mov ah,25H
  337.         mov al,interrupt number
  338.         int 21h
  339.  
  340.  
  341. INTERRUPT HANDLER
  342. -----------------
  343. Your interrupt handler, which begins at the label INT_HANDLER, performs
  344. its duties as required.  When you are done processing, check the
  345. contents of PREVHNDLR; if it is nonzero (the usual case), chain to the
  346. previous handler by executing a long jump to the address contained in
  347. PREVHNDLR.  Otherwise, just IRET.  Sample code:
  348.  
  349.     int_handler:
  350.         (do your thing, preserving regs as necessary)
  351.         push ax             ; Is PREVHNDLR 0:0?
  352.         mov ax,word ptr cs:prevhndlr
  353.         or ax,word ptr cs:prevhndlr+2
  354.         pop ax
  355.         jz all_done         ; Yes, PREVHNDLR is 0:0, do IRET
  356.         jmp cs:prevhndlr    ; No, chain to next handler
  357.     all_done:
  358.         ; if you are a hardware handler with flags=80h, do EOI here
  359.         iret
  360.  
  361.     hwreset: retf           ; Don't forget this!
  362.  
  363. It is strongly recommended that the INT_HANDLER label immediately
  364. follow the end of the protocol block, even though this is not required
  365. by the protocol.  Some programs may assume this to be the case and
  366. look for a specific jump distance at offset 1 of the block when
  367. attempting to identify whether or not this is a valid block.  In other
  368. words, they will look for the first item in the block to be a JMP
  369. SHORT $+18.
  370.  
  371. It is critical that you chain to the previous handler using the address
  372. stored in PREVHNDLR.  Do not store the address elsewhere and use that
  373. for chaining.  The reason for this is simple:  as discussed in the
  374. introductory sections, one of the main features of the protocol is that
  375. other programs are allowed to find and mess with the contents of your
  376. PREVHNDLR.  In particular, the handler whose address is in your
  377. PREVHNDLR may take himself out of the chain by replacing what's in your
  378. PREVHNDLR with what's in his PREVHNDLR.
  379.  
  380. To recap the introductory sections, suppose you are program C and the
  381. chain currently looks like this:
  382.  
  383.     vector -> C -> B -> A
  384.  
  385. You have program B's address in your PREVHNDLR.  B has program A's
  386. address in his PREVHNDLR.  B can remove himself from the chain by
  387. putting A's address in *YOUR* PREVHNDLR:
  388.  
  389.     vector -> C -> A
  390.  
  391. This will not work if you store B's address somewhere else--B must
  392. know where his address is stored in YOUR code.  There's an example of
  393. this in DISCONNECTING, below.
  394.  
  395.  
  396. WALKING THE CHAIN
  397. -----------------
  398. To "walk" an interrupt handler chain, get the current vector:
  399.  
  400.         mov al,interrupt number
  401.         mov ah,35h
  402.         int 21h                 ; ES:BX has current
  403.  
  404. Check to see whether ES:BX points to a valid entry structure.  Look
  405. for:
  406.  
  407.         byte ptr ES:[BX]   = 0EBh (jmp short)
  408.         word ptr ES:[BX+6] = 424Bh (signature)
  409.         byte ptr ES:[BX+9] = 0EBh (another jmp short)
  410.  
  411. If all of these match, odds are real good that this is a valid
  412. structure implementing the protocol:  there is a protocol-aware
  413. interrupt handler at ES:BX.  The address of the previous handler is at
  414. ES:[BX+2], so you can find the previous one via
  415.  
  416.         les bx,es:[bx+2]
  417.  
  418. You can continue in this fashion until either (a) ES:BX is zero, or
  419. (b) ES:BX doesn't point to a valid structure (meaning someone isn't
  420. cooperating or you've reached a pointer into DOS or BIOS).  See the
  421. next section for a more complete code example.
  422.  
  423.  
  424. DISCONNECTING FROM THE CHAIN
  425. ----------------------------
  426. To remove yourself from the chain, simply walk the chain as above
  427. until either (a) ES:BX points to your own structure, (b) ES:BX points
  428. to a structure whose PREVHNDLR field points to your structure, or (c)
  429. ES:BX does not point to a valid structure.
  430.  
  431. In case (a), you are the last handler registered, and you can simply
  432. reset the interrupt vector to point to the previous handler (the one
  433. whose address is in your PREVHNDLR field).
  434.  
  435. In case (b), someone has registered after you, but you can take
  436. yourself out of the chain by replacing his PREVHNDLR with what you
  437. have stored in your own.
  438.  
  439. In case (c), you cannot safely unload.  A non-protocol handler has
  440. broken the chain.
  441.  
  442. Coding this is not difficult at all, nor does it use much memory.  An
  443. example follows (with some pseudocode to save space).  Assume the
  444. existence of a check_valid_structure subroutine that returns carry set
  445. if ES:BX does not point to a valid protocol structure:
  446.  
  447.     ; Get address of first handler (last loaded)
  448.     mov al,interrupt number
  449.     mov ah,35h
  450.     int 21h                         ; First handler at ES:BX
  451.  
  452.     ; Are we the first handler (case A)?
  453.     if (es = seg INTERCEPT) and (bx = offset INTERCEPT) then
  454.         ; Yes, we are the first handler, just reset the
  455.         ; vector to point to the previous handler
  456.         lds dx,cs:prevhndlr         ; DS:DX -> previous handler
  457.         mov al,(interrupt number)
  458.         mov ah,25h
  459.         int 21h
  460.         jmp unload                  ; Now safe to unload
  461.     end
  462.  
  463.     ; No, walk the chain until case B or C occurs
  464.   L1:
  465.     call check_valid_structure      ; ES:BX -> protocol structure?
  466.     jc chain_busted                 ; No, chain is broken (case C)
  467.     lds dx,dword ptr es:[bx+2]      ; DS:DX = his PREVHNDLR
  468.     if (ds = seg INTERCEPT) and (dx = offset INTERCEPT) then
  469.         ; He points to us (case B).  Set his PREVHNDLR
  470.         ; to contents of our PREVHNDLR.  This takes us
  471.         ; out of the interrupt service chain.
  472.         lds dx,cs:prevhndlr         ; DS:DX -> handler before us
  473.         mov es:[bx+2],dx            ; Beam us up...
  474.         mov es:[bx+4],ds
  475.         jmp unload                  ; Now safe to unload
  476.     end
  477.  
  478.     ; ES:BX handler does not point to us, work backward
  479.     les bx,es:[bx+2]
  480.     jmp L1
  481.  
  482.   chain_busted:
  483.     ; If we get here, we cannot unload safely
  484.     ; Notify user and exit
  485.  
  486.   unload:
  487.     ; Here it is safe to unload
  488.  
  489. As you can see, the code is reasonably short and sweet.  It would be
  490. sensible to implement much of this as subroutines, especially for
  491. those TSRs that intercept more than one interrupt.  In general, you
  492. should check all of the vectors you intercept for "disconnectability"
  493. before disconnecting any of them.  This implies the existence of a
  494. "chain walking" subroutine and a disconnecting subroutine, both of
  495. these being generalized versions of the code shown above.
  496.  
  497.  
  498. FINAL COMMENTS
  499. --------------
  500. Note that the protocol allows you to install yourself as other than the
  501. first interrupt handler, and even to re-order a chain.  If for any
  502. reason you don't want to be first, walk the chain and insert yourself
  503. wherever you want by copying someone else's PREVHNDLR into your own,
  504. then putting your address into his.  You are now inserted into the chain
  505. just after him.
  506.  
  507. The code samples given above are generic and are not copied from
  508. working code from my own software.  There may be errors.
  509.  
  510. Chris Dunford 8/6/91
  511.