When an application crashes on the iPhone, it disappears without telling the user what happened. However, it is possible to add exception and signal handling to your applications so that an error message can be displayed to the user or you can save changes. It is even possible to try to recover from this situation without crashing at all.
从某种意义来说,调试,捕捉signal或则crash memory issue是正常程序不应该处理的事情,主要通过非正当渠道可解决的cras避免的
h
接下来介绍的方法能够搞定BAD——Access,以及BSD singal,甚至程序还能继续运行
EXC_BAD_ACCESS
exceptions and related BSD signals. All exceptions and signals are caught, presenting debug information and allowing the application to continue after these events.
我们看两类型的exception
An unhandled signal can come from three places: the kernel, other processes or the application itself. The two most common signals that cause crashes are:
EXC_BAD_ACCESS
is a Mach exception sent by the kernel to your application when you try to access memory that is not mapped for your application. If not handled at the Mach level, it will be translated into a SIGBUS
or SIGSEGV
BSD signal.SIGABRT
is a BSD signal sent by an application to itself when an NSException
orobj_exception_throw
is not caught.捕获非捕捉的异常
最正确的处理方式是找到原因并解决crash,如果您的程序一直运行正常,是不需要下面的手段的:
当然,程序往往发布的时候带有一些非常隐蔽的crash问题,然后你又想收集跟多的信息:
一般来说两个方法:使用
NSUncaughtExceptionHandler以及sinal
In these cases, there are two ways to catch otherwise uncaught conditions that will lead to a crash:
NSUncaughtExceptionHandler
to install a handler for uncaught Objective-C exceptions.signal
function to install handlers for BSD signals.
void
InstallUncaughtExceptionHandler()
{
NSSetUncaughtExceptionHandler(&HandleException);
signal(
SIGABRT
, SignalHandler);
signal(
SIGILL
, SignalHandler);
signal(
SIGSEGV
, SignalHandler);
signal(
SIGFPE
, SignalHandler);
signal(
SIGBUS
, SignalHandler);
signal(
SIGPIPE
, SignalHandler);
}
|
the run loop must handle all the modes of the main run loop. Since the main run loop includes a few private modes (for GSEvent handling and scroll tracking), the defaultNSDefaultRunLoopMode
is insufficent.
Fortunately, if the UIApplication
has already created all the modes for the main loop, then we can get all of these modes by reading from the loop. Assuming it is run on the main thread after the main loop is created, the following code will run the loop in all UIApplication
modes:
CFRunLoopRef
runLoop = CFRunLoopGetCurrent();
CFArrayRef
allModes = CFRunLoopCopyAllModes(runLoop);
while
(!dismissed)
{
for
(
NSString
*mode
in
(
NSArray
*)allModes)
{
CFRunLoopRunInMode((CFStringRef)mode,
0
.001
, false);
}
}
CFRelease(allModes);
|
You can get the backtrace using the function backtrace
and attempt to convert this to symbols using backtrace_symbols
.
+ (
NSArray
*)backtrace
{
void
* callstack[
1
2
8
];
int
frames = backtrace(callstack,
1
2
8
);
char
**strs = backtrace_symbols(callstack, frames);
int
i;
NSMutableArray
*backtrace = [
NSMutableArray
arrayWithCapacity
:frames];
for
(
i =
UncaughtExceptionHandlerSkipAddressCount
;
i <
UncaughtExceptionHandlerSkipAddressCount
+
UncaughtExceptionHandlerReportAddressCount
;
i++)
{
[backtrace
addObject
:[
NSString
stringWithUTF8String
:strs[i]]];
}
free(strs);
return
backtrace;
}
|
Notice that we skip the first few addresses: this is because they will be the addresses of the signal or exception handling functions (not very interesting). Since we want to keep the data minimal (for display in a UIAlert
dialog) I choose not to display the exception handling functions.
If the user selects "Quit" to abort the application instead of attempting to continue, it's a good idea to generate the crash log so that normal crash log handling can track the problem.
In this case, we need to remove all the exception handlers and re-raise the exception or resend the signal. This will cause the application to crash as normal (although the uncaught exception handler will appear at the top of the stack, lower frames will be the same).
Remember from the paragraph at the beginning:
The code in this post performs signal handling in non re-entrant way — this is not a reliable thing to do and is only done because proper re-entrant coding is brutally difficult and the assumption is that your program has already fatally crashed so we're not too worried. If multiple signals are caught, this code probably won't help at all.
If you want to learn how to write signal handlers for non-crash related signals or learn how to write proper re-entrant signal handling, I'm afraid you'll need to look elsewhere — there's not enough space here for me to show you and it's really hard. Ignoring this constraint here is okay for debug code only where we assume we're only going to get 1 signal.
The exact way that UIApplication
constructs windows and the main run loop is private. This means that if the main run loop and initial windows are not already constructed, the exception code I've given won't work — the code will run but the UIAlert
dialog will never appear. For this reason, I install the exception handlers with a performSelector:withObject:afterDelay:0
from the applicationDidFinishLaunching:
method on the App Delegate to ensure that this exception handler is only installed after the main run loop is fully configured. Any exception that occurs prior to this point on startup will crash the application as normal.
You cannot simply continue from all situations that trigger exceptions. If you're in the middle of a situation that must be completed in its entirety (a transaction on your document) then your application's document may now be invalid.
Alternately, the conditions which led to the exception or signal may have left your stack or heap in a state so corrupted that nothing is possible. In this type of situation, you're going to crash and there's little you can do.
The initial causes of the exception or signal will not be fixed by ignoring it. The application might simply raise the same exception immediately. In fact, you could become overwhelmed by exceptions in some cases — for this reason, I've limited the number of uncaught exceptions that may be handled to 10 in the sample application.
Since the stack is blocked from returning, everything allocated on the stack or the autorelease pool between the main run loop and the exception will be leaked.
Depending on the style of your application, it might be better to simply let the crash happen — not all users care about debug information and your application might not have data that needs saving, so a very rare crash might not be too offensive.
When you're debugging, the SIGBUS and SIGSEGV signals may not get called. This is because gdb inserts Mach exception handlers which picks them up at the EXC_BAD_ACCESS stage (and refuses to continue). Other signals type may also be handled by gdb, preventing the signals from reaching your handlers.
If you want to test signal handling properly, you'll need to run without gdb (Run with Breakpoints off).
You can download the sample project: UncaughtExceptions.zip (25kB)
It is possible to make your application continue running for a short period of time after a "crash" signal occurs by handling common exceptional signals and attempting to recover.
There are real risks though in terms of signal re-entrancy problems, leaked memory and potentially corrupted application data, so this type of approach should be viewed as either a debugging tool or a measure of last resort.
However, it is comforting to have a level of fallback in the situation where a hard to reproduce crash occurs during testing and you'd like more information on the application state when the crash happened.