How to Crash a Cocoa App for Testing Purposes (aka Abnormal Termination on Demand)

Posted in |

Sometimes you need to crash your Cocoa app to analyse its crash behaviour.

Apple offers some guidance on this http://developer.apple.com/tools/xcode/symbolizingcrashdumps.html

#define CRASH_CODE 1
#if CRASH_CODE
	(void)strlen((const char *)1);
#endif

On Leopard with Xcode 3.1.3 and GCC 4.2 it seems that something more is required.

The most reliable way to ensure abnormal termination is courtesy of stdlib.h:

abort(); 

abort() may optionally flush streams and delete temporary files. But if your app crashes for real it will simple receive a terminating signal.

abort() is defined by the ISO C reference to call SIGABRT. So the following also proves effective:

raise(SIGABRT); 

We can confirm this by examining the OS X stdlib source for abort:


extern void (*__cleanup)();
extern void __abort(void) __dead2;

#define TIMEOUT	10000	/* 10 milliseconds */

void
abort()
{
	struct sigaction act;

	/*
	 * POSIX requires we flush stdio buffers on abort.
	 * XXX ISO C requires that abort() be async-signal-safe.
	 */
	if (__cleanup)
		(*__cleanup)();

	sigfillset(&act.sa_mask);
	/*
	 * Don't block SIGABRT to give any handler a chance; we ignore
	 * any errors -- ISO C doesn't allow abort to return anyway.
	 */
	sigdelset(&act.sa_mask, SIGABRT);
	(void)_sigprocmask(SIG_SETMASK, &act.sa_mask, NULL);
	(void)raise(SIGABRT);
	usleep(TIMEOUT); /* give time for signal to happen */

	/*
	 * If SIGABRT was ignored, or caught and the handler returns, do
	 * it again, only harder.
	 */
	 __abort();
}

__private_extern__ void
__abort()
{
	struct sigaction act;

	act.sa_handler = SIG_DFL;
	act.sa_flags = 0;
	sigfillset(&act.sa_mask);
	(void)_sigaction(SIGABRT, &act, NULL);
	sigdelset(&act.sa_mask, SIGABRT);
	(void)_sigprocmask(SIG_SETMASK, &act.sa_mask, NULL);
	(void)raise(SIGABRT);
	usleep(TIMEOUT); /* give time for signal to happen */
	__builtin_trap(); /* never exit normally */
}

Several other signals are also configured by default to cause termination, eg:

raise(SIGBUS);
raise(SIGFPE);
raise(SIGSEGV);

It is possible that a developer who wishes to test out the crash behaviour their app will insert callable crash code like the above into their app. This can be avoided by sending a suitable signal to the application from the terminal using the kill(1) command.

kill -s ABRT process_id

When an exception occurs within the mach kernel that exception is mapped to a UNIX signal which is ultimately sent to your app. The xnu function responsible for this performs the following mapping between mach kernel exceptions and UNIX signals.


switch(exception) {

	case EXC_BAD_ACCESS:
		if (code == KERN_INVALID_ADDRESS)
			*ux_signal = SIGSEGV;
		else
			*ux_signal = SIGBUS;
		break;

	case EXC_BAD_INSTRUCTION:
	    *ux_signal = SIGILL;
	    break;

	case EXC_ARITHMETIC:
	    *ux_signal = SIGFPE;
	    break;

	case EXC_EMULATION:
	    *ux_signal = SIGEMT;
	    break;

	case EXC_SOFTWARE:
	    switch (code) {

	    case EXC_UNIX_BAD_SYSCALL:
		*ux_signal = SIGSYS;
		break;
	    case EXC_UNIX_BAD_PIPE:
		*ux_signal = SIGPIPE;
		break;
	    case EXC_UNIX_ABORT:
		*ux_signal = SIGABRT;
		break;
	    case EXC_SOFT_SIGNAL:
		*ux_signal = SIGKILL;
		break;
	    }
	    break;

	case EXC_BREAKPOINT:
	    *ux_signal = SIGTRAP;
	    break;
    }


And all Cocoa developers have certainly seen plenty of occurrences of EXC_BAD_ACCESS whilst debugging. EXC_BAD_ACCESS is simply the underlying mach exception which results in a SIGSEGV or SIGBUS error being sent to our app.

If you want to crash the app using a Cocoa call try:

NSDecimalNumber *n = (NSDecimalNumber *)[NSDecimalNumber numberWithInt:0];
[n decimalNumberByAdding:nil];

Submitted by Jonathan Mitchell on Tue, 08/18/2009 - 13:13

Post new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
Let us know you are human.
8 + 1 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.