xref: /aosp_15_r20/external/deqp/framework/qphelper/qpCrashHandler.c (revision 35238bce31c2a825756842865a792f8cf7f89930)
1 /*-------------------------------------------------------------------------
2  * drawElements Quality Program Helper Library
3  * -------------------------------------------
4  *
5  * Copyright 2014 The Android Open Source Project
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  *//*!
20  * \file
21  * \brief System handler handler override
22  *//*--------------------------------------------------------------------*/
23 
24 #include "qpCrashHandler.h"
25 #include "qpDebugOut.h"
26 
27 #include "deThread.h"
28 #include "deMemory.h"
29 #include "deString.h"
30 #include "deMutex.h"
31 
32 #include <stdio.h>
33 #include <stdarg.h>
34 #include <stdlib.h>
35 
36 #if (DE_OS == DE_OS_UNIX)
37 #include <unistd.h>
38 #include <errno.h>
39 #include <inttypes.h>
40 #if defined(__GLIBC__) || defined(__FreeBSD__)
41 #include <execinfo.h>
42 #endif
43 #endif
44 
45 #if 0
46 #define DBGPRINT(X) qpPrintf X
47 #else
48 #define DBGPRINT(X)
49 #endif
50 
51 /* Crash info write helper. */
writeInfoFormat(qpWriteCrashInfoFunc writeFunc,void * userPtr,const char * format,...)52 static void writeInfoFormat(qpWriteCrashInfoFunc writeFunc, void *userPtr, const char *format, ...)
53 {
54     char buf[256];
55     va_list ap;
56 
57     va_start(ap, format);
58     vsnprintf(buf, sizeof(buf), format, ap);
59     va_end(ap);
60 
61     writeFunc(userPtr, buf);
62 }
63 
64 /* Shared crash info. */
65 typedef enum qpCrashType_e
66 {
67     QP_CRASHTYPE_SEGMENTATION_FAULT = 0,
68     QP_CRASHTYPE_ASSERT,
69     QP_CRASHTYPE_UNHANDLED_EXCEPTION,
70     QP_CRASHTYPE_OTHER,
71 
72     QP_CRASHTYPE_LAST
73 } qpCrashType;
74 
75 typedef struct qpCrashInfo_s
76 {
77     qpCrashType type;
78     const char *message;
79     const char *file;
80     int line;
81 } qpCrashInfo;
82 
qpCrashInfo_init(qpCrashInfo * info)83 static void qpCrashInfo_init(qpCrashInfo *info)
84 {
85     info->type    = QP_CRASHTYPE_LAST;
86     info->message = DE_NULL;
87     info->file    = DE_NULL;
88     info->line    = 0;
89 }
90 
qpCrashInfo_set(qpCrashInfo * info,qpCrashType type,const char * message,const char * file,int line)91 static void qpCrashInfo_set(qpCrashInfo *info, qpCrashType type, const char *message, const char *file, int line)
92 {
93     info->type    = type;
94     info->message = message;
95     info->file    = file;
96     info->line    = line;
97 }
98 
qpCrashInfo_write(qpCrashInfo * info,qpWriteCrashInfoFunc writeInfo,void * userPtr)99 static void qpCrashInfo_write(qpCrashInfo *info, qpWriteCrashInfoFunc writeInfo, void *userPtr)
100 {
101     switch (info->type)
102     {
103     case QP_CRASHTYPE_SEGMENTATION_FAULT:
104         writeInfoFormat(writeInfo, userPtr, "Segmentation fault: '%s'\n", info->message);
105         break;
106 
107     case QP_CRASHTYPE_UNHANDLED_EXCEPTION:
108         writeInfoFormat(writeInfo, userPtr, "Unhandled exception: '%s'\n", info->message);
109         break;
110 
111     case QP_CRASHTYPE_ASSERT:
112         writeInfoFormat(writeInfo, userPtr, "Assertion '%s' failed at %s:%d\n", info->message, info->file, info->line);
113         break;
114 
115     case QP_CRASHTYPE_OTHER:
116     default:
117         writeInfoFormat(writeInfo, userPtr, "Crash: '%s'\n", info->message);
118         break;
119     }
120 }
121 
defaultWriteInfo(void * userPtr,const char * infoString)122 static void defaultWriteInfo(void *userPtr, const char *infoString)
123 {
124     DE_UNREF(userPtr);
125     qpPrintf("%s", infoString);
126 }
127 
defaultCrashHandler(qpCrashHandler * crashHandler,void * userPtr)128 static void defaultCrashHandler(qpCrashHandler *crashHandler, void *userPtr)
129 {
130     DE_UNREF(userPtr);
131     qpCrashHandler_writeCrashInfo(crashHandler, defaultWriteInfo, DE_NULL);
132     qpDief("Test process crashed");
133 }
134 
135 #if (DE_OS == DE_OS_WIN32) && (DE_COMPILER == DE_COMPILER_MSC)
136 
137 #define WIN32_LEAN_AND_MEAN
138 #include <windows.h>
139 
140 /* DbgHelp.h generates C4091 */
141 #pragma warning(push)
142 #pragma warning(disable : 4091)
143 #include <DbgHelp.h>
144 #pragma warning(pop)
145 
146 struct qpCrashHandler_s
147 {
148     qpCrashHandlerFunc crashHandlerFunc;
149     void *handlerUserPointer;
150 
151     deMutex crashHandlerLock;
152     qpCrashInfo crashInfo;
153     uintptr_t crashAddress;
154 
155     LPTOP_LEVEL_EXCEPTION_FILTER oldExceptionFilter;
156 };
157 
158 qpCrashHandler *g_crashHandler = DE_NULL;
159 
unhandledExceptionFilter(struct _EXCEPTION_POINTERS * info)160 static LONG WINAPI unhandledExceptionFilter(struct _EXCEPTION_POINTERS *info)
161 {
162     qpCrashType crashType = QP_CRASHTYPE_LAST;
163     const char *reason    = DE_NULL;
164 
165     /* Skip breakpoints. */
166     if (info->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
167     {
168         DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): breakpoint\n"));
169         return EXCEPTION_CONTINUE_SEARCH;
170     }
171 
172     /* If no handler present (how could that be?), don't handle. */
173     if (g_crashHandler == DE_NULL)
174     {
175         DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): no crash handler registered\n"));
176         return EXCEPTION_CONTINUE_SEARCH;
177     }
178 
179     /* If we have a debugger, let it handle the exception. */
180     if (IsDebuggerPresent())
181     {
182         DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): debugger present\n"));
183         return EXCEPTION_CONTINUE_SEARCH;
184     }
185 
186     /* Acquire crash handler lock. Otherwise we might get strange behavior when multiple threads enter crash handler simultaneously. */
187     deMutex_lock(g_crashHandler->crashHandlerLock);
188 
189     /* Map crash type. */
190     switch (info->ExceptionRecord->ExceptionCode)
191     {
192     case EXCEPTION_ACCESS_VIOLATION:
193         crashType = QP_CRASHTYPE_SEGMENTATION_FAULT;
194         reason    = "Access violation";
195         break;
196 
197     case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
198         crashType = QP_CRASHTYPE_SEGMENTATION_FAULT;
199         reason    = "Array bounds exceeded";
200         break;
201 
202     case EXCEPTION_ILLEGAL_INSTRUCTION:
203         crashType = QP_CRASHTYPE_OTHER;
204         reason    = "Illegal instruction";
205         break;
206 
207     case EXCEPTION_STACK_OVERFLOW:
208         crashType = QP_CRASHTYPE_OTHER;
209         reason    = "Stack overflow";
210         break;
211 
212     default:
213         /* \todo [pyry] Others. */
214         crashType = QP_CRASHTYPE_OTHER;
215         reason    = "";
216         break;
217     }
218 
219     /* Store reason. */
220     qpCrashInfo_set(&g_crashHandler->crashInfo, crashType, reason, __FILE__, __LINE__);
221 
222     /* Store win32-specific crash info. */
223     g_crashHandler->crashAddress = (uintptr_t)info->ExceptionRecord->ExceptionAddress;
224 
225     /* Handle the crash. */
226     DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): handled quietly\n"));
227     if (g_crashHandler->crashHandlerFunc != DE_NULL)
228         g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer);
229 
230     /* Release lock. */
231     deMutex_unlock(g_crashHandler->crashHandlerLock);
232 
233     return EXCEPTION_EXECUTE_HANDLER;
234 }
235 
assertFailureCallback(const char * expr,const char * file,int line)236 static void assertFailureCallback(const char *expr, const char *file, int line)
237 {
238     /* Don't execute crash handler function if debugger is present. */
239     if (IsDebuggerPresent())
240     {
241         DBGPRINT(("qpCrashHandler::assertFailureCallback(): debugger present\n"));
242         return;
243     }
244 
245     /* Acquire crash handler lock. */
246     deMutex_lock(g_crashHandler->crashHandlerLock);
247 
248     /* Store info. */
249     qpCrashInfo_set(&g_crashHandler->crashInfo, QP_CRASHTYPE_ASSERT, expr, file, line);
250     g_crashHandler->crashAddress = 0;
251 
252     /* Handle the crash. */
253     if (g_crashHandler->crashHandlerFunc != DE_NULL)
254         g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer);
255 
256     /* Release lock. */
257     deMutex_unlock(g_crashHandler->crashHandlerLock);
258 }
259 
qpCrashHandler_create(qpCrashHandlerFunc handlerFunc,void * userPointer)260 qpCrashHandler *qpCrashHandler_create(qpCrashHandlerFunc handlerFunc, void *userPointer)
261 {
262     /* Allocate & initialize. */
263     qpCrashHandler *handler = (qpCrashHandler *)deCalloc(sizeof(qpCrashHandler));
264     DBGPRINT(("qpCrashHandler::create() -- Win32\n"));
265     if (!handler)
266         return handler;
267 
268     DE_ASSERT(g_crashHandler == DE_NULL);
269 
270     handler->crashHandlerFunc   = handlerFunc ? handlerFunc : defaultCrashHandler;
271     handler->handlerUserPointer = userPointer;
272 
273     /* Create lock for crash handler. \note Has to be recursive or otherwise crash in assert failure causes deadlock. */
274     {
275         deMutexAttributes attr;
276         attr.flags                = DE_MUTEX_RECURSIVE;
277         handler->crashHandlerLock = deMutex_create(&attr);
278 
279         if (!handler->crashHandlerLock)
280         {
281             deFree(handler);
282             return DE_NULL;
283         }
284     }
285 
286     qpCrashInfo_init(&handler->crashInfo);
287     handler->crashAddress = 0;
288 
289     /* Unhandled exception filter. */
290     handler->oldExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionFilter);
291 
292     /* Prevent nasty error dialog. */
293     SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
294 
295     /* DE_ASSERT callback. */
296     deSetAssertFailureCallback(assertFailureCallback);
297 
298     g_crashHandler = handler;
299     return handler;
300 }
301 
qpCrashHandler_destroy(qpCrashHandler * handler)302 void qpCrashHandler_destroy(qpCrashHandler *handler)
303 {
304     DBGPRINT(("qpCrashHandler::destroy()\n"));
305 
306     DE_ASSERT(g_crashHandler == handler);
307 
308     deSetAssertFailureCallback(DE_NULL);
309     SetUnhandledExceptionFilter(handler->oldExceptionFilter);
310 
311     g_crashHandler = DE_NULL;
312     deFree(handler);
313 }
314 
315 enum
316 {
317     MAX_NAME_LENGTH = 64
318 };
319 
qpCrashHandler_writeCrashInfo(qpCrashHandler * handler,qpWriteCrashInfoFunc writeInfo,void * userPtr)320 void qpCrashHandler_writeCrashInfo(qpCrashHandler *handler, qpWriteCrashInfoFunc writeInfo, void *userPtr)
321 {
322     void *addresses[32];
323     HANDLE process;
324 
325     /* Symbol info struct. */
326     uint8_t symInfoStorage[sizeof(SYMBOL_INFO) + MAX_NAME_LENGTH];
327     SYMBOL_INFO *symInfo = (SYMBOL_INFO *)symInfoStorage;
328 
329     DE_STATIC_ASSERT(sizeof(TCHAR) == sizeof(uint8_t));
330 
331     /* Write basic info. */
332     qpCrashInfo_write(&handler->crashInfo, writeInfo, userPtr);
333 
334     /* Acquire process handle and initialize symbols. */
335     process = GetCurrentProcess();
336 
337     /* Write backtrace. */
338     if (SymInitialize(process, NULL, TRUE))
339     {
340         int batchStart     = 0;
341         int globalFrameNdx = 0;
342 
343         /* Initialize symInfo. */
344         deMemset(symInfo, 0, sizeof(symInfoStorage));
345         symInfo->SizeOfStruct = sizeof(SYMBOL_INFO);
346         symInfo->MaxNameLen   = MAX_NAME_LENGTH;
347 
348         /* Print address and symbol where crash happened. */
349         if (handler->crashAddress != 0)
350         {
351             BOOL symInfoOk = SymFromAddr(process, (DWORD64)handler->crashAddress, 0, symInfo);
352 
353             writeInfoFormat(writeInfo, userPtr, "  at %p %s%s\n", handler->crashAddress,
354                             symInfoOk ? symInfo->Name : "(unknown)", symInfoOk ? "()" : "");
355         }
356 
357         writeInfo(userPtr, "Backtrace:\n");
358 
359         for (;;)
360         {
361             int curFrame;
362             int numInBatch;
363 
364             /* Get one batch. */
365             numInBatch = CaptureStackBackTrace(batchStart, DE_LENGTH_OF_ARRAY(addresses), addresses, NULL);
366 
367             for (curFrame = 0; curFrame < numInBatch; curFrame++)
368             {
369                 BOOL symInfoOk = SymFromAddr(process, (DWORD64)addresses[curFrame], 0, symInfo);
370 
371                 writeInfoFormat(writeInfo, userPtr, "  %2d: %p %s%s\n", globalFrameNdx++, addresses[curFrame],
372                                 symInfoOk ? symInfo->Name : "(unknown)", symInfoOk ? "()" : "");
373             }
374 
375             batchStart += numInBatch;
376 
377             /* Check if we hit end of stack trace. */
378             if (numInBatch == 0 || numInBatch < DE_LENGTH_OF_ARRAY(addresses))
379                 break;
380         }
381     }
382 }
383 
384 #else /* posix / generic implementation */
385 
386 #if defined(QP_USE_SIGNAL_HANDLER)
387 #include <signal.h>
388 #endif
389 
390 #if defined(QP_USE_SIGNAL_HANDLER)
391 
392 typedef struct SignalInfo_s
393 {
394     int signalNum;
395     qpCrashType type;
396     const char *name;
397 } SignalInfo;
398 
399 static const SignalInfo s_signals[] = {
400     {SIGABRT, QP_CRASHTYPE_UNHANDLED_EXCEPTION, "SIGABRT"}, {SIGILL, QP_CRASHTYPE_OTHER, "SIGILL"},
401     {SIGSEGV, QP_CRASHTYPE_SEGMENTATION_FAULT, "SIGSEGV"},  {SIGFPE, QP_CRASHTYPE_OTHER, "SIGFPE"},
402     {SIGBUS, QP_CRASHTYPE_SEGMENTATION_FAULT, "SIGBUS"},    {SIGPIPE, QP_CRASHTYPE_OTHER, "SIGPIPE"}};
403 
404 #endif /* QP_USE_SIGNAL_HANDLER */
405 
406 struct qpCrashHandler_s
407 {
408     qpCrashHandlerFunc crashHandlerFunc;
409     void *handlerUserPointer;
410 
411     qpCrashInfo crashInfo;
412     int crashSignal;
413 
414 #if defined(QP_USE_SIGNAL_HANDLER)
415     struct sigaction oldHandlers[DE_LENGTH_OF_ARRAY(s_signals)];
416 #endif
417 };
418 
419 qpCrashHandler *g_crashHandler = DE_NULL;
420 
assertFailureCallback(const char * expr,const char * file,int line)421 static void assertFailureCallback(const char *expr, const char *file, int line)
422 {
423     /* Store info. */
424     qpCrashInfo_set(&g_crashHandler->crashInfo, QP_CRASHTYPE_ASSERT, expr, file, line);
425 
426     /* Handle the crash. */
427     if (g_crashHandler->crashHandlerFunc != DE_NULL)
428         g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer);
429 }
430 
431 #if defined(QP_USE_SIGNAL_HANDLER)
432 
getSignalInfo(int sigNum)433 static const SignalInfo *getSignalInfo(int sigNum)
434 {
435     int ndx;
436     for (ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_signals); ndx++)
437     {
438         if (s_signals[ndx].signalNum == sigNum)
439             return &s_signals[ndx];
440     }
441     return DE_NULL;
442 }
443 
signalHandler(int sigNum)444 static void signalHandler(int sigNum)
445 {
446     const SignalInfo *info = getSignalInfo(sigNum);
447     qpCrashType type       = info ? info->type : QP_CRASHTYPE_OTHER;
448     const char *name       = info ? info->name : "Unknown signal";
449 
450     qpCrashInfo_set(&g_crashHandler->crashInfo, type, name, DE_NULL, 0);
451 
452     if (g_crashHandler->crashHandlerFunc != DE_NULL)
453         g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer);
454 }
455 
456 #endif /* QP_USE_SIGNAL_HANDLER */
457 
qpCrashHandler_create(qpCrashHandlerFunc handlerFunc,void * userPointer)458 qpCrashHandler *qpCrashHandler_create(qpCrashHandlerFunc handlerFunc, void *userPointer)
459 {
460     /* Allocate & initialize. */
461     qpCrashHandler *handler = (qpCrashHandler *)deCalloc(sizeof(qpCrashHandler));
462     DBGPRINT(("qpCrashHandler::create()\n"));
463     if (!handler)
464         return handler;
465 
466     DE_ASSERT(g_crashHandler == DE_NULL);
467 
468     handler->crashHandlerFunc   = handlerFunc ? handlerFunc : defaultCrashHandler;
469     handler->handlerUserPointer = userPointer;
470 
471     qpCrashInfo_init(&handler->crashInfo);
472 
473     g_crashHandler = handler;
474 
475     /* DE_ASSERT callback. */
476     deSetAssertFailureCallback(assertFailureCallback);
477 
478 #if defined(QP_USE_SIGNAL_HANDLER)
479     /* Register signal handlers. */
480     {
481         struct sigaction action;
482         int sigNdx;
483 
484         sigemptyset(&action.sa_mask);
485         action.sa_handler = signalHandler;
486         action.sa_flags   = 0;
487 
488         for (sigNdx = 0; sigNdx < DE_LENGTH_OF_ARRAY(s_signals); sigNdx++)
489             sigaction(s_signals[sigNdx].signalNum, &action, &handler->oldHandlers[sigNdx]);
490     }
491 #endif
492 
493     return handler;
494 }
495 
qpCrashHandler_destroy(qpCrashHandler * handler)496 void qpCrashHandler_destroy(qpCrashHandler *handler)
497 {
498     DBGPRINT(("qpCrashHandler::destroy()\n"));
499 
500     DE_ASSERT(g_crashHandler == handler);
501 
502     deSetAssertFailureCallback(DE_NULL);
503 
504 #if defined(QP_USE_SIGNAL_HANDLER)
505     /* Restore old handlers. */
506     {
507         int sigNdx;
508         for (sigNdx = 0; sigNdx < DE_LENGTH_OF_ARRAY(s_signals); sigNdx++)
509             sigaction(s_signals[sigNdx].signalNum, &handler->oldHandlers[sigNdx], DE_NULL);
510     }
511 #endif
512 
513     g_crashHandler = DE_NULL;
514 
515     deFree(handler);
516 }
517 
518 #if (DE_PTR_SIZE == 8)
519 #define PTR_FMT "0x%016"
520 #elif (DE_PTR_SIZE == 4)
521 #define PTR_FMT "0x%08"
522 #else
523 #error Unknwon DE_PTR_SIZE
524 #endif
525 
qpCrashHandler_writeCrashInfo(qpCrashHandler * crashHandler,qpWriteCrashInfoFunc writeInfo,void * userPtr)526 void qpCrashHandler_writeCrashInfo(qpCrashHandler *crashHandler, qpWriteCrashInfoFunc writeInfo, void *userPtr)
527 {
528     qpCrashInfo_write(&crashHandler->crashInfo, writeInfo, userPtr);
529 
530 #if (DE_OS == DE_OS_UNIX && (defined(__GLIBC__) || defined(__FreeBSD__)))
531     {
532         char tmpFileName[] = "backtrace-XXXXXX";
533         int tmpFile        = mkstemp(tmpFileName);
534 
535         if (tmpFile == -1)
536         {
537             writeInfoFormat(writeInfo, userPtr, "Failed to create tmpfile '%s' for the backtrace %s.", tmpFileName,
538                             strerror(errno));
539             return;
540         }
541         else
542         {
543             void *symbols[32];
544             int symbolCount;
545             int symbolNdx;
546 
547             /* Remove file from filesystem. */
548             remove(tmpFileName);
549 
550             symbolCount = backtrace(symbols, DE_LENGTH_OF_ARRAY(symbols));
551             backtrace_symbols_fd(symbols, symbolCount, tmpFile);
552 
553             if (lseek(tmpFile, 0, SEEK_SET) < 0)
554             {
555                 writeInfoFormat(writeInfo, userPtr, "Failed to seek to the beginning of the trace file %s.",
556                                 strerror(errno));
557                 close(tmpFile);
558                 return;
559             }
560 
561             for (symbolNdx = 0; symbolNdx < symbolCount; symbolNdx++)
562             {
563                 char nameBuffer[256];
564                 size_t symbolNameLength = 0;
565                 char c;
566 
567                 {
568                     const int ret = snprintf(nameBuffer, DE_LENGTH_OF_ARRAY(nameBuffer), PTR_FMT PRIXPTR " : ",
569                                              (uintptr_t)symbols[symbolNdx]);
570 
571                     if (ret < 0)
572                     {
573                         writeInfoFormat(writeInfo, userPtr, "Failed to print symbol pointer.");
574                         symbolNameLength = 0;
575                     }
576                     else if (ret >= DE_LENGTH_OF_ARRAY(nameBuffer))
577                     {
578                         symbolNameLength                               = DE_LENGTH_OF_ARRAY(nameBuffer) - 1;
579                         nameBuffer[DE_LENGTH_OF_ARRAY(nameBuffer) - 1] = '\0';
580                     }
581                     else
582                         symbolNameLength = ret;
583                 }
584 
585                 for (;;)
586                 {
587                     if (read(tmpFile, &c, 1) == 1)
588                     {
589                         if (c == '\n')
590                         {
591                             /* Flush nameBuffer and move to next symbol. */
592                             nameBuffer[symbolNameLength] = '\0';
593                             writeInfo(userPtr, nameBuffer);
594                             break;
595                         }
596                         else
597                         {
598                             /* Add character to buffer if there is still space left. */
599                             if (symbolNameLength + 1 < DE_LENGTH_OF_ARRAY(nameBuffer))
600                             {
601                                 nameBuffer[symbolNameLength] = c;
602                                 symbolNameLength++;
603                             }
604                         }
605                     }
606                     else
607                     {
608                         /* Flush nameBuffer. */
609                         nameBuffer[symbolNameLength] = '\0';
610                         writeInfo(userPtr, nameBuffer);
611 
612                         /* Temp file ended unexpectedly? */
613                         writeInfoFormat(writeInfo, userPtr, "Unexpected EOF reading backtrace file '%s'", tmpFileName);
614                         close(tmpFile);
615                         tmpFile = -1;
616 
617                         break;
618                     }
619                 }
620 
621                 if (tmpFile == -1)
622                     break;
623             }
624 
625             if (tmpFile != -1)
626                 close(tmpFile);
627         }
628     }
629 #endif
630 }
631 
632 #endif /* generic */
633