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