source: trunk/src/tools/qsysxcpt_pm.cpp@ 150

Last change on this file since 150 was 150, checked in by dmik, 19 years ago

Common: System Exceptions:

  • Added exploring and writing the context and call stack of all other threads of the process.
  • The 'Process/Module' tag is now a single line (for easier visual lookup with line wrapping turned off).
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 43.1 KB
Line 
1/****************************************************************************
2** $Id: qsysxcpt_pm.cpp 150 2006-11-01 21:55:03Z dmik $
3**
4** OS/2 System Exception handling routines
5**
6** Copyright (C) 2006 netlabs.org.
7**
8** This file is part of the kernel module of the Qt GUI Toolkit.
9**
10** This file may be distributed under the terms of the Q Public License
11** as defined by Trolltech AS of Norway and appearing in the file
12** LICENSE.QPL included in the packaging of this file.
13**
14** This file may be distributed and/or modified under the terms of the
15** GNU General Public License version 2 as published by the Free Software
16** Foundation and appearing in the file LICENSE.GPL included in the
17** packaging of this file.
18**
19** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
20** licenses may use this file in accordance with the Qt Commercial License
21** Agreement provided with the Software.
22**
23** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
24** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
25**
26** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
27** information about Qt Commercial License Agreements.
28** See http://www.trolltech.com/qpl/ for QPL licensing information.
29** See http://www.trolltech.com/gpl/ for GPL licensing information.
30**
31** Contact info@trolltech.com if any conditions of this licensing are
32** not clear to you.
33**
34**********************************************************************/
35
36/*
37 * The below code was started as a partial cut & paste from the except.h
38 * and except.c sources from the xwphelpers package (which is a part of the
39 * xworkplace product, see www.xworkplace.org, xworkplace.netlabs.org).
40 * XWorkplace is Copyright (C) 1999-2002 Ulrich Moeller. It has changed a lot
41 * since then, but thanks to XWP authors for a good example.
42 */
43
44/// @todo (r=dmik) use plain DosWrite() and friends instead of fprintf() and
45// friends (LIBC may be in a inconsistent state at the point when an exception
46// is thrown (see kLIBC's logstrict.c for some sprintf like stuff)
47
48#if !defined(QT_OS2_NO_SYSEXCEPTIONS)
49
50#include "qt_os2.h"
51
52#include <stdio.h>
53#include <stdlib.h>
54#include <stdarg.h>
55#include <unistd.h>
56#include <string.h>
57#include <time.h>
58#include <errno.h>
59#include <sys/stat.h>
60
61/** @class QtOS2SysXcptMainHandler qt_os2.h
62 *
63 * @todo (r=dmik) describe...
64 *
65 * - must be instantiated on the stack of the main thread only
66 * - should be instantiated before a QAplication instance is created
67 * (does nothing otherwise)
68 * - note that callback can be called on any thread
69 */
70
71/* static */
72bool QtOS2SysXcptMainHandler::installed = FALSE;
73QtOS2SysXcptCallback QtOS2SysXcptMainHandler::callback = NULL;
74ERR QtOS2SysXcptMainHandler::libcHandler = NULL;
75
76/**
77 * @todo (r=dmik) describe...
78 */
79QtOS2SysXcptMainHandler::QtOS2SysXcptMainHandler (QtOS2SysXcptCallback cb)
80{
81 rec.prev_structure = NULL;
82 rec.ExceptionHandler = NULL;
83
84 PTIB ptib = NULL;
85 DosGetInfoBlocks (&ptib, NULL);
86 Q_ASSERT (ptib && ptib->tib_ptib2);
87
88 if (ptib && ptib->tib_ptib2)
89 {
90 /* must be instantiated only on the main (first) thread */
91 Q_ASSERT (ptib->tib_ptib2->tib2_ultid == 1);
92 if (ptib->tib_ptib2->tib2_ultid == 1)
93 {
94 /* must not be already instantiated */
95 Q_ASSERT (installed == FALSE);
96 Q_ASSERT (libcHandler == NULL);
97 if (installed == FALSE && libcHandler == NULL)
98 {
99 installed = TRUE;
100 callback = cb;
101 /* install the exception handler for the main thread */
102 rec.ExceptionHandler = handler;
103 DosSetExceptionHandler (&rec);
104 }
105 }
106 }
107}
108
109/**
110 * @todo (r=dmik) describe...
111 */
112QtOS2SysXcptMainHandler::~QtOS2SysXcptMainHandler()
113{
114 Q_ASSERT (rec.ExceptionHandler == handler || rec.ExceptionHandler == NULL);
115 if (rec.ExceptionHandler == handler)
116 {
117 /* uninstall the exception handler for the main thread */
118 DosUnsetExceptionHandler (&rec);
119 rec.ExceptionHandler = NULL;
120 callback = NULL;
121 installed = FALSE;
122 }
123}
124
125static void qt_excEscapeString (FILE *file, const char *pcszStr);
126
127#define XcptPvt QtOS2SysXcptMainHandler::Private
128
129class XcptPvt
130{
131public:
132 static void callbackWriter (const char *msg);
133
134 static inline int askCallback (QtOS2SysXcptReq req)
135 {
136 if (callback)
137 return callback (req, NULL, 0);
138 return FALSE;
139 }
140
141 static inline int letCallback (QtOS2SysXcptReq req)
142 {
143 if (callback)
144 return callback (req, callbackWriter, 0);
145 return FALSE;
146 }
147
148 static FILE *file;
149};
150
151/* static */
152FILE *XcptPvt::file = NULL;
153
154/* static */
155void XcptPvt::callbackWriter (const char *msg)
156{
157 if (file)
158 qt_excEscapeString (file, msg);
159}
160
161/** \internal
162 * Writes the given string with [<>&'"] characters escaped for XML.
163 */
164static void qt_excEscapeString (FILE *file, const char *pcszStr)
165{
166 const char *pcszChars = "<>&'\"";
167 const char *aszEntities[] = { "&lt;", "&gt;", "&amp;", "&apos;", "&quot;" };
168
169 const char *pcsz = pcszStr;
170 const char *pcszRepl = NULL;
171 size_t cbLen = 0;
172
173 if (!pcsz)
174 return;
175
176 while (*pcsz)
177 {
178 cbLen = strcspn (pcsz, pcszChars);
179 fwrite (pcsz, 1, cbLen, file);
180 pcsz += cbLen;
181 if (!*pcsz)
182 break;
183 cbLen = strchr (pcszChars, *pcsz) - pcszChars;
184 pcszRepl = aszEntities[cbLen];
185 fwrite (pcszRepl, 1, strlen(pcszRepl), file);
186 ++ pcsz;
187 }
188}
189
190/** \internal
191 * Writes the given error message.
192 */
193static void qt_excWriteErrorMsg (FILE *file, ULONG ulIndent, const char *pcszMsg,
194 ...)
195{
196 char szBuf[1024];
197 va_list args;
198 va_start (args, pcszMsg);
199 fprintf (file, "%*s<Error message=\"", ulIndent, "");
200 vsnprintf (szBuf, sizeof(szBuf), pcszMsg, args);
201 szBuf[sizeof(szBuf) - 1] = '\0';
202 qt_excEscapeString (file, szBuf);
203 fprintf (file, "\"/>\n");
204 va_end (args);
205}
206
207/** \internal
208 * Writes the register name, value and optionally memory flags
209 */
210static void qt_excWriteReg (FILE *file, const char *pszName, ULONG ulValue,
211 BOOL bQueryMem = TRUE)
212{
213 fprintf (file, " <Register name=\"%s\" value=\"%08lX\"",
214 pszName, ulValue);
215
216 if (bQueryMem)
217 {
218 APIRET arc;
219 ULONG ulCount = 4;
220 ULONG ulFlags = 0;
221 arc = DosQueryMem ((PVOID) ulValue, &ulCount, &ulFlags);
222
223 if (arc == NO_ERROR || arc == ERROR_INVALID_ADDRESS)
224 {
225 if (arc == NO_ERROR)
226 {
227 fprintf (file, " flags=\"%08lX\"", ulFlags);
228 if ((ulFlags & (PAG_COMMIT | PAG_READ)) ==
229 (PAG_COMMIT | PAG_READ))
230 fprintf (file, " word=\"%08lX\"/>\n", *(ULONG *) ulValue);
231 else
232 fprintf (file, "/>\n");
233 }
234 else
235 fprintf (file, " flags=\"invalid\"/>\n");
236 }
237 else
238 {
239 fprintf (file, ">\n");
240 qt_excWriteErrorMsg (file, 7, "DosQueryMem returned %lu "
241 "and flags %08lX", arc, ulFlags);
242 fprintf (file, " </Register>\n");
243 }
244 }
245 else
246 fprintf (file, "/>\n");
247}
248
249/** \internal
250 * Writes information about a signle stack frame.
251 */
252static void qt_excWriteStackFrame (FILE *file, ULONG ulRegEbp, ULONG ulRegEip)
253{
254 APIRET arc = NO_ERROR;
255 HMODULE hMod = NULLHANDLE;
256 char szMod[CCHMAXPATH] = "";
257 ULONG ulObject = 0,
258 ulOffset = 0;
259
260 fprintf (file, " <Frame pointer=\"%08lX\">\n", ulRegEbp);
261 fprintf (file, " <Location address=\"%08lX\">\n", ulRegEip);
262
263 arc = DosQueryModFromEIP (&hMod, &ulObject,
264 sizeof (szMod), szMod, &ulOffset,
265 ulRegEip);
266
267 if (arc != NO_ERROR)
268 qt_excWriteErrorMsg (file, 8, "DosQueryModFromEIP returned %lu", arc);
269 else
270 fprintf (file, " <Module ID=\"%04lX\" segment=\"%04lX\" "
271 "offset=\"%08lX\"/>\n",
272 hMod, ulObject + 1, ulOffset);
273
274 /* write a small memory dump with the location address in the middle */
275 {
276 enum { enmDelta = 8 };
277 UCHAR *pch = (UCHAR *) ulRegEip - enmDelta;
278 UCHAR *pchEnd = (UCHAR *) ulRegEip + enmDelta - 1;
279 ULONG ulCount = enmDelta * 2;
280 ULONG ulFlags = 0;
281 APIRET arc = NO_ERROR;
282 while (1)
283 {
284 arc = DosQueryMem ((void *) (ULONG) pch, &ulCount, &ulFlags);
285 if (arc == NO_ERROR)
286 {
287 if (ulCount >= enmDelta * 2)
288 break;
289 if (pch + ulCount <= (UCHAR *) ulRegEip)
290 {
291 /* ulAddress is outside the pch object */
292 pch += ulCount;
293 ulCount = enmDelta * 2 - ulCount;
294 }
295 else
296 {
297 /* ulAddress is within the pch object */
298 pchEnd = pch += ulCount;
299 break;
300 }
301 }
302 else if (arc == ERROR_INVALID_ADDRESS)
303 {
304 if ((((ULONG) pch) & 0xFFFFF000) == (ulRegEip & 0xFFFFF000))
305 break; /* the same page, ulAddress inaccessible */
306 pch = (UCHAR *) (ulRegEip & 0xFFFFF000);
307 }
308 }
309 fprintf (file, " <Dump address=\"%08lX\">\n ", pch);
310 if (arc == NO_ERROR &&
311 (ulFlags & (PAG_COMMIT | PAG_READ)) == (PAG_COMMIT | PAG_READ))
312 {
313 for (; pch < pchEnd; ++pch)
314 fprintf (file, "%02lX%c", (ULONG) *pch,
315 ulRegEip - (ULONG) pch == 1 ? '-' : ' ');
316 fprintf (file, "\n");
317 }
318 else
319 qt_excWriteErrorMsg (file, 0, "%08lX: DosQueryMem returned %lu "
320 "and flags %08lX", pch, arc, ulFlags);
321 fprintf (file, " </Dump>\n");
322 }
323
324 fprintf (file, " </Location>\n");
325 fprintf (file, " </Frame>\n");
326}
327
328/** \internal
329 * Walks the stack and writes information about stack frames.
330 * @note This function should be in sync with qt_excWriteModulesOnStack()
331 * because they share the same logic.
332 */
333static void qt_excWriteStackFrames (FILE *file, ULONG ulStackLimit,
334 PCONTEXTRECORD pContextRec)
335{
336 ULONG ulRegEbp = pContextRec->ctx_RegEbp;
337 ULONG ulRegEip = pContextRec->ctx_RegEip;
338
339 fprintf (file, " <Frames>\n");
340
341 /* first the trapping address itself */
342 qt_excWriteStackFrame (file, ulRegEbp, ulRegEip);
343
344 /* first call to qt_excWriteStackFrame() is done before the EBP validity
345 * check below to get a chance to call qt_excWriteStackFrame() for the
346 * trapping address itself even if EBP there is invalid. */
347
348 while (ulRegEbp != 0 && ulRegEbp < ulStackLimit)
349 {
350 /* skip the trapping stack frame -- already written above */
351 if (pContextRec->ctx_RegEbp != ulRegEbp)
352 qt_excWriteStackFrame (file, ulRegEbp, ulRegEip);
353
354 if ((ulRegEbp & 0x00000FFF) == 0x00000000)
355 {
356 /* we're on a page boundary: check access */
357 ULONG ulCount = 0x1000;
358 ULONG ulFlags = 0;
359 APIRET arc = DosQueryMem ((void *) ulRegEbp, &ulCount, &ulFlags);
360 if ( (arc != NO_ERROR)
361 || ( arc == NO_ERROR
362 && (ulFlags & (PAG_COMMIT | PAG_READ))
363 != (PAG_COMMIT | PAG_READ)))
364 {
365 fprintf (file, " <Frame pointer=\"%08lX\">\n", ulRegEbp);
366 qt_excWriteErrorMsg (file, 7, "DosQueryMem returned %lu "
367 "and flags %08lX", arc, ulFlags);
368 fprintf (file, " </Frame>\n");
369 /* try go to the next page */
370 /// @todo (r=dmik) I don't know how much is it accurate,
371 // I've just taken the logic from xwphelpers sources.
372 ulRegEbp += 0x1000;
373 continue; /* while */
374 }
375 }
376
377 /* get the return address of the current call */
378 ulRegEip = *(((PULONG) ulRegEbp) + 1);
379 /* get the address of the outer stack frame */
380 ulRegEbp = *((PULONG) ulRegEbp);
381 }
382
383 fprintf (file, " </Frames>\n");
384}
385
386/** \internal
387 * Writes the thread information.
388 * If ptib is not NULL, the current thread's information is to be written.
389 * If ptib is NULL, pThrdRec is guaranted not to be NULL.
390 */
391static void qt_excWriteThreadInfo (FILE *file, PTIB ptib, QSTREC *pThrdRec,
392 PCONTEXTRECORD pContextRec)
393{
394 ULONG ulStackBase = 0, ulStackLimit = 0;
395 ULONG ulCount = 0, ulFlags = 0;
396 ULONG ul = 0;
397 APIRET arc = NO_ERROR;
398
399 if (ptib)
400 {
401 fprintf (file, " <Thread ID=\"%04lX\" slot=\"%04lX\" "
402 "priority=\"%04lX\" ",
403 ptib->tib_ptib2->tib2_ultid, ptib->tib_ordinal,
404 ptib->tib_ptib2->tib2_ulpri);
405 if (pThrdRec)
406 fprintf (file, "state=\"%02lX\" ", (ULONG) pThrdRec->state);
407 fprintf (file, "mc=\"%04lX\" mcf=\"%04lX\">\n",
408 ptib->tib_ptib2->tib2_usMCCount,
409 ptib->tib_ptib2->tib2_fMCForceFlag);
410 }
411 else
412 {
413 fprintf (file, " <Thread ID=\"%04lX\" slot=\"%04lX\" "
414 "priority=\"%04lX\" state=\"%02lX\">\n",
415 pThrdRec->tid, pThrdRec->slot, pThrdRec->priority,
416 pThrdRec->state);
417 }
418
419 /* *** registers */
420
421 fprintf (file, " <CPU>\n"
422 " <Registers>\n");
423
424 if (pContextRec->ContextFlags & CONTEXT_SEGMENTS)
425 {
426 qt_excWriteReg (file, "DS", pContextRec->ctx_SegDs, FALSE);
427 qt_excWriteReg (file, "ES", pContextRec->ctx_SegEs, FALSE);
428 qt_excWriteReg (file, "FS", pContextRec->ctx_SegFs, FALSE);
429 qt_excWriteReg (file, "GS", pContextRec->ctx_SegGs, FALSE);
430 }
431
432 if (pContextRec->ContextFlags & CONTEXT_INTEGER)
433 {
434 qt_excWriteReg (file, "EAX", pContextRec->ctx_RegEax);
435 qt_excWriteReg (file, "EBX", pContextRec->ctx_RegEbx);
436 qt_excWriteReg (file, "ECX", pContextRec->ctx_RegEcx);
437 qt_excWriteReg (file, "EDX", pContextRec->ctx_RegEdx);
438 qt_excWriteReg (file, "ESI", pContextRec->ctx_RegEsi);
439 qt_excWriteReg (file, "EDI", pContextRec->ctx_RegEdi);
440 }
441
442 if (pContextRec->ContextFlags & CONTEXT_CONTROL)
443 {
444 qt_excWriteReg (file, "CS", pContextRec->ctx_SegCs, FALSE);
445 qt_excWriteReg (file, "EIP", pContextRec->ctx_RegEip);
446 qt_excWriteReg (file, "SS", pContextRec->ctx_SegSs, FALSE);
447 qt_excWriteReg (file, "ESP", pContextRec->ctx_RegEsp);
448 qt_excWriteReg (file, "EBP", pContextRec->ctx_RegEbp);
449 qt_excWriteReg (file, "EFLAGS", pContextRec->ctx_EFlags, FALSE);
450 }
451
452 fprintf (file, " </Registers>\n"
453 " </CPU>\n");
454
455 /* *** stack */
456
457 if (ptib)
458 {
459 ulStackBase = (ULONG) ptib->tib_pstack;
460 ulStackLimit = (ULONG) ptib->tib_pstacklimit;
461 }
462 else
463 if (pContextRec->ContextFlags & CONTEXT_CONTROL)
464 {
465 /* first, try to find the end of the stack object */
466 ulCount = 0xFFFFFFFF;
467 arc = DosQueryMem ((void *) pContextRec->ctx_RegEbp,
468 &ulCount, &ulFlags);
469 if (arc == NO_ERROR)
470 {
471 ulStackLimit = pContextRec->ctx_RegEbp + ulCount;
472 /* next, try to find the start of the stack object */
473 ul = (pContextRec->ctx_RegEbp & 0xFFFFF000);
474 while (arc == NO_ERROR && !(ulFlags & PAG_BASE))
475 {
476 ulCount = 0x1000;
477 ul -= 0x1000;
478 arc = DosQueryMem ((void *) ul, &ulCount, &ulFlags);
479 }
480 if (arc == NO_ERROR)
481 ulStackBase = ul;
482 }
483 }
484
485 fprintf (file, " <Stack");
486 if (ulStackBase)
487 fprintf (file, " base=\"%08lX\"", ulStackBase);
488 if (ulStackLimit)
489 fprintf (file, " limit=\"%08lX\"", ulStackLimit);
490 fprintf (file, ">\n");
491
492 if (pContextRec->ContextFlags & CONTEXT_CONTROL)
493 qt_excWriteStackFrames (file, ulStackLimit, pContextRec);
494
495 fprintf (file, " </Stack>\n");
496 fprintf (file, " </Thread>\n");
497}
498
499#ifndef max
500#define max(a,b) (((a) > (b)) ? (a) : (b))
501#endif
502
503/** @internal
504 * Writes the module description string (if found) as the 'desciption'
505 * attribute. Returns TRUE if the description was found and written.
506 */
507static BOOL qt_excWriteModuleDesc (FILE *file, char *pszFileName)
508{
509 /* see OS2TK\h\exe.h, OS2TK\h\exe386.h and LXSPEC.INF for details */
510 enum { EXEID = 0x5a4d, LXOffset = 0x3C, sizeof_exe = LXOffset + 4,
511 E32MAGIC = 0x584c, e32_nrestab = 34 * 4,
512 sizeof_e32 = e32_nrestab + 4,
513 max_nresname = 255 + 1 /* trailing 0 */ };
514
515 UCHAR achBuf [max (max_nresname, max (sizeof_e32, sizeof_exe))];
516
517 BOOL bFound = FALSE;
518 ULONG ul1 = 0, ul2 = 0;
519
520 HFILE hf = NULL;
521 APIRET arc = NO_ERROR;
522
523 arc = DosOpen (pszFileName, &hf, &ul1,
524 0, 0, OPEN_ACTION_OPEN_IF_EXISTS,
525 OPEN_SHARE_DENYWRITE | OPEN_ACCESS_READONLY,
526 NULL);
527 if (arc != NO_ERROR)
528 return FALSE;
529
530 do
531 {
532 /* read the old EXE header plus the offset to the LX header */
533 arc = DosRead (hf, achBuf, sizeof_exe, &ul1);
534 if (arc != NO_ERROR || sizeof_exe != ul1)
535 break;
536
537 if (*(USHORT *) &achBuf == EXEID)
538 {
539 /* go to the LX header */
540 ul1 = *(ULONG *) &achBuf [LXOffset];
541 arc = DosSetFilePtr (hf, (LONG) ul1, FILE_BEGIN, &ul2);
542 if (arc != NO_ERROR || ul1 != ul2)
543 break;
544
545 /* read it upto the e32_nrestab field */
546 arc = DosRead (hf, achBuf, sizeof_e32, &ul1);
547 if (arc != NO_ERROR || sizeof_e32 != ul1 ||
548 *(USHORT *) &achBuf != E32MAGIC)
549 break;
550 }
551 else
552 if (*(USHORT *) &achBuf == E32MAGIC)
553 {
554 /* there may be no old EXE header, but LX only,
555 * read it upto the e32_nrestab field */
556 if (sizeof_e32 > sizeof_exe)
557 {
558 arc = DosRead (hf, achBuf + sizeof_exe,
559 sizeof_e32 - sizeof_exe, &ul1);
560 if (arc != NO_ERROR || sizeof_e32 - sizeof_exe != ul1)
561 break;
562 }
563 }
564 else
565 break;
566
567 /* go to the beginning of the non-resident name table */
568 ul1 = *(ULONG *) &achBuf [e32_nrestab];
569 if (ul1)
570 {
571 arc = DosSetFilePtr (hf, (LONG) ul1, FILE_BEGIN, &ul2);
572 if (arc != NO_ERROR || ul1 != ul2)
573 break;
574
575 /* read the first entry */
576 arc = DosRead (hf, achBuf, max_nresname, &ul1);
577 if (arc != NO_ERROR || max_nresname != ul1)
578 break;
579
580 /* if the ordinal is 0, then it's a description field (we don't do
581 * the full table search, since it shoud be ordered by ordinals) */
582 if (*(USHORT *) &achBuf [1 + *achBuf] == 0)
583 {
584 /* place the trailing zero */
585 achBuf [1 + *achBuf] = 0;
586 /* write the description attribute */
587 fprintf (file, " desc=\"%s\"", achBuf + 1);
588 bFound = TRUE;
589 }
590 }
591 }
592 while (0);
593
594 DosClose (hf);
595 return bFound;
596}
597
598/** \internal
599 * Writes module information to the log file.
600 * \a ulOrigin is 1 (self), 2 (imported), or 3 (loaded). Other values are
601 * ignored.
602 */
603static void qt_excWriteModule (FILE *file, USHORT hmte, char *pszName,
604 ULONG ulOrigin = 0)
605{
606 static const char *apszOrigins[] = { "self", "imported", "loaded" };
607
608 FILESTATUS3 Status;
609 APIRET arc = NO_ERROR;
610
611 fprintf (file, " <Module ID=%\"%04lX\" name=\"", hmte);
612 qt_excEscapeString (file, pszName);
613 fprintf (file, "\"");
614
615 arc = DosQueryPathInfo (pszName, FIL_STANDARD, &Status, sizeof (Status));
616
617 if ((ulOrigin >= 1 && ulOrigin <= 3) || arc == NO_ERROR)
618 {
619 if (ulOrigin >= 1 && ulOrigin <= 3)
620 fprintf (file, " origin=\"%s\"",
621 apszOrigins [ulOrigin - 1]);
622 if (arc == NO_ERROR)
623 fprintf (file, " timestamp=\"%04d-%02d-%02dT%02d:%02d:%02d\"",
624 Status.fdateLastWrite.year + 1980,
625 Status.fdateLastWrite.month, Status.fdateLastWrite.day,
626 Status.ftimeLastWrite.hours, Status.ftimeLastWrite.minutes,
627 Status.ftimeLastWrite.twosecs * 2);
628 }
629
630 qt_excWriteModuleDesc (file, pszName);
631
632 fprintf (file, "/>\n");
633}
634
635/** \internal
636 * Writes module and all its imports information to the log file.
637 * \a ulOrigin is 1 (self), or 3 (loaded), other values are ignored.
638 */
639static void qt_excWriteModules (FILE *file, USHORT hmte, QSLREC *pFirstLibRec,
640 ULONG ulOrigin)
641{
642 QSLREC *pLibRec = pFirstLibRec;
643 while (pLibRec)
644 {
645 if (pLibRec->hmte == hmte &&
646 pLibRec->pName /* not yet visited */)
647 {
648 qt_excWriteModule (file, hmte, (char *) pLibRec->pName, ulOrigin);
649 /* mark as visited */
650 pLibRec->pName = NULL;
651 /* go through imports */
652 if (pLibRec->ctImpMod)
653 {
654 USHORT *pHmte = (USHORT *) (pLibRec + 1);
655 for (ULONG i = 0; i < pLibRec->ctImpMod; ++ i, ++ pHmte)
656 qt_excWriteModules (file, *pHmte, pFirstLibRec,
657 ulOrigin == 1 ? 2 : ulOrigin);
658 break;
659 }
660 }
661 pLibRec = (QSLREC *) pLibRec->pNextRec;
662 }
663}
664
665/** @internal
666 * Struct for recursive qt_excWriteModulesOnStack() to reduce stack usage
667 */
668typedef struct _WMOS_STATE
669{
670 FILE *file;
671 ULONG ulRegEbp;
672 ULONG ulRegEip;
673 ULONG ulStackLimit;
674 USHORT hmteExe;
675 ULONG cLevel;
676 USHORT *pHmteOuter;
677} WMOS_STATE;
678
679/** \internal
680 * Walks the stack and writes information about all encountered modules.
681 * Used only when DosQuerySysState() fails (so qt_excWriteModules() is not
682 * applicable). We use recursiveness to avoid module record duplicates.
683 * @note This function should be in sync with qt_excWriteStackFrames()
684 * because they share the same logic.
685 */
686static void qt_excWriteModulesOnStack (WMOS_STATE *pState)
687{
688 USHORT hmteThis = 0;
689
690 {
691 APIRET arc = NO_ERROR;
692 char szMod [CCHMAXPATH] = "";
693 ULONG ulObject = 0, ulOffset = 0;
694
695 arc = DosQueryModFromEIP ((HMODULE *) &hmteThis, &ulObject,
696 sizeof (szMod), szMod, &ulOffset,
697 pState->ulRegEip);
698 if (arc == NO_ERROR && hmteThis != pState->hmteExe)
699 {
700 /* look if we've already seen this module using the hmteThis chain
701 * on the stack */
702 USHORT *pH = &hmteThis;
703 /* calculate distance between recursive hmteThis on the stack */
704 ULONG ulOuter = (ULONG) pState->pHmteOuter - (ULONG) pH;
705 ULONG cL = pState->cLevel;
706 while (cL != 0)
707 {
708 pH = (USHORT *)(((ULONG) pH) + ulOuter);
709 if (*pH == hmteThis)
710 break;
711 -- cL;
712 }
713 if (cL == 0)
714 {
715 /* got an unique module handle */
716 DosQueryModuleName (hmteThis, sizeof (szMod), szMod);
717 qt_excWriteModule (pState->file, hmteThis, szMod);
718 }
719 }
720 }
721
722 /* we do EBP validity check here as well as before the recursive
723 * call below to get a chance for the above code (EIP to hmod translation)
724 * to be executed even if EBP there is invalid. */
725
726 if (pState->ulRegEbp != 0 && pState->ulRegEbp < pState->ulStackLimit)
727 {
728 if ((pState->ulRegEbp & 0x00000FFF) == 0x00000000)
729 {
730 /* we're on a page boundary: check access */
731 while (pState->ulRegEbp < pState->ulStackLimit)
732 {
733 ULONG ulCount = 0x1000;
734 ULONG ulFlags = 0;
735 APIRET arc = DosQueryMem ((void *) pState->ulRegEbp,
736 &ulCount, &ulFlags);
737 if ( (arc != NO_ERROR)
738 || ( arc == NO_ERROR
739 && (ulFlags & (PAG_COMMIT | PAG_READ))
740 != (PAG_COMMIT | PAG_READ)))
741 {
742 /* try go to the next page */
743 /// @todo (r=dmik) I don't know how much is it accurate,
744 // I've just taken the logic from xwphelpers sources.
745 pState->ulRegEbp += 0x1000;
746 continue; /* while */
747 }
748 break;
749 }
750 }
751
752 /* get the return address of the current call */
753 pState->ulRegEip = *(((PULONG) pState->ulRegEbp) + 1);
754 /* get the address of the outer stack frame */
755 pState->ulRegEbp = *((PULONG) pState->ulRegEbp);
756
757 if (pState->ulRegEbp != 0 && pState->ulRegEbp < pState->ulStackLimit)
758 {
759 pState->cLevel ++;
760 pState->pHmteOuter = &hmteThis;
761 qt_excWriteModulesOnStack (pState);
762 }
763 }
764}
765
766/** @internal Simple process info struct */
767typedef struct _PROCESSINFO
768{
769 /* common values */
770 char *pszFullName;
771 char *pszBaseName;
772 /* direct info pointers */
773 PPIB ppib;
774 PTIB ptib;
775 BOOL bHaveSysState; /**< TRUE when both pProcRec and pLibRec are not NULL */
776 QSPREC *pProcRec; /**< NULL when bHaveSysState is FALSE */
777 QSLREC *pLibRec; /**< NULL when bHaveSysState is FALSE */
778} PROCESSINFO;
779
780/** \internal
781 * Writes exception information to the log file.
782 */
783static void qt_excWriteException (FILE *file,
784 PROCESSINFO *pInfo,
785 PEXCEPTIONREPORTRECORD pReportRec,
786 PCONTEXTRECORD pContextRec)
787{
788 ULONG ul = 0;
789 ULONG aulBuf [3];
790
791 QSTREC *pThrdRec = NULL;
792
793 /* *** application info */
794
795 {
796 fprintf (file, " <Application name=\"");
797 if (XcptPvt::askCallback (QtOS2SysXcptReq_AppName) == TRUE)
798 XcptPvt::letCallback (QtOS2SysXcptReq_AppName);
799 else
800 qt_excEscapeString (file, pInfo->pszBaseName);
801 fprintf (file, "\" version=\"");
802 if (XcptPvt::askCallback (QtOS2SysXcptReq_AppVer) == TRUE)
803 XcptPvt::letCallback (QtOS2SysXcptReq_AppVer);
804 else
805 fprintf (file, "unknown");
806 fprintf (file, "\">\n");
807
808 if (XcptPvt::askCallback (QtOS2SysXcptReq_ReportTo) == TRUE)
809 {
810 fprintf (file, " <Report to=\"");
811 XcptPvt::letCallback (QtOS2SysXcptReq_ReportTo);
812 if (XcptPvt::askCallback (QtOS2SysXcptReq_ReportSubj) == TRUE)
813 {
814 fprintf (file, "\" subject=\"");
815 XcptPvt::letCallback (QtOS2SysXcptReq_ReportSubj);
816 }
817 fprintf (file, "\"/>\n");
818 }
819
820 fprintf (file, " </Application>\n");
821 }
822
823 /* *** generic exception info */
824
825 fprintf (file,
826 " <Exception type=\"%08lX\" flags=\"%08lX\" address=\"%08lX\">\n",
827 pReportRec->ExceptionNum, pReportRec->fHandlerFlags,
828 (ULONG) pReportRec->ExceptionAddress);
829
830 for (ul = 0; ul < pReportRec->cParameters; ++ul)
831 {
832 fprintf (file, " <Param value=\"%08lX\"/>\n",
833 pReportRec->ExceptionInfo [ul]);
834 }
835
836 fprintf (file, " </Exception>\n");
837
838 /* *** system info */
839
840 DosQuerySysInfo (QSV_VERSION_MAJOR, QSV_VERSION_REVISION,
841 &aulBuf, sizeof (aulBuf));
842 /* Warp 3 is reported as 20.30 */
843 /* Warp 4 is reported as 20.40 */
844 /* Aurora is reported as 20.45 */
845
846 fprintf (file,
847 " <System name=\"OS/2\" version=\"%u.%u.%u\"/>\n",
848 aulBuf[0], aulBuf[1], aulBuf[2]);
849
850 /* *** process info */
851
852 fprintf (file, " <Process ID=\"%04lX\" parentID=\"%04lX\">\n",
853 pInfo->ppib->pib_ulpid, pInfo->ppib->pib_ulppid);
854
855 fprintf (file, " <Modules>\n");
856 if (pInfo->bHaveSysState)
857 {
858 /* write process module and it's imports */
859 qt_excWriteModules (file, pInfo->ppib->pib_hmte, pInfo->pLibRec, 1);
860 /* write loaded modules and their imports */
861 if (pInfo->pProcRec->cLib && pInfo->pProcRec->pLibRec)
862 {
863 for (ULONG i = 0; i < pInfo->pProcRec->cLib; ++ i)
864 qt_excWriteModules (file, pInfo->pProcRec->pLibRec [i],
865 pInfo->pLibRec, 3);
866 }
867 }
868 else
869 {
870 qt_excWriteModule (file, pInfo->ppib->pib_hmte, pInfo->pszFullName, 1);
871 WMOS_STATE State = { file, pContextRec->ctx_RegEbp,
872 pContextRec->ctx_RegEip,
873 (ULONG) pInfo->ptib->tib_pstacklimit,
874 pInfo->ppib->pib_hmte,
875 0 /* cLevel */, NULL /* pHmteOuter */ };
876 qt_excWriteModulesOnStack (&State);
877 }
878 fprintf (file, " </Modules>\n");
879
880 fprintf (file, " <Threads>\n");
881
882 /* first, the current thread */
883 pThrdRec = pInfo->bHaveSysState ? pInfo->pProcRec->pThrdRec : NULL;
884 if (pThrdRec)
885 {
886 /* locate the current thread structure */
887 for (ul = 0; ul < pInfo->pProcRec->cTCB; ++ ul, ++ pThrdRec)
888 if ((ULONG) pThrdRec->tid == pInfo->ptib->tib_ptib2->tib2_ultid)
889 break;
890 if (ul == pInfo->pProcRec->cTCB)
891 pThrdRec = NULL;
892 }
893 qt_excWriteThreadInfo (file, pInfo->ptib, pThrdRec, pContextRec);
894
895 /* then, all other threads */
896 pThrdRec = pInfo->bHaveSysState ? pInfo->pProcRec->pThrdRec : NULL;
897 if (pThrdRec)
898 {
899 /* important to set ContextFlags to CONTEXT_FULL, otherwise we'll
900 * get nothing */
901 CONTEXTRECORD ContextRec = {CONTEXT_FULL, 0};
902 APIRET arc = NO_ERROR;
903 /* enter the critical section to effectively suspend all other threads
904 * while reading their contexts and walking their stacks */
905 DosEnterCritSec();
906 for (ul = 0; ul < pInfo->pProcRec->cTCB; ++ ul, ++ pThrdRec)
907 {
908 if ((ULONG) pThrdRec->tid == pInfo->ptib->tib_ptib2->tib2_ultid)
909 continue;
910 arc = DosQueryThreadContext (pThrdRec->tid, CONTEXT_FULL,
911 &ContextRec);
912 if (arc == NO_ERROR)
913 qt_excWriteThreadInfo (file, NULL, pThrdRec, &ContextRec);
914 else
915 {
916 fprintf (file, " <Thread ID=\"%04lX\">", pThrdRec->tid);
917 qt_excWriteErrorMsg (file, 3, "DosQueryThreadContext returned "
918 "%lu", arc);
919 fprintf (file, " </Thread>\n");
920 }
921 }
922 /* exit the critical section */
923 DosExitCritSec();
924 }
925
926 fprintf (file, " </Threads>\n");
927
928 fprintf (file, " </Process>\n");
929}
930
931/**
932 * Opens an unique log file using \a pcszBasePath as the base path.
933 * On input, \a pszFileName is the desired base file name w/o extension.
934 * If it doesn't fit into the file name length limit (ENAMETOOLONG) for the
935 * FS of the given disk, then a 8x3 file name is attemptet as a fallback.
936 * On output, \a pszFileName will contain the full file name of the opened
937 * log file. Note that \a pszFileName must be at least CCHMAXPATH bytes length.
938 */
939static FILE *qt_excOpenLogFile (const char *pcszBasePath, char *pszFileName)
940{
941 FILE *file = NULL;
942
943 char szFileName[CCHMAXPATH];
944 char szDir[] = "\\.systrap";
945 char szFile[] = "\\12345678.123";
946 enum { cbFileName = sizeof(szFileName) };
947 enum { cbDir = sizeof(szDir) };
948 enum { cbFile = sizeof(szFile) };
949
950 ULONG ul = 0;
951 ULONG cbDirLen = 0;
952 ULONG cbLen = 0;
953 ULONG ulStamp = 0;
954 char *pszStamp = NULL;
955
956 if (pcszBasePath == NULL || pszFileName == NULL)
957 return NULL;
958
959 if (access (pcszBasePath, F_OK) != 0)
960 return NULL;
961
962 if (strlen (pcszBasePath) + cbDir + cbFile - 2 >= CCHMAXPATH)
963 return NULL;
964
965 /* get the full path if it's not 'X:' */
966 if (strlen(pcszBasePath) != 2 ||
967 pcszBasePath[1] != ':')
968 {
969 if (DosQueryPathInfo (pcszBasePath, FIL_QUERYFULLNAME,
970 szFileName, cbFileName) != NO_ERROR)
971 return NULL;
972 }
973 else
974 strcpy (szFileName, pcszBasePath);
975
976 strcat (szFileName, szDir);
977 if (access (szFileName, F_OK) != 0)
978 if (mkdir (szFileName, 0777) != 0)
979 return NULL;
980
981 cbDirLen = strlen (szFileName);
982
983 /* first, try to use the desired base file name */
984
985 /* we will append -NN to the file name to add some 'fraction of a
986 * second' granularity to avoid name conflicts (0 <= NNN <= 99) */
987 cbLen = strlen (pszFileName);
988 if (cbDirLen + cbLen + 12 /* \-NN.trp.xml */ < CCHMAXPATH)
989 {
990 strcat (szFileName, "\\");
991 strcat (szFileName, pszFileName);
992 cbLen += cbDirLen + 1 /* \ */;
993 for (ul = 0; ul < 100; ++ul)
994 {
995 sprintf (szFileName + cbLen, "-%02ld.trp.xml", ul);
996 if (access (szFileName, F_OK) == 0)
997 continue;
998 file = fopen (szFileName, "wt");
999 if (file)
1000 break;
1001 if (errno == ENAMETOOLONG)
1002 break;
1003 }
1004 }
1005
1006 /* next, try a time stamp as a 8x3 file name */
1007
1008 if (file == NULL)
1009 {
1010 pszStamp = szFileName + cbDirLen + 1;
1011 strcat (szFileName, szFile);
1012
1013 ulStamp = time (NULL);
1014
1015 /* In order to add some 'fraction of a second' granularity to the
1016 * timestamp, we shift it by 5 bits. This gives us 0x07FFFFFF seconds
1017 * which is a period of approx. 4,25 years. 5 bits in turn give us 32
1018 * fractions of a second. */
1019 ulStamp <<= 5;
1020
1021 /* try some few next stamps if the first one fails */
1022 for (ul = 0; ul < 32; ++ul, ++ulStamp)
1023 {
1024 sprintf (pszStamp, "%08lX.xml", ulStamp);
1025 if (access (szFileName, F_OK) == 0)
1026 continue;
1027 file = fopen (szFileName, "wt");
1028 if (file)
1029 break;
1030 }
1031 }
1032
1033 if (file)
1034 {
1035 strncpy (pszFileName, szFileName, CCHMAXPATH - 1);
1036 pszFileName[CCHMAXPATH - 1] = '\0';
1037 }
1038
1039 return file;
1040}
1041
1042/** \internal
1043 * Qt Exception handler.
1044 */
1045/* static */
1046ULONG APIENTRY
1047QtOS2SysXcptMainHandler::handler (PEXCEPTIONREPORTRECORD pReportRec,
1048 PEXCEPTIONREGISTRATIONRECORD pRegRec,
1049 PCONTEXTRECORD pContextRec,
1050 PVOID pv)
1051{
1052 PROCESSINFO Info = {0};
1053
1054 /* From the VAC++3 docs:
1055 * "The first thing an exception handler should do is check the
1056 * exception flags. If EH_EXIT_UNWIND is set, meaning
1057 * the thread is ending, the handler tells the operating system
1058 * to pass the exception to the next exception handler. It does the
1059 * same if the EH_UNWINDING flag is set, the flag that indicates
1060 * this exception handler is being removed.
1061 * The EH_NESTED_CALL flag indicates whether the exception
1062 * occurred within an exception handler. If the handler does
1063 * not check this flag, recursive exceptions could occur until
1064 * there is no stack remaining."
1065 *
1066 * So for all these conditions, we exit immediately.
1067 */
1068
1069 if (pReportRec->fHandlerFlags &
1070 (EH_EXIT_UNWIND | EH_UNWINDING | EH_NESTED_CALL))
1071 return XCPT_CONTINUE_SEARCH;
1072
1073 /* setup the process info structure */
1074 DosGetInfoBlocks (&Info.ptib, &Info.ppib);
1075
1076 switch (pReportRec->ExceptionNum)
1077 {
1078 case XCPT_ACCESS_VIOLATION:
1079 case XCPT_INTEGER_DIVIDE_BY_ZERO:
1080 case XCPT_ILLEGAL_INSTRUCTION:
1081 case XCPT_PRIVILEGED_INSTRUCTION:
1082 case XCPT_INVALID_LOCK_SEQUENCE:
1083 case XCPT_INTEGER_OVERFLOW:
1084 {
1085 /* "real" exceptions: */
1086
1087 APIRET arc = NO_ERROR;
1088 PVOID pBuf = NULL;
1089
1090 char szFullExeName [CCHMAXPATH] = "";
1091 char szBaseExeName [CCHMAXPATH] = "";
1092
1093 TIB2 Tib2Orig;
1094 DATETIME dt;
1095
1096 char szFileName [CCHMAXPATH];
1097 FILE *file = NULL;
1098
1099 /* raise this thread's priority to do it fast */
1100 Tib2Orig = *Info.ptib->tib_ptib2;
1101 Info.ptib->tib_ptib2 = &Tib2Orig;
1102 DosSetPriority (PRTYS_THREAD, PRTYC_REGULAR, PRTYD_MAXIMUM, 0);
1103
1104 DosBeep (880, 200);
1105
1106 /* query system state */
1107 {
1108 ULONG ulCB = 64 * 1024;
1109 ULONG cTries = 3;
1110 while (-- cTries)
1111 {
1112 arc = DosAllocMem (&pBuf, ulCB, PAG_WRITE | PAG_COMMIT);
1113 if (arc == NO_ERROR)
1114 {
1115 memset (pBuf, 0, ulCB);
1116 arc = DosQuerySysState (QS_PROCESS | QS_MTE, 0,
1117 Info.ppib->pib_ulpid, 0,
1118 pBuf, ulCB);
1119 if (arc == NO_ERROR)
1120 {
1121 QSPTRREC *pPtrRec = (QSPTRREC *) pBuf;
1122 Info.bHaveSysState = TRUE;
1123 Info.pProcRec = pPtrRec->pProcRec;
1124 Info.pLibRec = pPtrRec->pLibRec;
1125 }
1126 else if (arc == ERROR_BUFFER_OVERFLOW)
1127 {
1128 /* retry with some bigger buffer size */
1129 DosFreeMem (pBuf);
1130 pBuf = NULL;
1131 ulCB *= 2;
1132 continue;
1133 }
1134 }
1135 break;
1136 }
1137 }
1138
1139 /* get the main module name */
1140 DosQueryModuleName (Info.ppib->pib_hmte,
1141 sizeof (szFullExeName), szFullExeName);
1142 {
1143 /* find the base name (w/o path and extension) */
1144 char *psz = strrchr (szFullExeName, '\\');
1145 if (psz)
1146 ++ psz;
1147 if (!psz)
1148 psz = szFullExeName;
1149 strcpy (szBaseExeName, psz);
1150 psz = strrchr (szBaseExeName, '.');
1151 if (psz)
1152 *psz = '\0';
1153 Info.pszFullName = szFullExeName;
1154 Info.pszBaseName = szBaseExeName;
1155 }
1156
1157 /* compose the desired base file name for the log file
1158 * (<datetime>-<exe>) */
1159 DosGetDateTime (&dt);
1160 sprintf (szFileName, "%04d%02d%02d%02d%02d%02d-%s",
1161 dt.year, dt.month, dt.day,
1162 dt.hours, dt.minutes, dt.seconds,
1163 szBaseExeName);
1164
1165 /* open the log file: */
1166
1167 /* first, try the home directory, then the root drive
1168 * (see QDir::homeDirPath()) */
1169 file = qt_excOpenLogFile (getenv ("HOME"), szFileName);
1170 if (file == NULL)
1171 {
1172 char *pszHomeDrive = getenv ("HOMEDRIVE");
1173 char *pszHomePath = getenv ("HOMEPATH");
1174 if (pszHomeDrive && pszHomePath &&
1175 strlen (pszHomeDrive) + strlen (pszHomePath) < CCHMAXPATH)
1176 {
1177 char szHomePath [CCHMAXPATH];
1178 strcpy (szHomePath, pszHomeDrive);
1179 strcat (szHomePath, pszHomePath);
1180 file = qt_excOpenLogFile (szHomePath, szFileName);
1181 }
1182 }
1183 if (file == NULL)
1184 {
1185 char szBootDrive[] = "\0:";
1186 ULONG ulBootDrive;
1187 DosQuerySysInfo (QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
1188 &ulBootDrive, sizeof(ulBootDrive));
1189 szBootDrive[0] = (char) ulBootDrive + 'A' - 1;
1190 file = qt_excOpenLogFile (szBootDrive, szFileName);
1191 }
1192
1193 if (file != NULL)
1194 {
1195 fprintf (file,
1196 "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
1197 "<!--\n"
1198 " A fatal error has occured while running this application.\n"
1199 " Please contact the application vendor, describe what happened\n"
1200 " and send the contents of this file saved locally as\n"
1201 " '%s'.\n"
1202 "-->\n",
1203 szFileName);
1204
1205 fprintf (file,
1206 "<Trap timestamp=\"%04d-%02d-%02dT%02d:%02d:%02d\"" //%c%02d:%02d\""
1207 " version=\"1.0\"\n"
1208 " generator=\"Qt Library Version %s\"\n"
1209 " xmlns=\"http://db.hugaida.com/xml/os2/sys/Trap\">\n",
1210 dt.year, dt.month, dt.day,
1211 dt.hours, dt.minutes, dt.seconds,
1212 //(dt.timezone > 0 ? '-' : '+'),
1213 //abs (dt.timezone / 60),
1214 //abs (dt.timezone % 60),
1215 QT_VERSION_STR);
1216
1217 XcptPvt::file = file;
1218
1219 /* write trap information */
1220 qt_excWriteException (file, &Info, pReportRec, pContextRec);
1221
1222 XcptPvt::file = NULL;
1223
1224 fprintf (file, "</Trap>\n\n");
1225 fclose (file);
1226
1227 /* attempt to display the created file */
1228 {
1229 STARTDATA SData = {0};
1230 PID pid = 0;
1231 ULONG ulSessID = 0;
1232
1233 SData.Length = sizeof (STARTDATA);
1234 SData.PgmName = "E.EXE";
1235 SData.PgmInputs = szFileName;
1236
1237 DosStartSession (&SData, &ulSessID, &pid);
1238 }
1239 }
1240 else
1241 DosBeep (220, 200);
1242
1243 if (pBuf != NULL)
1244 DosFreeMem (pBuf);
1245
1246 /* reset old priority */
1247 DosSetPriority (PRTYS_THREAD, (Tib2Orig.tib2_ulpri & 0x0F00) >> 8,
1248 (UCHAR) Tib2Orig.tib2_ulpri,
1249 0);
1250 }
1251 break;
1252 }
1253
1254 /* we never handle the exception ourselves */
1255
1256 if (Info.ptib->tib_ptib2->tib2_ultid == 1 &&
1257 QtOS2SysXcptMainHandler::libcHandler != NULL)
1258 {
1259 /* we are on the main thread and were installed from qt_init() during
1260 * QApplication initialization using a hack; pass control back to the
1261 * LIBC exception handler */
1262 return QtOS2SysXcptMainHandler::libcHandler (pReportRec, pRegRec,
1263 pContextRec, pv);
1264 }
1265
1266 return XCPT_CONTINUE_SEARCH;
1267}
1268
1269#endif // !defined(QT_PM_NO_SYSEXCEPTIONS)
Note: See TracBrowser for help on using the repository browser.