summaryrefslogtreecommitdiff
path: root/sal/osl/os2/except.c
blob: 29962889fb018afa02713d1fc79954bfa0095681 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

/*
 *@@sourcefile except.c:
 *      this file contains powerful exception handlers.
 *      except.h also defines easy-to-use macros for them.
 *
 *      Usage: All OS/2 programs, PM or text mode.
 *
 *      <B>Introduction</B>
 *
 *      OS/2 exception handlers are a mess to program and,
 *      if installed wrongly, almost impossible to debug.
 *      The problem is that for any program that does a bit
 *      more than showing a message box, using exception
 *      handlers is a must to avoid system hangs. This
 *      especially applies to multi-thread programs using
 *      mutex semaphores (more on that below). The functions
 *      and macros in here are designed to make that more
 *      simple.
 *
 *      The macros in except.h automatically insert code for
 *      properly registering and deregistering the handlers
 *      in except.c. You should ALWAYS use these macros
 *      instead  of directly registering the handlers to avoid
 *      accidentally  forgetting to deregister them. If you
 *      forget to deregister an exception handler, this can
 *      lead to really strange errors (crashes, hangs) which
 *      are nearly impossible to debug because the thread's
 *      stack probably got completely messed up.
 *
 *      The general idea of these macros is to define
 *      TRY / CATCH blocks similar to C++. If an exception
 *      occurs in the TRY block, execution is transferred to
 *      the CATCH block. (This works in both C and C++, by the
 *      way.)
 *
 *      The "OnKill" function that was added with V0.9.0 has
 *      been removed again with V0.9.7.
 *
 *      The general usage is like this:
 *
 +          int your_protected_func(int ...)
 +          {
 +              TRY_LOUD(excptid)         // or: TRY_QUIET(excptid)
 +              {
 +                  char *p = NULL;
 +
 +                  ....        // the stuff in here is protected by
 +                              // the excHandlerLoud or excHandlerQuiet
 +                              // exception handler
 +                  *p = "A";
 +              }
 +              CATCH(excptid)
 +              {
 +                  ....        // exception occured: react here
 +              } END_CATCH();  // always needed!
 +          } // end of your_func
 *
 *      TRY_LOUD  is for installing excHandlerLoud.
 *      TRY_QUIET is for installing excHandlerQuiet.
 *      CATCH / END_CATCH are the same for the two. This
 *      is where the exception handler jumps to if an
 *      exception occurs.
 *      The CATCH block is _required_ even if you do nothing
 *      in there, because the CATCH() macro will deregister
 *      the handler.
 *
 *      "excptid" can be any C identifier which is not used in
 *      your current variable scope, e.g. "excpt1". This
 *      is used for creating an EXCEPTSTRUCT variable of
 *      that name on the stack. The "excptid"'s in TRY_* and
 *      CATCH must match, since this is where the macros
 *      store the exception handler data.
 *
 *      These macros may be nested if you use different
 *      "excptid"'s for sub-macros.
 *
 *      Inside the TRY and CATCH blocks, you must not use
 *      "goto" (to a location outside the block) or "return",
 *      because this will not deregister the handler.
 *
 *      Keep in mind that all the code in the TRY_* block is
 *      protected by the handler, including all functions that
 *      get called. So if you enclose your main() code in a
 *      TRY_* block, your entire application is protected.
 *      If any subfunction fails, execution is transferred to
 *      the closest CATCH() that was installed (as with C++
 *      try and catch).
 *
 *      <B>Asynchronous exceptions</B>
 *
 *      The exception handlers in this file (which are installed
 *      with the TRY/CATCH mechanism) only intercept synchronous
 *      exceptions, most importantly, XCPT_ACCESS_VIOLATION (see
 *      excHandlerLoud for a list). They do not protect your code
 *      against asynchronous exceptions.
 *
 *      OS/2 defines asynchronous exceptions to be those that
 *      can be delayed. With OS/2, there are only three of these:
 *
 *      -- XCPT_PROCESS_TERMINATE
 *      -- XCPT_ASYNC_PROCESS_TERMINATE
 *      -- XCPT_SIGNAL (thread 1 only)
 *
 *      To protect yourself against these also, put the section
 *      in question in a DosEnterMustComplete/DosExitMustComplete
 *      block as well.
 *
 *      <B>Mutex semaphores</B>
 *
 *      The problem with OS/2 mutex semaphores is that they are
 *      sometimes not automatically released when a thread terminates.
 *      If there are several mutexes involved and they are released
 *      in improper order, you can get zombie threads on exit.
 *      Even worse, if this happens to a PM thread, this will hang
 *      the system.
 *
 *      As a result, you should protect any section of code which
 *      requests a semaphore with the exception handlers.
 *
 *      So _whenever_ you request a mutex semaphore, enclose
 *      the block with TRY/CATCH in case the code crashes.
 *      Besides, enclose the TRY/CATCH block in a must-complete
 *      section, like this:
 *
 +          HMTX hmtx = ...
 +
 +          int your_func(int)
 +          {
 +              BOOL    fSemOwned = FALSE;
 +
 +              TRY_QUIET(excpt1)           // or TRY_LOUD
 +              {
 +                  if (fSemOwned = !DosRequestMutexSem(hmtx, ...))
 +                  {       ... // work on your protected data
 +                  }
 +                  // mutex gets released below
 +              }
 +              CATCH(excpt1) { } END_CATCH();    // always needed!
 +
 +              if (fSemOwned)
 +                  // this gets executed always, even if an exception occured
 +                  DosReleaseMutexSem(hmtx);
 +          } // end of your_func
 *
 *      This way your mutex semaphore gets released in every
 *      possible condition.
 *
 *      <B>Customizing</B>
 *
 *      As opposed to versions before 0.9.0, this code is now
 *      completely independent of XWorkplace. This file now
 *      contains "pure" exception handlers only.
 *
 *      However, you can customize these exception handlers by
 *      calling excRegisterHooks. This is what XWorkplace does now.
 *      This should be done upon initialization of your application.
 *      If excRegisterHooks is not called, the following safe
 *      defaults are used:
 *
 *          --  the trap log file is TRAP.LOG in the root
 *              directory of your boot drive.
 *
 *      For details on the provided exception handlers, refer
 *      to excHandlerLoud and excHandlerQuiet.
 *
 *      More useful debug information can be found in the "OS/2 Debugging
 *      Handbook", which is now available in INF format on the IBM
 *      DevCon site ("http://service2.boulder.ibm.com/devcon/").
 *      This book shows worked examples of how to unwind a stack dump.
 *
 *      This file incorporates code from the following:
 *      -- Monte Copeland, IBM Boca Ration, Florida, USA (1993)
 *      -- Roman Stangl, from the Program Commander/2 sources
 *         (1997-98)
 *      -- Marc Fiammante, John Currier, Kim Rasmussen,
 *         Anthony Cruise (EXCEPT3.ZIP package for a generic
 *         exception handling DLL, available at Hobbes).
 *
 *      If not explicitly stated otherwise, the code has been written
 *      by me, Ulrich M�ller.
 *
 *      Note: Version numbering in this file relates to XWorkplace version
 *            numbering.
 *
 *@@header "helpers\except.h"
 */

/*
 *      This file Copyright (C) 1992-99 Ulrich M�ller,
 *                                      Monte Copeland,
 *                                      Roman Stangl,
 *                                      Kim Rasmussen,
 *                                      Marc Fiammante,
 *                                      John Currier,
 *                                      Anthony Cruise.
 *      This file is part of the "XWorkplace helpers" source package.
 *
 *      2009-06-15 published under LGPL3 with Ulrich M�ller permission.
 *
 */

#define OS2EMX_PLAIN_CHAR
    // this is needed for "os2emx.h"; if this is defined,
    // emx will define PSZ as _signed_ char, otherwise
    // as unsigned char

#define INCL_DOSMODULEMGR
#define INCL_DOSEXCEPTIONS
#define INCL_DOSPROCESS
#define INCL_DOSMISC
#define INCL_DOSERRORS
#include <os2.h>

// C library headers
#include <stdio.h>              // needed for except.h
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

#define DONT_REPLACE_MALLOC
#include "helpers\setup.h"                      // code generation and debugging options

// headers in /helpers
#include "helpers\dosh.h"               // Control Program helper routines
#include "helpers\except.h"             // exception handling
#include "helpers\debug.h"              // symbol/debug code analysis

#pragma hdrstop

/* ******************************************************************
 *
 *   Global variables
 *
 ********************************************************************/

// hooks to be registered using excRegisterHooks
PFNEXCOPENFILE  G_pfnExcOpenFile = 0;
PFNEXCHOOK      G_pfnExcHook = 0;
PFNEXCHOOKERROR G_pfnExcHookError = 0;
// beep flag for excHandlerLoud
BOOL            G_fBeepOnException = TRUE;

ULONG           G_ulExplainExceptionRunning = 0;
    // global flag which is != 0 if some exception handler
    // is inside excExplainException, so that XShutdown can
    // wait until the trap log is done;
    // this is exported thru except.h
    // V0.9.13 (2001-06-19) [umoeller]

/*
 *@@category: Helpers\Control program helpers\Exceptions/debugging
 *      See except.c.
 */

/* ******************************************************************
 *
 *   Exception helper routines
 *
 ********************************************************************/

/*
 *@@ excDescribePage:
 *
 */

VOID excDescribePage(FILE *file, ULONG ulCheck)
{
    APIRET arc;
    ULONG ulCountPages = 1;
    ULONG ulFlagsPage = 0;
    arc = DosQueryMem((PVOID)ulCheck, &ulCountPages, &ulFlagsPage);

    if (arc == NO_ERROR)
    {
        fprintf(file, "valid, flags: ");
        if (ulFlagsPage & PAG_READ)
            fprintf(file, "read ");
        if (ulFlagsPage & PAG_WRITE)
            fprintf(file, "write ");
        if (ulFlagsPage & PAG_EXECUTE)
            fprintf(file, "execute ");
        if (ulFlagsPage & PAG_GUARD)
            fprintf(file, "guard ");
        if (ulFlagsPage & PAG_COMMIT)
            fprintf(file, "committed ");
        if (ulFlagsPage & PAG_SHARED)
            fprintf(file, "shared ");
        if (ulFlagsPage & PAG_FREE)
            fprintf(file, "free ");
        if (ulFlagsPage & PAG_BASE)
            fprintf(file, "base ");
    }
    else if (arc == ERROR_INVALID_ADDRESS)
        fprintf(file, "invalid");
}

/*
 *@@ excPrintStackFrame:
 *      wrapper for dbgPrintStackFrame to format
 *      output stuff right.
 *
 *@@added V0.9.2 (2000-03-10) [umoeller]
 *@@changed V0.9.12 (2001-05-12) [umoeller]: added seg:ofs to output always
 */

VOID excPrintStackFrame(FILE *file,         // in: output log file
                        PSZ pszDescription, // in: description for stack frame (should be eight chars)
                        ULONG ulAddress)    // in: address to debug
{
    APIRET  arc = NO_ERROR;
    HMODULE hmod1 = NULLHANDLE;
    CHAR    szMod1[2*CCHMAXPATH] = "unknown";
    ULONG   ulObject = 0,
            ulOffset = 0;
    fprintf(file,
            "    %-8s: %08lX ",
            pszDescription,
            ulAddress);
    arc = DosQueryModFromEIP(&hmod1,
                             &ulObject,
                             sizeof(szMod1), szMod1,
                             &ulOffset,
                             ulAddress);

    if (arc != NO_ERROR)
    {
        // error:
        fprintf(file,
                " %-8s Error: DosQueryModFromEIP returned %lu\n",
                szMod1,
                arc);
    }
    else
    {
        CHAR szFullName[2*CCHMAXPATH];

        fprintf(file,
                " %-8s %02lX:%08lX\n                                 ",
                szMod1,
                ulObject + 1,       // V0.9.12 (2001-05-12) [umoeller]
                ulOffset);          // V0.9.12 (2001-05-12) [umoeller]

        DosQueryModuleName(hmod1, sizeof(szFullName), szFullName);
        dbgPrintStackFrame(file,
                           szFullName,
                           ulObject,
                           ulOffset);

        fprintf(file, "\n");

        // make a 'tick' sound to let the user know we're still alive
        DosBeep(2000, 10);
    }
}

/*
 *@@ excDumpStackFrames:
 *      called from excExplainException to dump the
 *      thread's stack frames. This calls excPrintStackFrame
 *      for each stack frame found.
 *
 *@@added V0.9.4 (2000-06-15) [umoeller]
 */

VOID excDumpStackFrames(FILE *file,                   // in: logfile from fopen()
                        PTIB ptib,
                        PCONTEXTRECORD pContextRec)   // in: excpt info
{
    PULONG pulStackWord = 0;

    fprintf(file, "\n\nStack frames:\n              Address   Module   seg:ofs\n");

    // first the trapping address itself
    excPrintStackFrame(file,
                       "CS:EIP  ",
                       pContextRec->ctx_RegEip);


    pulStackWord = (PULONG)pContextRec->ctx_RegEbp;
    /* if (pContextRec->ctx_RegEbp < pContextRec->ctx_RegEsp)
        pulStackWord = (PULONG)(pContextRec->ctx_RegEbp & 0xFFFFFFF0);
    else
        pulStackWord = (PULONG)(pContextRec->ctx_RegEsp & 0xFFFFFFF0); */

    while (    (pulStackWord != 0)
            && (pulStackWord < (PULONG)ptib->tib_pstacklimit)
          )
    {
        CHAR szAddress[20];

        if (((ULONG)pulStackWord & 0x00000FFF) == 0x00000000)
        {
            // we're on a page boundary: check access
            ULONG ulCountPages = 0x1000;
            ULONG ulFlagsPage = 0;
            APIRET arc = DosQueryMem((void *)pulStackWord,
                                     &ulCountPages,
                                     &ulFlagsPage);
            if (    (arc != NO_ERROR)
                 || (   (arc == NO_ERROR)
                      && ( !( ((ulFlagsPage & (PAG_COMMIT|PAG_READ))
                               == (PAG_COMMIT|PAG_READ)
                              )
                            )
                         )
                    )
               )
            {
                fprintf(file, "\n    %08lX: ", (ULONG)pulStackWord);
                fprintf(file, "Page inaccessible");
                pulStackWord += 0x1000;
                continue; // for
            }
        }

        sprintf(szAddress, "%08lX",
                (ULONG)pulStackWord);
        excPrintStackFrame(file,
                           szAddress,
                           *(pulStackWord+1));
        pulStackWord = (PULONG)*(pulStackWord);

        if (pulStackWord == 0)
            fprintf(file, "\n    pulStackWord == 0");
        else if (pulStackWord >= (PULONG)ptib->tib_pstacklimit)
            fprintf(file, "\n    pulStackWord >= (PULONG)ptib->tib_pstacklimit");
    } // end while
}

/*
 *@@ excExplainException:
 *      used by the exception handlers below to write
 *      LOTS of information about the exception into a logfile.
 *
 *      This calls excPrintStackFrame for each stack frame.
 *
 *@@changed V0.9.0 [umoeller]: added support for application hook
 *@@changed V0.9.0 (99-11-02) [umoeller]: added TID to dump
 *@@changed V0.9.2 (2000-03-10) [umoeller]: now using excPrintStackFrame
 *@@changed V0.9.3 (2000-05-03) [umoeller]: fixed crashes
 *@@changed V0.9.6 (2000-11-06) [umoeller]: added more register dumps
 *@@changed V0.9.13 (2001-06-19) [umoeller]: added global flag for whether this is running
 *@@changed V0.9.16 (2001-11-02) [pr]: make object display signed
 *@@changed V0.9.19 (2002-03-28) [umoeller]: added thread ordinal
 */

VOID excExplainException(FILE *file,                   // in: logfile from fopen()
                         PSZ pszHandlerName,           // in: descriptive string
                         PEXCEPTIONREPORTRECORD pReportRec, // in: excpt info
                         PCONTEXTRECORD pContextRec)   // in: excpt info
{
    ULONG       aulBuf[3];
    const char  *pcszVersion = "unknown";

    PTIB        ptib = NULL;
    PPIB        ppib = NULL;
    HMODULE     hMod1, hMod2;
    CHAR        szMod1[CCHMAXPATH] = "unknown",
                szMod2[CCHMAXPATH] = "unknown";
    ULONG       ulObjNum,
                ulOffset;
    ULONG       ul;

    ULONG       ulOldPriority = 0x0100; // regular, delta 0

    // raise global flag for whether this func is running
    // V0.9.13 (2001-06-19) [umoeller]
    G_ulExplainExceptionRunning++;

    // raise this thread's priority, because this
    // might take some time
    if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR)
        if (ptib)
            if (ptib->tib_ptib2)
            {
                ulOldPriority = ptib->tib_ptib2->tib2_ulpri;
                DosSetPriority(PRTYS_THREAD,
                               PRTYC_REGULAR,
                               PRTYD_MAXIMUM,
                               0);     // current thread
            }

    // make some noise
#ifndef __NOEXCEPTIONBEEPS__        // V0.9.19 (2002-04-17) [umoeller]
    if (G_fBeepOnException)
    {
        DosBeep( 250, 30);
        DosBeep( 500, 30);
        DosBeep(1000, 30);
        DosBeep(2000, 30);
        DosBeep(4000, 30);
        DosBeep(2000, 30);
        DosBeep(1000, 30);
        DosBeep( 500, 30);
        DosBeep( 250, 30);
    }
#endif

    // generic exception info
    DosQuerySysInfo(QSV_VERSION_MAJOR,      // 11
                    QSV_VERSION_MINOR,      // 12
                    &aulBuf, sizeof(aulBuf));
    // Warp 3 is reported as 20.30
    // Warp 4 is reported as 20.40
    // Aurora is reported as 20.45

    if (aulBuf[0] == 20)
    {
        switch (aulBuf[1])
        {
            case 30: pcszVersion = "Warp 3"; break;
            case 40: pcszVersion = "Warp 4"; break;
            case 45: pcszVersion = "WSeB kernel"; break;
        }
    }
    fprintf(file,
            "Running OS/2 version: %u.%u (%s)\n",
            aulBuf[0],                      // major
            aulBuf[1],
            pcszVersion);


    // generic exception info
    fprintf(file,
            "\n%s:\n    Exception type: %08lX\n    Address:        %08lX\n    Params:         ",
            pszHandlerName,
            pReportRec->ExceptionNum,
            (ULONG)pReportRec->ExceptionAddress);
    for (ul = 0;  ul < pReportRec->cParameters;  ul++)
    {
        fprintf(file, "%08lX  ",
                pReportRec->ExceptionInfo[ul]);
    }

    // now explain the exception in a bit more detail;
    // depending on the exception, pReportRec->ExceptionInfo
    // contains some useful data
    switch (pReportRec->ExceptionNum)
    {
        case XCPT_ACCESS_VIOLATION:
            fprintf(file, "\nXCPT_ACCESS_VIOLATION: ");
            if (pReportRec->ExceptionInfo[0] & XCPT_READ_ACCESS)
                fprintf(file, "Invalid read access from 0x%04lX:%08lX.\n",
                        pContextRec->ctx_SegDs, pReportRec->ExceptionInfo[1]);
            else if (pReportRec->ExceptionInfo[0] & XCPT_WRITE_ACCESS)
                fprintf(file, "Invalid write access to 0x%04lX:%08lX.\n",
                        pContextRec->ctx_SegDs, pReportRec->ExceptionInfo[1]);
            else if (pReportRec->ExceptionInfo[0] & XCPT_SPACE_ACCESS)
                fprintf(file, "Invalid space access at 0x%04lX.\n",
                        pReportRec->ExceptionInfo[1]);
            else if (pReportRec->ExceptionInfo[0] & XCPT_LIMIT_ACCESS)
                fprintf(file, "Invalid limit access occurred.\n");
            else if (pReportRec->ExceptionInfo[0] == XCPT_UNKNOWN_ACCESS)
                fprintf(file, "unknown at 0x%04lX:%08lX\n",
                            pContextRec->ctx_SegDs, pReportRec->ExceptionInfo[1]);
            fprintf(file,
                    "Explanation: An attempt was made to access a memory object which does\n"
                    "             not belong to the current process. Most probable causes\n"
                    "             for this are that an invalid pointer was used, there was\n"
                    "             confusion with administering memory or error conditions \n"
                    "             were not properly checked for.\n");
        break;

        case XCPT_INTEGER_DIVIDE_BY_ZERO:
            fprintf(file, "\nXCPT_INTEGER_DIVIDE_BY_ZERO.\n");
            fprintf(file,
                    "Explanation: An attempt was made to divide an integer value by zero,\n"
                    "             which is not defined.\n");
        break;

        case XCPT_ILLEGAL_INSTRUCTION:
            fprintf(file, "\nXCPT_ILLEGAL_INSTRUCTION.\n");
            fprintf(file,
                    "Explanation: An attempt was made to execute an instruction that\n"
                    "             is not defined on this machine's architecture.\n");
        break;

        case XCPT_PRIVILEGED_INSTRUCTION:
            fprintf(file, "\nXCPT_PRIVILEGED_INSTRUCTION.\n");
            fprintf(file,
                    "Explanation: An attempt was made to execute an instruction that\n"
                    "             is not permitted in the current machine mode or that\n"
                    "             the program had no permission to execute.\n");
        break;

        case XCPT_INTEGER_OVERFLOW:
            fprintf(file, "\nXCPT_INTEGER_OVERFLOW.\n");
            fprintf(file,
                    "Explanation: An integer operation generated a carry-out of the most\n"
                    "             significant bit. This is a sign of an attempt to store\n"
                    "             a value which does not fit into an integer variable.\n");
        break;

        default:
            fprintf(file, "\nUnknown OS/2 exception number %d.\n", pReportRec->ExceptionNum);
            fprintf(file, "Look this up in the OS/2 header files.\n");
        break;
    }

    // V0.9.16 (2001-11-02) [pr]: We already got this info. above - this overwrites the
    // original values before the priority change, which is rather confusing.
    // if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR)
    {
        /*
         * process info:
         *
         */

        if ((ptib) && (ppib))       // (99-11-01) [umoeller]
        {
            if (pContextRec->ContextFlags & CONTEXT_CONTROL)
            {
                // get the main module
                hMod1 = ppib->pib_hmte;
                DosQueryModuleName(hMod1,
                                   sizeof(szMod1),
                                   szMod1);

                // get the trapping module
                DosQueryModFromEIP(&hMod2,
                                   &ulObjNum,
                                   sizeof(szMod2),
                                   szMod2,
                                   &ulOffset,
                                   pContextRec->ctx_RegEip);
                DosQueryModuleName(hMod2,
                                   sizeof(szMod2),
                                   szMod2);
            }

            fprintf(file,
                    "\nProcess information:"
                    "\n    Process ID:      0x%lX"
                    "\n    Process module:  0x%lX (%s)"
                    "\n    Trapping module: 0x%lX (%s)"
                    "\n    Object: %ld\n",  // V0.9.16 (2001-11-02) [pr]: make this display signed
                    ppib->pib_ulpid,
                    hMod1, szMod1,
                    hMod2, szMod2,
                    ulObjNum);

            fprintf(file,
                    "\nTrapping thread information:"
                    "\n    Thread ID:       0x%lX (%lu)"
                    "\n    Thread slot ID:  0x%lX (%lu)"        // added V0.9.19 (2002-03-28) [umoeller]
                    "\n    Priority:        0x%lX\n",
                    ptib->tib_ptib2->tib2_ultid, ptib->tib_ptib2->tib2_ultid,
                    ptib->tib_ordinal, ptib->tib_ordinal,
                    ulOldPriority);
        }
        else
            fprintf(file, "\nProcess information was not available.");

        /*
         *  now call the hook, if one has been defined,
         *  so that the application can write additional
         *  information to the traplog (V0.9.0)
         */

        if (G_pfnExcHook)
            G_pfnExcHook(file, ptib, ulOldPriority);  // V0.9.16 (2001-12-02) [pr]

        // *** registers

        fprintf(file, "\nRegisters:");
        if (pContextRec->ContextFlags & CONTEXT_INTEGER)
        {
            // DS the following 4 added V0.9.6 (2000-11-06) [umoeller]
            fprintf(file, "\n    DS  = %08lX  ", pContextRec->ctx_SegDs);
            excDescribePage(file, pContextRec->ctx_SegDs);
            // ES
            fprintf(file, "\n    ES  = %08lX  ", pContextRec->ctx_SegEs);
            excDescribePage(file, pContextRec->ctx_SegEs);
            // FS
            fprintf(file, "\n    FS  = %08lX  ", pContextRec->ctx_SegFs);
            excDescribePage(file, pContextRec->ctx_SegFs);
            // GS
            fprintf(file, "\n    GS  = %08lX  ", pContextRec->ctx_SegGs);
            excDescribePage(file, pContextRec->ctx_SegGs);

            // EAX
            fprintf(file, "\n    EAX = %08lX  ", pContextRec->ctx_RegEax);
            excDescribePage(file, pContextRec->ctx_RegEax);
            // EBX
            fprintf(file, "\n    EBX = %08lX  ", pContextRec->ctx_RegEbx);
            excDescribePage(file, pContextRec->ctx_RegEbx);
            // ECX
            fprintf(file, "\n    ECX = %08lX  ", pContextRec->ctx_RegEcx);
            excDescribePage(file, pContextRec->ctx_RegEcx);
            // EDX
            fprintf(file, "\n    EDX = %08lX  ", pContextRec->ctx_RegEdx);
            excDescribePage(file, pContextRec->ctx_RegEdx);
            // ESI
            fprintf(file, "\n    ESI = %08lX  ", pContextRec->ctx_RegEsi);
            excDescribePage(file, pContextRec->ctx_RegEsi);
            // EDI
            fprintf(file, "\n    EDI = %08lX  ", pContextRec->ctx_RegEdi);
            excDescribePage(file, pContextRec->ctx_RegEdi);
            fprintf(file, "\n");
        }
        else
            fprintf(file, " not available\n");

        if (pContextRec->ContextFlags & CONTEXT_CONTROL)
        {

            // *** instruction

            fprintf(file, "Instruction pointer (where exception occured):\n    CS:EIP = %04lX:%08lX  ",
                    pContextRec->ctx_SegCs,
                    pContextRec->ctx_RegEip);
            excDescribePage(file, pContextRec->ctx_RegEip);

            // *** CPU flags

            fprintf(file, "\n    EFLAGS = %08lX", pContextRec->ctx_EFlags);

            /*
             * stack:
             *
             */

            fprintf(file, "\nStack:\n    Base:         %08lX\n    Limit:        %08lX",
                   (ULONG)(ptib ? ptib->tib_pstack : 0),
                   (ULONG)(ptib ? ptib->tib_pstacklimit : 0));
            fprintf(file, "\n    SS:ESP = %04lX:%08lX  ",
                    pContextRec->ctx_SegSs,
                    pContextRec->ctx_RegEsp);
            excDescribePage(file, pContextRec->ctx_RegEsp);

            fprintf(file, "\n    EBP    =      %08lX  ", pContextRec->ctx_RegEbp);
            excDescribePage(file, pContextRec->ctx_RegEbp);

            /*
             * stack dump:
             */

            if (ptib != 0)
            {
                excDumpStackFrames(file, ptib, pContextRec);
            }
        }
    }
    fprintf(file, "\n");

    // reset old priority
    DosSetPriority(PRTYS_THREAD,
                   (ulOldPriority & 0x0F00) >> 8,
                   (UCHAR)ulOldPriority,
                   0);     // current thread

    // lower global flag again V0.9.13 (2001-06-19) [umoeller]
    G_ulExplainExceptionRunning--;
}

/* ******************************************************************
 *
 *   Exported routines
 *
 ********************************************************************/

/*
 *@@ excRegisterHooks:
 *      this registers hooks which get called for
 *      exception handlers. You can set any of the
 *      hooks to NULL for safe defaults (see top of
 *      except.c for details). You can set none,
 *      one, or both of the hooks, and you can call
 *      this function several times.
 *
 *      Both hooks get called whenever an exception
 *      occurs, so there better be no bugs in these
 *      routines. ;-) They only get called from
 *      within excHandlerLoud (because excHandlerQuiet
 *      writes no trap logs).
 *
 *      The hooks are as follows:
 *
 *      --  pfnExcOpenFileNew gets called to open
 *          the trap log file. This must return a FILE*
 *          pointer from fopen(). If this is not defined,
 *          ?:\TRAP.LOG is used. Use this to specify a
 *          different file and have some notes written
 *          into it before the actual exception info.
 *
 *      --  pfnExcHookNew gets called while the trap log
 *          is being written. At this point,
 *          the following info has been written into
 *          the trap log already:
 *          -- exception type/address block
 *          -- exception explanation
 *          -- process information
 *
 *          _After_ the hook, the exception handler
 *          continues with the "Registers" information
 *          and stack dump/analysis.
 *
 *          Use this hook to write additional application
 *          info into the trap log, such as the state
 *          of your own threads and mutexes.
 *
 *      --  pfnExcHookError gets called when the TRY_* macros
 *          fail to install an exception handler (when
 *          DosSetExceptionHandler fails). I've never seen
 *          this happen.
 *
 *@@added V0.9.0 [umoeller]
 *@@changed V0.9.2 (2000-03-10) [umoeller]: pfnExcHookError added
 */

VOID excRegisterHooks(PFNEXCOPENFILE pfnExcOpenFileNew,
                      PFNEXCHOOK pfnExcHookNew,
                      PFNEXCHOOKERROR pfnExcHookError,
                      BOOL fBeepOnExceptionNew)
{
    // adjust the global variables
    G_pfnExcOpenFile = pfnExcOpenFileNew;
    G_pfnExcHook = pfnExcHookNew;
    G_pfnExcHookError = pfnExcHookError;
    G_fBeepOnException = fBeepOnExceptionNew;
}

/*
 *@@ excHandlerLoud:
 *      this is the "sophisticated" exception handler;
 *      which gives forth a loud sequence of beeps thru the
 *      speaker, writes a trap log and then returns back
 *      to the thread to continue execution, i.e. the
 *      default OS/2 exception handler will never get
 *      called.
 *
 *      This requires a setjmp() call on
 *      EXCEPTIONREGISTRATIONRECORD2.jmpThread before
 *      being installed. The TRY_LOUD macro will take
 *      care of this for you (see except.c).
 *
 *      This intercepts the following exceptions (see
 *      the OS/2 Control Program Reference for details):
 *
 *      --  XCPT_ACCESS_VIOLATION         (traps 0x0d, 0x0e)
 *      --  XCPT_INTEGER_DIVIDE_BY_ZERO   (trap 0)
 *      --  XCPT_ILLEGAL_INSTRUCTION      (trap 6)
 *      --  XCPT_PRIVILEGED_INSTRUCTION
 *      --  XCPT_INTEGER_OVERFLOW         (trap 4)
 *
 *      For these exceptions, we call the functions in debug.c
 *      to try to find debug code or SYM file information about
 *      what source code corresponds to the error.
 *
 *      See excRegisterHooks for the default setup of this.
 *
 *      Note that to get meaningful debugging information
 *      in this handler's traplog, you need the following:
 *
 *      a)  have a MAP file created at link time (/MAP)
 *
 *      b)  convert the MAP to a SYM file using MAPSYM
 *
 *      c)  put the SYM file in the same directory of
 *          the module (EXE or DLL). This must have the
 *          same filestem as the module.
 *
 *      All other exceptions are passed to the next handler
 *      in the exception handler chain. This might be the
 *      C/C++ compiler handler or the default OS/2 handler,
 *      which will probably terminate the process.
 *
 *@@changed V0.9.0 [umoeller]: added support for thread termination
 *@@changed V0.9.2 (2000-03-10) [umoeller]: switched date format to ISO
 *@@changed V0.9.19 (2002-05-07) [umoeller]: added EXCEPTIONREPORTRECORD info so that catch block can check that
 */

ULONG _System excHandlerLoud(PEXCEPTIONREPORTRECORD pReportRec,
                             PEXCEPTIONREGISTRATIONRECORD2 pRegRec2,
                             PCONTEXTRECORD pContextRec,
                             PVOID pv)
{
    /* From the VAC++3 docs:
     *     "The first thing an exception handler should do is check the
     *     exception flags. If EH_EXIT_UNWIND is set, meaning
     *     the thread is ending, the handler tells the operating system
     *     to pass the exception to the next exception handler. It does the
     *     same if the EH_UNWINDING flag is set, the flag that indicates
     *     this exception handler is being removed.
     *     The EH_NESTED_CALL flag indicates whether the exception
     *     occurred within an exception handler. If the handler does
     *     not check this flag, recursive exceptions could occur until
     *     there is no stack remaining."
     * So for all these conditions, we exit immediately.
     */

    if (pReportRec->fHandlerFlags & EH_EXIT_UNWIND)
       return (XCPT_CONTINUE_SEARCH);
    if (pReportRec->fHandlerFlags & EH_UNWINDING)
       return (XCPT_CONTINUE_SEARCH);
    if (pReportRec->fHandlerFlags & EH_NESTED_CALL)
       return (XCPT_CONTINUE_SEARCH);

    switch (pReportRec->ExceptionNum)
    {
        case XCPT_ACCESS_VIOLATION:
        case XCPT_INTEGER_DIVIDE_BY_ZERO:
        case XCPT_ILLEGAL_INSTRUCTION:
        case XCPT_PRIVILEGED_INSTRUCTION:
        case XCPT_INVALID_LOCK_SEQUENCE:
        case XCPT_INTEGER_OVERFLOW:
        {
            // "real" exceptions:
            FILE *file;

            // open traplog file;
            if (G_pfnExcOpenFile)
                // hook defined for this: call it
                file = (*G_pfnExcOpenFile)();
            else
            {
                CHAR szFileName[100];
                // no hook defined: open some
                // default traplog file in root directory of
                // boot drive
                sprintf(szFileName, "%c:\\trap.log", doshQueryBootDrive());
                file = fopen(szFileName, "a");

                if (file)
                {
                    DATETIME DT;
                    DosGetDateTime(&DT);
                    fprintf(file,
                            "\nTrap message -- Date: %04d-%02d-%02d, Time: %02d:%02d:%02d\n",
                            DT.year, DT.month, DT.day,
                            DT.hours, DT.minutes, DT.seconds);
                    fprintf(file, "------------------------------------------------\n");

                }
            }

            // write error log
            excExplainException(file,
                                "excHandlerLoud",
                                pReportRec,
                                pContextRec);
            fclose(file);

            // copy report rec to user buffer
            // V0.9.19 (2002-05-07) [umoeller]
            memcpy(&pRegRec2->err,
                   pReportRec,
                   sizeof(EXCEPTIONREPORTRECORD));

            // jump back to failing routine
            longjmp(pRegRec2->jmpThread, pReportRec->ExceptionNum);
        break; }
    }

    // not handled
    return (XCPT_CONTINUE_SEARCH);
}

/*
 *@@ excHandlerQuiet:
 *      "quiet" xcpt handler, which simply suppresses exceptions;
 *      this is useful for certain error-prone functions, where
 *      exceptions are likely to appear, for example used by
 *      wpshCheckObject to implement a fail-safe SOM object check.
 *
 *      This does _not_ write an error log and makes _no_ sound.
 *      This simply jumps back to the trapping thread or
 *      calls EXCEPTIONREGISTRATIONRECORD2.pfnOnKill.
 *
 *      Other than that, this behaves like excHandlerLoud.
 *
 *      This is best registered thru the TRY_QUIET macro
 *      (new with V0.84, described in except.c), which
 *      does the necessary setup.
 *
 *@@changed V0.9.0 [umoeller]: added support for thread termination
 *@@changed V0.9.19 (2002-05-07) [umoeller]: added EXCEPTIONREPORTRECORD info so that catch block can check that
 */

ULONG _System excHandlerQuiet(PEXCEPTIONREPORTRECORD pReportRec,
                              PEXCEPTIONREGISTRATIONRECORD2 pRegRec2,
                              PCONTEXTRECORD pContextRec,
                              PVOID pv)
{
    if (pReportRec->fHandlerFlags & EH_EXIT_UNWIND)
       return (XCPT_CONTINUE_SEARCH);
    if (pReportRec->fHandlerFlags & EH_UNWINDING)
       return (XCPT_CONTINUE_SEARCH);
    if (pReportRec->fHandlerFlags & EH_NESTED_CALL)
       return (XCPT_CONTINUE_SEARCH);

    switch (pReportRec->ExceptionNum)
    {
        case XCPT_ACCESS_VIOLATION:
        case XCPT_INTEGER_DIVIDE_BY_ZERO:
        case XCPT_ILLEGAL_INSTRUCTION:
        case XCPT_PRIVILEGED_INSTRUCTION:
        case XCPT_INVALID_LOCK_SEQUENCE:
        case XCPT_INTEGER_OVERFLOW:
            // write excpt explanation only if the
            // resp. debugging #define is set (setup.h)
            #ifdef DEBUG_WRITEQUIETEXCPT
            {
                FILE *file = excOpenTraplogFile();
                excExplainException(file,
                                    "excHandlerQuiet",
                                    pReportRec,
                                    pContextRec);
                fclose(file);
            }
            #endif

            // copy report rec to user buffer
            // V0.9.19 (2002-05-07) [umoeller]
            memcpy(&pRegRec2->err,
                   pReportRec,
                   sizeof(EXCEPTIONREPORTRECORD));

            // jump back to failing routine
            longjmp(pRegRec2->jmpThread, pReportRec->ExceptionNum);
        break;

        default:
             break;
    }

    return (XCPT_CONTINUE_SEARCH);
}