異常簡(jiǎn)介
處理器和系統(tǒng)內(nèi)核中有設(shè)計(jì)標(biāo)識(shí)不同事件的狀態(tài)碼,這些狀態(tài)被編碼為不同的位和信號(hào)。每次處理器和內(nèi)核檢測(cè)到狀態(tài)的變化時(shí),便會(huì)觸發(fā)一個(gè)事件,該事件稱為異常。
系統(tǒng)中可能的每種類型的異常都分配了一個(gè)唯一的非負(fù)整數(shù)的異常號(hào)。這些異常號(hào)由處理器和操作系統(tǒng)的內(nèi)核設(shè)計(jì)者分配。
當(dāng)處理器檢測(cè)到事件時(shí),會(huì)通過(guò)一張被稱為異常表的跳轉(zhuǎn)表,進(jìn)行間接過(guò)程調(diào)用到一個(gè)專門設(shè)計(jì)用來(lái)處理這類事件的操作系統(tǒng)的子程序,稱為異常處理程序。
在系統(tǒng)啟動(dòng)時(shí)(計(jì)算機(jī)啟動(dòng)),操作系統(tǒng)會(huì)分配和初始化一張異常表,該表的起始地址存放在一個(gè)稱為異常表基寄存器(exception table base register)的特殊CPU寄存器中。異常號(hào)是異常表中的索引。通過(guò)起始地址與異常號(hào),找到異常程序的調(diào)用地址,最終執(zhí)行異常程序。
異常的類別
異常一般分四類:中斷(interrupt),陷阱(trap),故障(fault),終止(abort)。
中斷:是來(lái)自處理器外部的I/O異常信號(hào)導(dǎo)致的,不是由任何一條專門的指令造成的,從這個(gè)層面上來(lái)講,它是異步的。硬件中斷的處理程序通常被稱為中斷處理程序。比如:拔插U盤。
陷阱:是有意的異常,是執(zhí)行一條指令的結(jié)果。和中斷一樣,陷阱處理程序?qū)⒖刂品祷氐较乱粭l指令。陷阱最終的用途,是在用戶程序和內(nèi)核之間提供一個(gè)像過(guò)程一樣的接口,稱為系統(tǒng)調(diào)用。比如用戶程序經(jīng)常需要向內(nèi)核請(qǐng)求服務(wù),比如讀一個(gè)文件(read)、創(chuàng)建一個(gè)進(jìn)程(fork)、加載一個(gè)新的程序(execve)或者終止當(dāng)前進(jìn)程(exit),這些操作都需要通過(guò)觸發(fā)陷阱異常,來(lái)執(zhí)行系統(tǒng)內(nèi)核的程序來(lái)實(shí)現(xiàn)。
故障:是由錯(cuò)誤情況引起的,它可能被故障處理程序修正。當(dāng)故障發(fā)生時(shí),處理器將控制轉(zhuǎn)移給故障處理程序。如果故障處理程序可以修正這個(gè)錯(cuò)誤情況,便會(huì)將控制放回到故障指令,從而重新執(zhí)行它,如果不能修正,故障處理程序便會(huì)將控制轉(zhuǎn)移到系統(tǒng)內(nèi)核的abort()函數(shù),abort()最終會(huì)終止引起故障的應(yīng)用程序。
一般保護(hù)性故障,Unix不會(huì)嘗試恢復(fù),而是將這種保護(hù)性故障報(bào)告為段違規(guī)(
segmentation violation)對(duì)應(yīng)信號(hào)為SIGSEGV,然后終止程序。比如:除零、程序嘗試寫(xiě)入只讀的文本段等。
故障的經(jīng)典示例便是缺頁(yè)異常。
終止:是由不可恢復(fù)的致命錯(cuò)誤造成的結(jié)果。終止從不將控制返回給應(yīng)用程序。如:奇偶校驗(yàn)錯(cuò)誤(機(jī)器硬件錯(cuò)誤檢測(cè))
Unix信號(hào)(signal)
更高層的軟件形式的異常,一個(gè)信號(hào)就是一條消息,它通知進(jìn)程某種類型的消息已經(jīng)在系統(tǒng)中發(fā)生了。信號(hào)提供了一種向用戶進(jìn)程通知這些異常發(fā)生的機(jī)制,也允許進(jìn)程中斷其他進(jìn)程。
Linux與macOS都是類Unix系統(tǒng),下表列舉了Linux系統(tǒng)支持的30多種不同類型的信號(hào),有許多Linux信號(hào)在macOS同樣適用:
| 號(hào)碼 | 名字 | 默認(rèn)行為 | 相應(yīng)事件 | 號(hào)碼 | 名字 | 默認(rèn)行為 | 相應(yīng)事件 |
|---|---|---|---|---|---|---|---|
| 1 | SIGHUP | 終止 | 終端線掛起 | 16 | SIGSTKFLT | 終止 | 協(xié)處理器上的棧故障 |
| 2 | SIGINT | 終止 | 來(lái)自鍵盤的中斷 | 17 | SIGCHLD | 忽略 | 一個(gè)子進(jìn)程暫?;蛘呓K止 |
| 3 | SIGQUIT | 終止 | 來(lái)自鍵盤的退出 | 18 | SIGCONT | 忽略 | 若進(jìn)程暫停則繼續(xù)進(jìn)程 |
| 4 | SIGILL | 終止 | 非法指令 | 19 | SIGSTOP | 停止直到下一個(gè)SIGCONT | 不來(lái)自終端的暫停信號(hào) |
| 5 | SIGTRAP | 終止并轉(zhuǎn)儲(chǔ)存儲(chǔ)器 | 跟蹤陷阱 | 20 | SIGTSTP | 停止直到下一個(gè)SIGCONT | 來(lái)自終端的暫停信號(hào) |
| 6 | SIGABRT | 終止并轉(zhuǎn)儲(chǔ)存儲(chǔ)器 | 來(lái)自abort函數(shù)的終止信號(hào) |
21 | SIGTTIN | 停止直到下一個(gè)SIGCONT | 后臺(tái)進(jìn)程從終端讀 |
| 7 | SIGBUS | 終止 | 總線錯(cuò)誤 | 22 | SIGTTOU | 停止直到下一個(gè)SIGCONT | 后臺(tái)進(jìn)程向終端寫(xiě) |
| 8 | SIGFPE | 終止并轉(zhuǎn)儲(chǔ)存儲(chǔ)器 | 浮點(diǎn)異常 | 23 | SIGURG | 忽略 | 套接字上的緊急情況 |
| 9 | SIGKILL | 終止 | 殺死程序 | 24 | SIGXCPU | 終止 | CPU時(shí)間超出限制 |
| 10 | SIGUSER1 | 終止 | 用戶定義的信號(hào)1 | 25 | SIGXFSZ | 終止 | 文件大小超出限制 |
| 11 | SIGSEGV | 終止 | 無(wú)效的存儲(chǔ)器引用(段故障) | 26 | SIGVTALRM | 終止 | 虛擬定時(shí)器期滿 |
| 12 | SIGUSER2 | 終止 | 用戶定義的信號(hào)2 | 27 | SIGPROF | 終止 | 剖析定時(shí)器期滿 |
| 13 | SIGPIPE | 終止 | 向一個(gè)沒(méi)有用戶讀的管道做寫(xiě)操作 | 28 | SIGWINCH | 忽略 | 窗口大小變化 |
| 14 | SIGALRM | 終止 | 來(lái)自alarm函數(shù)的定時(shí)器信號(hào) |
29 | SIGIO | 終止 | 在某個(gè)描述符上執(zhí)行I/O操作 |
| 15 | SIGTERM | 終止 | 軟件終止信號(hào) | 30 | SIGPWR | 終止 | 電源故障 |
系統(tǒng)內(nèi)核
Mac OS X&iOS&iPad OS系統(tǒng)內(nèi)核都是Darwin。Darwin包含了開(kāi)放源代碼的XNU混合內(nèi)核,它包含了Mach/BSD,BSD是建立在Mach之上提供標(biāo)準(zhǔn)化(POSIX)的API,XNU的核心是Mach。下圖為OS X 內(nèi)核架構(gòu),查看來(lái)源。
Mach:是一個(gè)微內(nèi)核的操作系統(tǒng)。微內(nèi)核僅處理最核心的任務(wù),其他任務(wù)交給用戶態(tài)的程序,包括文件管理,設(shè)備驅(qū)動(dòng)等服務(wù),這些服務(wù)被分解到不同的地址空間,彼此消息傳遞需要IPC。主要負(fù)責(zé):線程與進(jìn)程管理、虛擬內(nèi)存管理、進(jìn)程通信與消息傳遞、任務(wù)調(diào)度。與之對(duì)應(yīng)的單內(nèi)核則是把所有的服務(wù)放在相同的地址空間下,服務(wù)之間可相互調(diào)用。
BSD:是Unix的衍生系統(tǒng)。主要負(fù)責(zé):Unix進(jìn)程模型、POSIX線程模型以及相關(guān)原語(yǔ)、文件系統(tǒng)訪問(wèn)、設(shè)備訪問(wèn)、網(wǎng)絡(luò)協(xié)議棧、Unix用戶與群組。
異常來(lái)源
iOS中異常主要來(lái)源于硬件異常、軟件異常、Mach異常、Signal異常,它們之間的關(guān)系如下圖:
Mach異常
Mach異常是系統(tǒng)內(nèi)核級(jí)異常,是由CPU觸發(fā)一個(gè)陷阱引發(fā),調(diào)用到Mach的異常處理程序,將來(lái)自硬件的異常轉(zhuǎn)換為Mach異常,然后將Mach異常傳遞到相應(yīng)的thread、task、host,若無(wú)結(jié)果返回,任務(wù)便會(huì)被終止。
Mach異常傳遞涉及到的內(nèi)核函數(shù)如下圖:
依據(jù)上圖提供信息,查閱蘋(píng)果開(kāi)源資料,找到對(duì)應(yīng)的函數(shù)信息,簡(jiǎn)單列舉如下(詳細(xì)查閱請(qǐng)前往此處):
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.線程級(jí)別
*/
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. 任務(wù)級(jí)別
*/
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? 主機(jī)級(jí)別
*/
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. 沒(méi)有處理終止
*/
// ...
(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!");
}
}
關(guān)于如何捕獲Mach異常,蘋(píng)果文檔描述很少,也沒(méi)有提供可用的API,具體的Mach內(nèi)核的API介紹,可從此處查閱。
Sigal信號(hào)
BSD派生自Unix操作系統(tǒng),屬于類Unix系統(tǒng),基于Mach內(nèi)核進(jìn)程任務(wù),提供POSIX應(yīng)用程序接口。詳見(jiàn)維基百科-XNU?;诖耍?code>Unix Signal機(jī)制同樣適用蘋(píng)果操作系統(tǒng)。蘋(píng)果系統(tǒng)對(duì)于Unix Signal的定義,可通過(guò)#import <sys/signal.h>跳轉(zhuǎn)查看。
Mach異常-> Signal信號(hào)
蘋(píng)果操作系統(tǒng)Mach異常與Signal信號(hào)共存。Mach將操作系統(tǒng)的核心部分當(dāng)做獨(dú)立進(jìn)程運(yùn)行,與BSD服務(wù)進(jìn)程之間通過(guò)IPC機(jī)制實(shí)現(xiàn)消息傳遞。同理Mach內(nèi)核態(tài)的異常也是基于IPC將異常消息發(fā)送到BSD,BSD將消息轉(zhuǎn)換為用戶態(tài)的Signal信號(hào)。具體流程如下:
- 蘋(píng)果內(nèi)核啟動(dòng)時(shí)會(huì)執(zhí)行
bsdinit_task()并最終調(diào)用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()初始化一個(gè)ux_handler()方法,并創(chuàng)建線程執(zhí)行它。
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()申請(qǐng)用于接收Mach內(nèi)核消息的端口(port)集合,接收來(lái)自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);
///收到消息后調(diào)用 mach_exc_server()
if (mach_exc_server(&exc_msg.Head, &rep_msg.Head)) {
///收到消息,回復(fù)消息
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內(nèi)核消息后,便會(huì)調(diào)用mach_exc_server函數(shù),這個(gè)函數(shù)會(huì)根據(jù)異常的行為調(diào)用對(duì)應(yīng)的catch_mach_exception_raise(),catch_mach_exception_raise_state(), 和catch_mach_exception_raise_state_identity(),catch_mach_exception_raise()會(huì)觸發(fā)Mach異常消息到Unix信號(hào)的轉(zhuǎn)換。而關(guān)于mach_exc_server()的實(shí)現(xiàn),并未像其他函數(shù)直接給出,具體請(qǐng)見(jiàn)此處。調(diào)用
catch_mach_exception_raise()將Mach異常轉(zhuǎn)換為Unix信號(hào),最終發(fā)送到對(duì)應(yīng)線程。
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()函數(shù),展示了Mach異常與Signal信號(hào)轉(zhuǎn)換關(guān)系。關(guān)于iOS中Mach異常信號(hào)的定義,可通過(guò)#include <mach/exception_types.h>跳轉(zhuǎn)查看。
硬件異常
硬件異常依據(jù)前文所述,主要為:中斷、缺陷、故障、終止。硬件異常的觸發(fā)流程如下圖:
軟件異常
應(yīng)用級(jí)別的異常,在iOS中就是NSException。如果NSException異常沒(méi)有捕獲處理(try-catch),系統(tǒng)最終會(huì)調(diào)用abort()函數(shù),向應(yīng)用程序發(fā)送SIGABRT的信號(hào)。
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);
}
//...
}
異常捕獲
上文分析可知道硬件異常與軟件異常最終都會(huì)轉(zhuǎn)換為Unix Signal,因此對(duì)于Signal信號(hào)的處理,可以覆蓋大部分的崩潰信息。除此之外系統(tǒng)給我們提供的NSException,可以用來(lái)獲取更詳細(xì)的奔潰信息?;诖?,下文我們將只對(duì)Signal和NSException的捕獲進(jìn)行簡(jiǎn)單示例。
Signal捕獲
在進(jìn)行Signal捕獲時(shí),需要注意覆蓋問(wèn)題。因?yàn)槊總€(gè)Signal對(duì)應(yīng)一個(gè)Handler的處理函數(shù),當(dāng)我們通過(guò)綁定我們自己的Hanlder來(lái)收集奔潰信息時(shí),可能會(huì)覆蓋其他三方庫(kù)已經(jīng)綁定的Handler導(dǎo)致他們無(wú)法收集奔潰信息。
核心代碼如下:
//頭文件
#import <sys/signal.h>
#import "execinfo.h"
///1.用以保存舊的handler
static struct sigaction *previous_signalHandlers = NULL;
///2.定義我們要處理的信號(hào)
static int signals[] = {SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGPIPE,SIGSEGV,SIGSYS,SIGTRAP};
///3.注冊(cè)`Handler`
+ (BOOL)registerSignalHandler; {
///初始化我們的Sigaction
struct sigaction action = { 0 };
///初始化存放舊的Sigaction數(shù)組
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);
/// 綁定我們的處理函數(shù)
action.sa_sigaction = &_handleSignal;
for (int i = 0; i < count; i ++) {
///遍歷信號(hào)
int signal = signals[i];///or *(signals + i)
///綁定新的`Sigaction`,存儲(chǔ)舊的`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--) {
/// 恢復(fù)舊的Sigaction,此次函數(shù)返回NO
sigaction(signals[j], &previous_signalHandlers[j], NULL);
}
return NO;
}
}
return YES;
}
/// 4. 信號(hào)處理函數(shù)
void _handleSignal(int sigNum,siginfo_t *info,void *ucontext_t) {
/// todo our operation
NSLog(@"?攔截到崩潰信號(hào):%d,打印堆棧信息:%@",[CrashSignals callStackSymbols]);
/// 獲取`sigNum`在信號(hào)數(shù)組中對(duì)應(yīng)的`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`忽略信號(hào),`SIG_DFL`默認(rèn)方式處理信號(hào)
return;
}
/// 恢復(fù)舊的`Sigaction`與Signal的綁定關(guān)系
sigaction(sigNum, &previous_action, NULL);
/// 重新拋出這個(gè)`Signal`,此時(shí)便會(huì)被`previous_action`的處理程序攔截到。
raise(sigNum);
}
//5. 函數(shù)的調(diào)用棧
+ (NSArray*)callStackSymbols {
/// ` int backtrace(void ** buffer , int size )`
/// void ** buffer:在`buffer`指向的數(shù)組返回程序棧楨的回溯,
/// void ** buffer: Each item in the array pointed to by buffer is of type void *
void* backtrace_buffer[128];
/// 返回值可能比 128大,大便截?cái)?,小則全部顯示
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);
/// 提取每個(gè)返回地址對(duì)應(yīng)的符號(hào)信息,棧楨是嵌套的
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捕獲
系統(tǒng)提供了對(duì)應(yīng)的處理iOS系統(tǒng)中未被捕獲的NSException的API,我們只需要按照API進(jìn)行操作即可,但與Signal一樣,需要注意多處注冊(cè)的覆蓋問(wèn)題,避免影響項(xiàng)目中其他收集程序。
核心代碼如下:
///1.聲明用以保存舊的`Hanlder`的靜態(tài)變量
static NSUncaughtExceptionHandler *previous_uncaughtExceptionHandler;
///注冊(cè)處理應(yīng)用級(jí)異常的`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);
}
// 殺掉程序,這樣可以防止同時(shí)拋出的SIGABRT被Signal異常捕獲
/// kill (cannot be caught or ignored)
kill(getpid(), SIGKILL);
}
調(diào)試驗(yàn)證
Xcode的Debug環(huán)境下,Signal異常與NSException異常都會(huì)被Xcode調(diào)試器攔截,不會(huì)走到我們的處理程序。因此代碼的調(diào)試驗(yàn)證,筆者采用模擬器運(yùn)行程序后,停止運(yùn)行,脫離Xcode的調(diào)試環(huán)境,重新在模擬器打開(kāi)程序,開(kāi)啟Mac的控制臺(tái)程序,點(diǎn)擊按鈕觸發(fā)奔潰,查看控制臺(tái)對(duì)應(yīng)模擬器的log記錄,來(lái)驗(yàn)證是否正確捕獲。另:Signal奔潰采用kill(getpid(), SIGBUS);來(lái)觸發(fā)。
參考資料
https://flylib.com/books/en/3.126.1.109/1/
http://shevakuilin.com/ios-crashprotection/