home *** CD-ROM | disk | FTP | other *** search
/ The Developer Connection…ice Driver Kit for OS/2 3 / DEV3-D1.ISO / source / devnews / vol2 / sample2 / readme < prev    next >
Encoding:
Text File  |  1993-10-25  |  17.6 KB  |  564 lines

  1.  
  2. Exception Management with 32-bit OS/2
  3.  
  4.  
  5.  
  6.  
  7. 10-15-1993
  8.  
  9.  
  10. Monte Copeland
  11.  
  12. IBM Corporation
  13. 1000 NW 51st Street
  14. Boca Raton, FL 33431
  15.  
  16.  
  17.  
  18.                 Exception Management with 32-bit OS/2
  19.  
  20. 32-BIT OS/2 EXCEPTION MANAGEMENT
  21.  
  22.  
  23. Under 16-bit OS/2 architecture, a process cannot handle access
  24. violations and certain other exceptions; the system invariably
  25. terminates the process. The only thing a 16-bit program can do is
  26. register an exit-list function using the DosExitList() API. Then, at
  27. process termination time, OS/2 calls each of the registered exit-list
  28. functions, and they do some cleanup before the process terminates.
  29. This approach is process-granular. It allows for process cleanup, but
  30. not error recovery.
  31.  
  32. Under 32-bit OS/2, error recovery is thread-granular. OS/2 keeps a
  33. chain of exception handler functions for every thread. When a thread
  34. causes an exception, OS/2 walks the chain and calls each of the
  35. functions until one reports "handled." If no function handles the
  36. exception, the system takes default action. For many exceptions, the
  37. default action is process termination.
  38.  
  39. The exception management APIs are new in 32-bit OS/2. They are
  40. available to 32-bit executables and dynamic link libraries (DLLs).
  41. OS/2 designers intend for 32-bit exception management to be
  42. hardware-independent, to be a superset of traditional 16-bit
  43. exit-list processing, to encompass 16-bit signals, and to provide
  44. thread-granular recovery of exceptions.
  45.  
  46.  
  47. ---------------------------------------------------------------------------
  48.  
  49.    TIB         |<-- exception registration records  -->|
  50. -----------    -----------    -----------    -----------
  51. | pFirst  | -> | pNext-> | -> | pNext-> | -> |    -1   |
  52. |         |    |---------|    |---------|    |---------|
  53. | Thread  |    | pHandler|    | pHandler|    | pHandler|
  54. | Info    |    -----------    -----------    -----------
  55. | Block   |
  56. |         |
  57. |         |
  58. |         |
  59. -----------
  60.  
  61. ---------------------------------------------------------------------------
  62.        Figure 1.  Chain of Exception Registration Records
  63.      A pointer to the first record in the chain is stored in
  64.          the thread information block (TIB) structure.
  65. ---------------------------------------------------------------------------
  66.  
  67.  
  68. This article shows three possibilities for exception handler
  69. functions:
  70.  
  71. 1. A function recovers from the error and reports "handled" by
  72. returning XCPT_CONTINUE_EXECUTION. Execution resumes.
  73.  
  74.  
  75.                                   32-bit OS/2 Exception Management  1
  76.  
  77.  
  78.  
  79.                 Exception Management with 32-bit OS/2
  80.  
  81.  
  82. 2. A function does not handle the exception and reports "not handled"
  83. by returning XCPT_CONTINUE_SEARCH. Other handlers in the chain get a
  84. chance to handle the exception.
  85.  
  86. 3. The third option is graceful failure. This approach is nicely
  87. suited for worker functions in EXEs and DLLs that must remain robust
  88. in spite of bad parameters or killed threads.
  89.  
  90. A discussion of these options follows.
  91.  
  92.  
  93.  
  94.  
  95.  
  96. ADDING A HANDLER TO THE CHAIN
  97.  
  98.  
  99. Use the API DosSetExceptionHandler() to insert an exception handler
  100. for the calling thread. This API performs an insert-at-head
  101. operation; therefore, the last handler inserted is the first one
  102. called at exception time. It is quite possible for one handler to
  103. serve numerous threads, but each thread must call
  104. DosSetExceptionHandler() for itself.
  105.  
  106. The OS/2 Toolkit defines a exception registration record structure
  107. called EXCEPTIONREGISTRATIONRECORD, but you can define your own. See
  108. Figure 2. (More later on why that is a good thing to do.) The
  109. absolute minimum exception registration record is a structure that
  110. contains two 32-bit pointers: a pointer to the next exception
  111. registration record in the chain and a pointer to the handler
  112. function.
  113.  
  114. ---------------------------------------------------------------------------
  115. // bare-bones exception registration record
  116. // see also \toolkt20\c\os2h\bsexcpt.h
  117. typedef struct _regrec {
  118.   PVOID   pNext;
  119.   PFN     pfnHandler;
  120. } REGREC;
  121. typedef REGREC *PREGREC;
  122.  
  123. // a prototype for an exception handler function
  124. ULONG _System HandlerFunction(  PEXCEPTIONREPORTRECORD       p1,
  125.                                 PREGREC                      p2,
  126.                                 PCONTEXTRECORD               p3,
  127.                                 PVOID                        p4 );
  128. ---------------------------------------------------------------------------
  129.      Figure 2.  REGREC definition and handler function prototype
  130. ---------------------------------------------------------------------------
  131.  
  132.  
  133. Assign the pointer regrec.pfnHandler, then call
  134.  
  135.  
  136.                                   32-bit OS/2 Exception Management  2
  137.  
  138.  
  139.  
  140.                 Exception Management with 32-bit OS/2
  141.  
  142. DosSetExceptionHandler(). The system assigns regrec.pNext. See Figure
  143. 3.
  144.  
  145. ---------------------------------------------------------------------------
  146.   REGREC regrec;
  147.   ...
  148.   regrec.pfnHandler = (PFN)HandlerFunction;
  149.   rc = DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)®rec );
  150.   assert( 0 == rc );
  151. ---------------------------------------------------------------------------
  152.   Figure 3: Code fragment showing REGREC declaration and use.
  153. ---------------------------------------------------------------------------
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160. RECOVERABLE EXCEPTIONS
  161.  
  162.  
  163. When an exception handler returns "handled," it means that the
  164. handler has recovered from the exception, and execution resumes at
  165. the point of the exception.
  166.  
  167. One scenario involving recoverable exceptions is NPX (80387)
  168. emulation. For example, compile a program with hardware
  169. floating-point instructions, and run it on a system without a
  170. floating-point coprocessor. Executing a floating-point instruction
  171. causes OS/2 to raise a coprocessor-not-available exception.
  172.  
  173. The handler emulates the floating-point instruction in software.  In
  174. fact, this scenario describes one of OS/2's default exception
  175. handlers. Code compiled with floating-point instructions will run
  176. under 32-bit OS/2 on systems without a math coprocessor.
  177.  
  178. Another scenario is sparse allocation of memory. In 32-bit OS/2,
  179. DosAllocMem() allocates memory in a collection of 4K pages. (The size
  180. of every DosAllocMem allocation is always rounded up to the next
  181. higher multiple of 4K.) The pages within a memory allocation can have
  182. different attributes: notable ones are "committed" and "invalid." By
  183. using DosSetMem(), one can commit individual pages within a memory
  184. allocation.
  185.  
  186. Sample Program 1 uses DosSetMem() in an exception handler to commit
  187. memory as it is referenced. The sample program allocates a memory
  188. object such that none of the pages are committed, then it writes to
  189. that memory. This causes a page fault, and the system delievers an
  190. exception to the handler. The handler commits the memory, returns
  191. "handled," and the system restarts the instruction.
  192.  
  193.  
  194.  
  195.  
  196.  
  197.                                   32-bit OS/2 Exception Management  3
  198.  
  199.  
  200.  
  201.                 Exception Management with 32-bit OS/2
  202.  
  203. ---------------------------------------------------------------------------
  204. /* SPARSE.C.  This program allocates a one MB memory object but commits no
  205. pages.  The program then writes to that memory which is invalid, and this
  206. causes a trap.  The handler commits the invalid page and resumes execution.
  207. Compile and link this program with:  icc /Ss sparse.c */
  208.  
  209. // os2 includes
  210. #define INCL_DOS
  211. #define INCL_ERRORS
  212. #include <os2.h>
  213.  
  214. // c includes
  215. #include <stdio.h>
  216. #include <stdlib.h>
  217. #include <string.h>
  218. #include <assert.h>
  219.  
  220. // exception handler registration record
  221. typedef struct _regrec {
  222.   PVOID pNext;
  223.   PFN   pfnHandler;
  224. } REGREC;
  225. typedef REGREC *PREGREC;
  226.  
  227. // ----------------------------------------------------------------------
  228. ULONG _System Handler( PEXCEPTIONREPORTRECORD p1,
  229.                        PREGREC p2,
  230.                        PCONTEXTRECORD p3,
  231.                        PVOID pv )
  232. {
  233.   // interested in access violation
  234.   if( p1->ExceptionNum == XCPT_ACCESS_VIOLATION  ) {
  235.     assert( p1->ExceptionInfo[0] == XCPT_WRITE_ACCESS );
  236.     // try to commit the referenced page
  237.     if( 0==DosSetMem((PVOID)p1->ExceptionInfo[1], 1, PAG_COMMIT|PAG_WRITE )){
  238.       // successful commit; resume execution
  239.       return XCPT_CONTINUE_EXECUTION;
  240.     }
  241.   }
  242.   // not handled, let other handlers in the chain have the exception
  243.   return XCPT_CONTINUE_SEARCH;
  244. }
  245.  
  246. // ----------------------------------------------------------------------
  247. int main ( void )
  248. {
  249.   APIRET      rc;
  250.   PCHAR       pchar;
  251.   PSZ         psz;
  252.   PVOID       pvBase;
  253.   REGREC      regrec;
  254.  
  255.   // insert exception handler into the chain of handlers for this thread
  256.  
  257.  
  258.                                   32-bit OS/2 Exception Management  4
  259.  
  260.  
  261.  
  262.                 Exception Management with 32-bit OS/2
  263.  
  264.   regrec.pfnHandler = (PFN)Handler;
  265.   rc = DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD) ®rec );
  266.   assert( rc == 0 );
  267.  
  268.   // allocate a memory object without committing any of it;
  269.   // note lack of PAG_COMMIT flag
  270.   rc = DosAllocMem(  &pvBase, 1048576, PAG_WRITE );
  271.   assert( rc == 0 );
  272.  
  273.   // this causes an exception since the page is not committed
  274.   pchar = (PCHAR)pvBase;
  275.   *pchar = 'a';
  276.  
  277.   // this string copy causes two more exceptions
  278.   psz = (PSZ)pvBase + (4096 + 4092);
  279.   strcpy( psz, "This string crosses a 4K page boundary." );
  280.  
  281.   // reference the memory
  282.   printf( "%c\n", *pchar );
  283.   printf( "%s\n", psz );
  284.  
  285.   // free memory object
  286.   rc = DosFreeMem( pvBase );
  287.   assert( rc == 0 );
  288.  
  289.   // unlink handler before returning
  290.   rc = DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD) ®rec );
  291.   assert( rc == 0 );
  292.  
  293.   return 0;
  294. }
  295. ---------------------------------------------------------------------------
  296.                     Sample Program 1: sparse.c
  297. ---------------------------------------------------------------------------
  298.  
  299.  
  300.  
  301.  
  302.  
  303. GRACEFUL FAILURE -- WHEN GOOD THREADS GO BAD
  304.  
  305.  
  306. Some exceptions are not so easy to restart. Can an exception handler
  307. fix a bad pointer during a general protection fault? Probably not.
  308. Should an exception handler choose a new divisor after division by
  309. zero? No. The operation must fail -- but gracefully.
  310.  
  311. Graceful failure is important to application programming interfaces,
  312. APIs. API worker functions must return sensible, failing result codes
  313. to the caller in error situations.
  314.  
  315. Worker functions use an exception handler like a safety net. If a
  316. thread traps while executing a function, the safety net is there to
  317.  
  318.  
  319.                                   32-bit OS/2 Exception Management  5
  320.  
  321.  
  322.  
  323.                 Exception Management with 32-bit OS/2
  324.  
  325. catch it. For the net to be in place, the worker function registers a
  326. handler at function entry and removes it at function exit. The
  327. overhead is small, and it is worth the robustness gained.
  328.  
  329.  
  330. Getting There from Here:
  331.  
  332. In Sample Program 1, OS/2 lifts the thread from the point of the
  333. exception, makes it call the exception handler, then drops it back on
  334. the faulting instruction. This is no good for graceful failure. Yes,
  335. it is desirable to jump back to the worker function, but not at the
  336. point of the exception!
  337.  
  338. Instead, the thread must jump from the exception handler function to
  339. a known point in the worker function.  This is an interfunctional
  340. GOTO.  Debates still rage about GOTO, but most programmers accept
  341. them when it comes to exception management.
  342.  
  343. Code an interfunctional GOTO in C using setjmp() and longjmp().  Use
  344. setjmp() to record the state of the thread at the beginning of the
  345. worker function. Later, from the exception handler function, use
  346. longjmp() to return the thread to the saved state. State information
  347. is stored in a variable of type jmp_buf.
  348.  
  349. The exception handler function must have addressability to the
  350. jmp_buf in order to use it on the call to longjmp(). The stack frame
  351. of the worker function is the ideal place to hold the jmp_buf and the
  352. exception registration record. Note that a pointer to the exception
  353. registration record is one of the parameters to the exception handler
  354. function. Therefore, the way for an exception handler function to get
  355. the address of a jmp_buf is to put a jmp_buf at the end of the
  356. exception registration record See Figure 4.
  357.  
  358. ---------------------------------------------------------------------------
  359.  
  360. // user-extended exception registration record
  361. typedef struct _regrec {
  362.   PVOID     pNext;
  363.   PFN       pfnHandler;
  364.   jmp_buf   jmpWorker;
  365. } REGREC;
  366. typedef REGREC *PREGREC;
  367.  
  368. ---------------------------------------------------------------------------
  369.                 Figure 4. Extended REGREC definition
  370. ---------------------------------------------------------------------------
  371.  
  372.  
  373. Sample Program 2 consists of the main() function, a worker function,
  374. and an exception handler function.  It shows how the worker function
  375. always returns a sensible result code in spite of bad parameters.
  376.  
  377.  
  378.  
  379.  
  380.                                   32-bit OS/2 Exception Management  6
  381.  
  382.  
  383.  
  384.                 Exception Management with 32-bit OS/2
  385.  
  386. ---------------------------------------------------------------------------
  387. /* WORKER.C.  This program shows how a worker function can use an
  388. exception handler like a safety net for calling threads.
  389. Compile and link this program with:  icc /ss worker.c */
  390.  
  391. // os2 includes
  392. #define INCL_DOS
  393. #define INCL_ERRORS
  394. #include <os2.h>
  395.  
  396. // c includes
  397. #include <stdio.h>
  398. #include <stdlib.h>
  399. #include <string.h>
  400. #include <setjmp.h>
  401. #include <assert.h>
  402.  
  403. // user-extended exception registration record
  404. typedef struct _regrec {
  405.   PVOID     pNext;
  406.   PFN       pfnHandler;
  407.   jmp_buf   jmpWorker;
  408. } REGREC;
  409. typedef REGREC *PREGREC;
  410.  
  411. // ----------------------------------------------------------------------
  412. ULONG _System Handler( PEXCEPTIONREPORTRECORD p1,
  413.                        PREGREC p2,
  414.                        PCONTEXTRECORD p3,
  415.                        PVOID pv )
  416. {
  417.   switch( p1->ExceptionNum ) {
  418.   case XCPT_ACCESS_VIOLATION:
  419.   case XCPT_INTEGER_DIVIDE_BY_ZERO:
  420.   case XCPT_INTEGER_OVERFLOW:
  421.   case XCPT_PROCESS_TERMINATE:        // killed thread case
  422.   case XCPT_ASYNC_PROCESS_TERMINATE:  // killed thread case
  423.     // interested in this one
  424.     longjmp( p2->jmpWorker, p1->ExceptionNum );
  425.   default:
  426.     break;
  427.   }
  428.   // not handled
  429.   return XCPT_CONTINUE_SEARCH;
  430. }
  431.  
  432.  
  433. // ----------------------------------------------------------------------
  434. // returns TRUE for success, FALSE for failure
  435. LONG _System WorkerFunction( PCHAR pch )
  436. {
  437.   LONG        rc;
  438.   LONG        rcResult;
  439.  
  440.  
  441.                                   32-bit OS/2 Exception Management  7
  442.  
  443.  
  444.  
  445.                 Exception Management with 32-bit OS/2
  446.  
  447.   ULONG       ulException;
  448.   REGREC      regrec;
  449.  
  450.   // set a handler
  451.   regrec.pfnHandler = (PFN)Handler;
  452.   rc = DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD) ®rec );
  453.   assert( rc == 0 );
  454.  
  455.   // store a known thread state
  456.   ulException = setjmp( regrec.jmpWorker );
  457.  
  458.   if( ulException ) {
  459.     // got here from longjmp; get the handler off the chain
  460.     rc = DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD) ®rec );
  461.     assert( rc == 0 );
  462.  
  463.     // clean up here: free memory allocations, release mutex sems, etc.
  464.  
  465.     // check for the killed-thread case
  466.     switch( ulException ) {
  467.     case XCPT_PROCESS_TERMINATE:
  468.     case XCPT_ASYNC_PROCESS_TERMINATE:
  469.       // clean up done above and thread really wants to die
  470.       DosExit( EXIT_THREAD, 0 );
  471.       break;
  472.     }
  473.     // set a failing result code
  474.     rcResult = FALSE;
  475.     goto depart;
  476.   }
  477.  
  478.   // dereference the supplied pointer
  479.   *pch = 'a';
  480.  
  481.   rc = DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD) ®rec );
  482.   assert( rc == 0 );
  483.  
  484.   rcResult = TRUE;
  485.  
  486. depart:
  487.   return rcResult;
  488. }
  489.  
  490.  
  491. // ----------------------------------------------------------------------
  492. int main ( void )
  493. {
  494.   CHAR     szWork[ 16 ];
  495.   LONG     rc;
  496.  
  497.   // try worker function with a good pointer
  498.   rc = WorkerFunction( szWork );
  499.   printf( "Good pointer returns %d\n", rc );
  500.  
  501.  
  502.                                   32-bit OS/2 Exception Management  8
  503.  
  504.  
  505.  
  506.                 Exception Management with 32-bit OS/2
  507.  
  508.  
  509.   // try worker function with a bad pointer
  510.   rc = WorkerFunction( NULL );
  511.   printf( "Bad pointer returns %d\n", rc );
  512.  
  513.   return 0;
  514. }
  515. ---------------------------------------------------------------------------
  516.                     Sample Program 2: worker.c
  517. ---------------------------------------------------------------------------
  518.  
  519.  
  520.  
  521. Related Notes:
  522.  
  523.  
  524. The Killed Thread: Sample Program 2 shows how to handle the killed
  525. thread case. Even though there are no killed threads in the program,
  526. the technique is critical to exported worker functions in DLLs where
  527. the client process may use DosKillThread with abandon.
  528.  
  529.  
  530. Nested Exceptions: At exception time, OS/2 inserts a handler at the
  531. head of the chain before invoking the other handlers in order to
  532. detect nested exceptions. (A nested exception is one that occurs in
  533. an exception handler.) The IBM C Set/2 implementation of longjmp()
  534. correctly unwinds the system's nested exception handler.
  535.  
  536.  
  537. Sparse Allocations in OS/2: When there is no COMMIT option on the
  538. MEMMAN statement in CONFIG.SYS, OS/2 handles every memory allocation
  539. in a sparse manner similar to Sample Program 1. This technique is
  540. called lazy commit.  When the COMMIT option is present on MEMMAN,
  541. commits are never deferred.
  542.  
  543.  
  544. Future Considerations:
  545.  
  546. Rest assured that this exception management strategy is portable to
  547. future versions of OS/2 including Workplace OS, the microkernel
  548. implementation of OS/2. It uses 32-bit APIs, ANSI C runtime routines,
  549. and no assembler code.
  550.  
  551.  
  552.  
  553.  
  554.  
  555.  
  556.  
  557.  
  558.  
  559.  
  560.  
  561.  
  562.  
  563.                                   32-bit OS/2 Exception Management  9
  564.