異常簡介
處理器和系統內核中有設計標識不同事件的狀態碼,這些狀態被編碼爲不同的位和信號。每次處理器和內核檢測到狀態的變化時,便會觸發一個事件,該事件稱爲異常。
系統中可能的每種類型的異常都分配了一個唯一的非負整數的異常號。這些異常號由處理器和操作系統的內核設計者分配。
當處理器檢測到事件時,會通過一張被稱爲異常表的跳轉表,進行間接過程調用到一個專門設計用來處理這類事件的操作系統的子程序,稱爲異常處理程序。
在系統啓動時(計算機啓動),操作系統會分配和初始化一張異常表,該表的起始地址存放在一個稱爲異常表基寄存器(exception table base register
)的特殊CPU寄存器中。異常號是異常表中的索引。通過起始地址與異常號,找到異常程序的調用地址,最終執行異常程序。
異常的類別
異常一般分四類:中斷(interrupt
),陷阱(trap
),故障(fault
),終止(abort
)。
中斷:是來自處理器外部的I/O
異常信號導致的,不是由任何一條專門的指令造成的,從這個層面上來講,它是異步的。硬件中斷的處理程序通常被稱爲中斷處理程序。比如:拔插U
盤。
陷阱:是有意的異常,是執行一條指令的結果。和中斷一樣,陷阱處理程序將控制返回到下一條指令。陷阱最終的用途,是在用戶程序和內核之間提供一個像過程一樣的接口,稱爲系統調用。比如用戶程序經常需要向內核請求服務,比如讀一個文件(read
)、創建一個進程(fork
)、加載一個新的程序(execve
)或者終止當前進程(exit
),這些操作都需要通過觸發陷阱異常,來執行系統內核的程序來實現。
故障:是由錯誤情況引起的,它可能被故障處理程序修正。當故障發生時,處理器將控制轉移給故障處理程序。如果故障處理程序可以修正這個錯誤情況,便會將控制放回到故障指令,從而重新執行它,如果不能修正,故障處理程序便會將控制轉移到系統內核的abort()
函數,abort()
最終會終止引起故障的應用程序。
一般保護性故障,
Unix
不會嘗試恢復,而是將這種保護性故障報告爲段違規(segmentation violation
)對應信號爲SIGSEGV
,然後終止程序。比如:除零、程序嘗試寫入只讀的文本段等。
故障的經典示例便是缺頁異常。
終止:是由不可恢復的致命錯誤造成的結果。終止從不將控制返回給應用程序。如:奇偶校驗錯誤(機器硬件錯誤檢測)
Unix
信號(signal
)
更高層的軟件形式的異常,一個信號就是一條消息,它通知進程某種類型的消息已經在系統中發生了。信號提供了一種向用戶進程通知這些異常發生的機制,也允許進程中斷其他進程。
Linux
與macOS
都是類Unix
系統,下表列舉了Linux
系統支持的30
多種不同類型的信號,有許多Linux
信號在macOS
同樣適用:
號碼 | 名字 | 默認行爲 | 相應事件 | 號碼 | 名字 | 默認行爲 | 相應事件 |
---|---|---|---|---|---|---|---|
1 | SIGHUP | 終止 | 終端線掛起 | 16 | SIGSTKFLT | 終止 | 協處理器上的棧故障 |
2 | SIGINT | 終止 | 來自鍵盤的中斷 | 17 | SIGCHLD | 忽略 | 一個子進程暫停或者終止 |
3 | SIGQUIT | 終止 | 來自鍵盤的退出 | 18 | SIGCONT | 忽略 | 若進程暫停則繼續進程 |
4 | SIGILL | 終止 | 非法指令 | 19 | SIGSTOP | 停止直到下一個SIGCONT | 不來自終端的暫停信號 |
5 | SIGTRAP | 終止並轉儲存儲器 | 跟蹤陷阱 | 20 | SIGTSTP | 停止直到下一個SIGCONT | 來自終端的暫停信號 |
6 | SIGABRT | 終止並轉儲存儲器 | 來自abort 函數的終止信號 |
21 | SIGTTIN | 停止直到下一個SIGCONT | 後臺進程從終端讀 |
7 | SIGBUS | 終止 | 總線錯誤 | 22 | SIGTTOU | 停止直到下一個SIGCONT | 後臺進程向終端寫 |
8 | SIGFPE | 終止並轉儲存儲器 | 浮點異常 | 23 | SIGURG | 忽略 | 套接字上的緊急情況 |
9 | SIGKILL | 終止 | 殺死程序 | 24 | SIGXCPU | 終止 | CPU時間超出限制 |
10 | SIGUSER1 | 終止 | 用戶定義的信號1 | 25 | SIGXFSZ | 終止 | 文件大小超出限制 |
11 | SIGSEGV | 終止 | 無效的存儲器引用(段故障) | 26 | SIGVTALRM | 終止 | 虛擬定時器期滿 |
12 | SIGUSER2 | 終止 | 用戶定義的信號2 | 27 | SIGPROF | 終止 | 剖析定時器期滿 |
13 | SIGPIPE | 終止 | 向一個沒有用戶讀的管道做寫操作 | 28 | SIGWINCH | 忽略 | 窗口大小變化 |
14 | SIGALRM | 終止 | 來自alarm 函數的定時器信號 |
29 | SIGIO | 終止 | 在某個描述符上執行I/O操作 |
15 | SIGTERM | 終止 | 軟件終止信號 | 30 | SIGPWR | 終止 | 電源故障 |
系統內核
Mac OS X
&iOS
&iPad OS
系統內核都是Darwin。Darwin
包含了開放源代碼的XNU
混合內核,它包含了Mach
/BSD
,BSD
是建立在Mach
之上提供標準化(POSIX
)的API
,XNU
的核心是Mach
。下圖爲OS X
內核架構,查看來源。
Mach
:是一個微內核的操作系統。微內核
僅處理最核心的任務,其他任務交給用戶態的程序,包括文件管理,設備驅動等服務,這些服務被分解到不同的地址空間,彼此消息傳遞需要IPC
。主要負責:線程與進程管理、虛擬內存管理、進程通信與消息傳遞、任務調度。與之對應的單內核
則是把所有的服務放在相同的地址空間下,服務之間可相互調用。
BSD
:是Unix
的衍生系統。主要負責:Unix
進程模型、POSIX
線程模型以及相關原語、文件系統訪問、設備訪問、網絡協議棧、Unix
用戶與羣組。
異常來源
iOS中異常主要來源於硬件異常、軟件異常、Mach
異常、Signal
異常,它們之間的關係如下圖:
Mach異常
Mach異常是系統內核級異常,是由CPU
觸發一個陷阱引發,調用到Mach
的異常處理程序,將來自硬件的異常轉換爲Mach
異常,然後將Mach
異常傳遞到相應的thread
、task
、host
,若無結果返回,任務便會被終止。
Mach異常傳遞涉及到的內核函數如下圖:
依據上圖提供信息,查閱蘋果開源資料,找到對應的函數信息,簡單列舉如下(詳細查閱請前往此處):
struct ppc_saved_state *trap(int trapno,
struct ppc_saved_state *ssp,
unsigned int dsisr,
unsigned int dar) {
//...
doexception(exception, code, subcode);
//...
}
void doexception(
int exc,
int code,
int sub) {
exception_data_type_t codes[EXCEPTION_CODE_MAX];
codes[0] = code;
codes[1] = sub;
exception(exc, codes, 2);
}
// Des:The current thread caught an exception.
// We make an up-call to the thread's exception server.
void exception(
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t codeCnt)
{
thread_act_t thr_act;
task_t task;
host_priv_t host_priv;
struct exception_action *excp;
mutex_t *mutex;
assert(exception != EXC_RPC_ALERT);
if (exception == KERN_SUCCESS)
panic("exception");
/*
* Try to raise the exception at the activation level.線程級別
*/
thr_act = current_act();
mutex = mutex_addr(thr_act->lock);
excp = &thr_act->exc_actions[exception];
exception_deliver(exception, code, codeCnt, excp, mutex);
/*
* Maybe the task level will handle it. 任務級別
*/
task = current_task();
mutex = mutex_addr(task->lock);
excp = &task->exc_actions[exception];
exception_deliver(exception, code, codeCnt, excp, mutex);
/*
* How about at the host level? 主機級別
*/
host_priv = host_priv_self();
mutex = mutex_addr(host_priv->lock);
excp = &host_priv->exc_actions[exception];
exception_deliver(exception, code, codeCnt, excp, mutex);
/*
* Nobody handled it, terminate the task. 沒有處理終止
*/
// ...
(void) task_terminate(task);
thread_exception_return();
/*NOTREACHED*/
}
// Make an upcall to the exception server provided.
void exception_deliver(
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t codeCnt,
struct exception_action *excp,
mutex_t *mutex)
{
///...
int behavior = excp->behavior;
switch (behavior) {
case EXCEPTION_STATE: {
///EXCEPTION_STATE:Send a `catch_exception_raise_state` message
///including the thread state.
//..
kr = exception_raise_state(exc_port, exception,
code, codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
//..
return;
}
case EXCEPTION_DEFAULT:
///EXCEPTION_DEFAULT表示:Send a `catch_exception_raise` message
///including the thread identity.
//..
kr = exception_raise(exc_port,
retrieve_act_self_fast(a_self),
retrieve_task_self_fast(a_self->task),
exception,
code, codeCnt);
//..
return;
case EXCEPTION_STATE_IDENTITY: {
/// EXCEPTION_STATE_IDENTITY:表示Send a `catch_exception_raise_state_identity` message
///including the thread identity and state.
//..
kr = exception_raise_state_identity(exc_port,
retrieve_act_self_fast(a_self),
retrieve_task_self_fast(a_self->task),
exception,
code, codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
//..
return;
}
default:
panic ("bad exception behavior!");
}
}
關於如何捕獲Mach異常,蘋果文檔描述很少,也沒有提供可用的API,具體的Mach內核的API介紹,可從此處查閱。
Sigal信號
BSD
派生自Unix
操作系統,屬於類Unix
系統,基於Mach
內核進程任務,提供POSIX
應用程序接口。詳見維基百科-XNU。基於此,Unix Signal
機制同樣適用蘋果操作系統。蘋果系統對於Unix Signal
的定義,可通過#import <sys/signal.h>
跳轉查看。
Mach異常-> Signal信號
蘋果操作系統Mach
異常與Signal
信號共存。Mach
將操作系統的核心部分當做獨立進程運行,與BSD
服務進程之間通過IPC
機制實現消息傳遞。同理Mach
內核態的異常也是基於IPC
將異常消息發送到BSD
,BSD
將消息轉換爲用戶態的Signal
信號。具體流程如下:
- 蘋果內核啓動時會執行
bsdinit_task()
並最終調用ux_handler_init()
方法。
void bsdinit_task(void)
{
proc_t p = current_proc();
struct uthread *ut;
thread_t thread;
process_name("init", p);
ux_handler_init();
thread = current_thread();
(void) host_set_exception_ports(host_priv_self(),
EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT),//pilotfish (shark) needs this port
(mach_port_t) ux_exception_port,
EXCEPTION_DEFAULT| MACH_EXCEPTION_CODES,
0);
ut = (uthread_t)get_bsdthread_info(thread);
bsd_init_task = get_threadtask(thread);
init_task_failure_data[0] = 0;
#if CONFIG_MACF
mac_cred_label_associate_user(p->p_ucred);
mac_task_label_update_cred (p->p_ucred, (struct task *) p->task);
#endif
load_init_program(p);
lock_trace = 1;
}
-
ux_handler_init()
初始化一個ux_handler()
方法,並創建線程執行它。
void ux_handler_init(void)
{
thread_t thread = THREAD_NULL;
ux_exception_port = MACH_PORT_NULL;
(void) kernel_thread_start((thread_continue_t)ux_handler, NULL, &thread);
thread_deallocate(thread);
proc_list_lock();
if (ux_exception_port == MACH_PORT_NULL) {
(void)msleep(&ux_exception_port, proc_list_mlock, 0, "ux_handler_wait", 0);
}
proc_list_unlock();
}
-
ux_handler()
申請用於接收Mach
內核消息的端口(port
)集合,接收來自Mach
的異常消息
static void ux_handler(void)
{
task_t self = current_task();
mach_port_name_t exc_port_name;
mach_port_name_t exc_set_name;
/*
* Allocate a port set that we will receive on.
*/
if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_PORT_SET, &exc_set_name) != MACH_MSG_SUCCESS)
panic("ux_handler: port_set_allocate failed");
/*
* Allocate an exception port and use object_copyin to
* translate it to the global name. Put it into the set.
*/
if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_RECEIVE, &exc_port_name) != MACH_MSG_SUCCESS)
panic("ux_handler: port_allocate failed");
if (mach_port_move_member(get_task_ipcspace(ux_handler_self),
exc_port_name, exc_set_name) != MACH_MSG_SUCCESS)
panic("ux_handler: port_set_add failed");
if (ipc_object_copyin(get_task_ipcspace(self), exc_port_name,
MACH_MSG_TYPE_MAKE_SEND,
(void *) &ux_exception_port) != MACH_MSG_SUCCESS)
panic("ux_handler: object_copyin(ux_exception_port) failed");
proc_list_lock();
thread_wakeup(&ux_exception_port);
proc_list_unlock();
/* Message handling loop. */
for (;;) {
struct rep_msg {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} rep_msg;
struct exc_msg {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
mach_exception_data_t code;
/* some times RCV_TO_LARGE probs */
char pad[512];
} exc_msg;
mach_port_name_t reply_port;
kern_return_t result;
exc_msg.Head.msgh_local_port = CAST_MACH_NAME_TO_PORT(exc_set_name);
exc_msg.Head.msgh_size = sizeof (exc_msg);
#if 0
result = mach_msg_receive(&exc_msg.Head);
#else
result = mach_msg_receive(&exc_msg.Head, MACH_RCV_MSG,
sizeof (exc_msg), exc_set_name,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
0);
#endif
if (result == MACH_MSG_SUCCESS) {
reply_port = CAST_MACH_PORT_TO_NAME(exc_msg.Head.msgh_remote_port);
///收到消息後調用 mach_exc_server()
if (mach_exc_server(&exc_msg.Head, &rep_msg.Head)) {
///收到消息,回覆消息
result = mach_msg_send(&rep_msg.Head, MACH_SEND_MSG,
sizeof (rep_msg),MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL);
if (reply_port != 0 && result != MACH_MSG_SUCCESS)
mach_port_deallocate(get_task_ipcspace(ux_handler_self), reply_port);
}
}
else if (result == MACH_RCV_TOO_LARGE)
/* ignore oversized messages */;
else
panic("exception_handler");
}
}
ux_handler(void)
收到Mach
內核消息後,便會調用mach_exc_server
函數,這個函數會根據異常的行爲調用對應的catch_mach_exception_raise()
,catch_mach_exception_raise_state()
, 和catch_mach_exception_raise_state_identity()
,catch_mach_exception_raise()
會觸發Mach異常消息到Unix
信號的轉換。而關於mach_exc_server()
的實現,並未像其他函數直接給出,具體請見此處。調用
catch_mach_exception_raise()
將Mach
異常轉換爲Unix
信號,最終發送到對應線程。
kern_return_t catch_mach_exception_raise(
__unused mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
__unused mach_msg_type_number_t codeCnt
)
{
///...
/*
* Convert exception to unix signal and code.
*/
ux_exception(exception, code[0], code[1], &ux_signal, &ucode);
///struct uthread *ut
///struct proc *p;
ut = get_bsdthread_info(th_act);
p = proc_findthread(th_act);
///...
/*
* Send signal.
*/
if (ux_signal != 0) {
ut->uu_exception = exception;
//ut->uu_code = code[0]; // filled in by threadsignal
ut->uu_subcode = code[1];
threadsignal(th_act, ux_signal, code[0]);
}
if (p != NULL)
proc_rele(p);
thread_deallocate(th_act);
///...
}
static void ux_exception(
int exception,
mach_exception_code_t code,
mach_exception_subcode_t subcode,
int *ux_signal,
mach_exception_code_t *ux_code)
{
/*
* Try machine-dependent translation first.
*/
if (machine_exception(exception, code, subcode, ux_signal, ux_code))
return;
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;
}
}
ux_exception()
函數,展示了Mach
異常與Signal
信號轉換關係。關於iOS
中Mach
異常信號的定義,可通過#include <mach/exception_types.h>
跳轉查看。
硬件異常
硬件異常依據前文所述,主要爲:中斷、缺陷、故障、終止。硬件異常的觸發流程如下圖:
軟件異常
應用級別的異常,在iOS中就是NSException
。如果NSException
異常沒有捕獲處理(try-catch),系統最終會調用abort()
函數,嚮應用程序發送SIGABRT
的信號。
void abort() {
///...
/* <rdar://problem/7397932> abort() should call pthread_kill to deliver a signal to the aborting thread
* This helps gdb focus on the thread calling abort()
*/
if (__is_threaded) {
//...
(void)pthread_kill(pthread_self(), SIGABRT);
} else {
//...
(void)kill(getpid(), SIGABRT);
}
//...
}
異常捕獲
上文分析可知道硬件異常與軟件異常最終都會轉換爲Unix Signal
,因此對於Signal
信號的處理,可以覆蓋大部分的崩潰信息。除此之外系統給我們提供的NSException
,可以用來獲取更詳細的奔潰信息。基於此,下文我們將只對Signal
和NSException
的捕獲進行簡單示例。
Signal捕獲
在進行Signal
捕獲時,需要注意覆蓋問題。因爲每個Signal
對應一個Handler
的處理函數,當我們通過綁定我們自己的Hanlder
來收集奔潰信息時,可能會覆蓋其他三方庫已經綁定的Handler
導致他們無法收集奔潰信息。
核心代碼如下:
//頭文件
#import <sys/signal.h>
#import "execinfo.h"
///1.用以保存舊的handler
static struct sigaction *previous_signalHandlers = NULL;
///2.定義我們要處理的信號
static int signals[] = {SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGPIPE,SIGSEGV,SIGSYS,SIGTRAP};
///3.註冊`Handler`
+ (BOOL)registerSignalHandler; {
///初始化我們的Sigaction
struct sigaction action = { 0 };
///初始化存放舊的Sigaction數組
int count = sizeof(signals) / sizeof(int);
if (previous_signalHandlers == NULL) {
previous_signalHandlers = malloc(sizeof(struct sigaction) * count);
}
action.sa_flags = SA_SIGINFO;
sigemptyset(&action.sa_mask);
/// 綁定我們的處理函數
action.sa_sigaction = &_handleSignal;
for (int i = 0; i < count; i ++) {
///遍歷信號
int signal = signals[i];///or *(signals + i)
///綁定新的`Sigaction`,存儲舊的`Sigaction`
int result = sigaction(signal, &action, &previous_signalHandlers[i]);
/// 綁定失敗
if (result != 0) {
NSLog(@"signal:%d,error:%s",signal,strerror(errno));
for (int j =i--; j >= 0;j--) {
/// 恢復舊的Sigaction,此次函數返回NO
sigaction(signals[j], &previous_signalHandlers[j], NULL);
}
return NO;
}
}
return YES;
}
/// 4. 信號處理函數
void _handleSignal(int sigNum,siginfo_t *info,void *ucontext_t) {
/// todo our operation
NSLog(@"❌攔截到崩潰信號:%d,打印堆棧信息:%@",[CrashSignals callStackSymbols]);
/// 獲取`sigNum`在信號數組中對應的`index`
int index = -1,count = sizeof(signals) / sizeof(int);
for (int i = 0; i < count; i++) {
if (*(signals + i) == sigNum) {
index = i;
break;
}
}
if (index == -1) return;
/// 取出舊的`Sigaction`
struct sigaction previous_action = previous_signalHandlers[index];
if (previous_action.sa_handler == SIG_IGN) {
//`SIG_IGN`忽略信號,`SIG_DFL`默認方式處理信號
return;
}
/// 恢復舊的`Sigaction`與Signal的綁定關係
sigaction(sigNum, &previous_action, NULL);
/// 重新拋出這個`Signal`,此時便會被`previous_action`的處理程序攔截到。
raise(sigNum);
}
//5. 函數的調用棧
+ (NSArray*)callStackSymbols {
/// ` int backtrace(void ** buffer , int size )`
/// void ** buffer:在`buffer`指向的數組返回程序棧楨的回溯,
/// void ** buffer: Each item in the array pointed to by buffer is of type void *
void* backtrace_buffer[128];
/// 返回值可能比 128大,大便截斷,小則全部顯示
int numberOfReturnAdderss = backtrace(backtrace_buffer, 128);
///char **backtrace_symbols(void *const *buffer, int size);
/// `backtrace_symbols()` translates the addresses into an array of strings that describe the addresses symbolically
/// The size argument specifies the number of addresses in buffer
char **symbols = backtrace_symbols(backtrace_buffer, numberOfReturnAdderss);
/// 提取每個返回地址對應的符號信息,棧楨是嵌套的
NSMutableArray *tempArray = [[NSMutableArray alloc]initWithCapacity:numberOfReturnAdderss];
for (int i = 0 ; i < numberOfReturnAdderss; i++) {
char *cstr_item = symbols[i];
NSString *objc_str = [NSString stringWithUTF8String:cstr_item];
[tempArray addObject:objc_str];
}
return [tempArray copy];
}
NSException捕獲
系統提供了對應的處理iOS系統中未被捕獲的NSException
的API
,我們只需要按照API
進行操作即可,但與Signal
一樣,需要注意多處註冊的覆蓋問題,避免影響項目中其他收集程序。
核心代碼如下:
///1.聲明用以保存舊的`Hanlder`的靜態變量
static NSUncaughtExceptionHandler *previous_uncaughtExceptionHandler;
///註冊處理應用級異常的`handler`
+ (void)registerExceptionHandler; {
previous_uncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
NSSetUncaughtExceptionHandler(&_handleException);
}
///我們的異常處理程序
void _handleException(NSException *exception) {
/// Todo our operation
NSLog(@"✅攔截到異常的堆棧信息:%@",exception.callStackSymbols);
/// 傳遞異常
if (previous_uncaughtExceptionHandler != NULL) {
previous_uncaughtExceptionHandler(exception);
}
// 殺掉程序,這樣可以防止同時拋出的SIGABRT被Signal異常捕獲
/// kill (cannot be caught or ignored)
kill(getpid(), SIGKILL);
}
調試驗證
Xcode
的Debug
環境下,Signal
異常與NSException
異常都會被Xcode
調試器攔截,不會走到我們的處理程序。因此代碼的調試驗證,筆者採用模擬器運行程序後,停止運行,脫離Xcode
的調試環境,重新在模擬器打開程序,開啓Mac
的控制檯程序,點擊按鈕觸發奔潰,查看控制檯對應模擬器的log
記錄,來驗證是否正確捕獲。另:Signal
奔潰採用kill(getpid(), SIGBUS);
來觸發。
參考資料
https://flylib.com/books/en/3.126.1.109/1/
http://shevakuilin.com/ios-crashprotection/