原文連結:
1. iOS Mach異常
1.1 XNU
Darwin是Mac OS和iOS的作業系統,而XNU是Darwin作業系統的核心部分。XNU是混合核心,兼具單核心和微核心的特性,而Mach即為其微核心。
Darwin作業系統和MacOS、iOS系統版本號的對應如上圖所示,Mac可執行下述命令檢視Darwin版本號。
system_profiler SPSoftwareDataType
1.2 Mach
Mach:[mʌk],作業系統微核心,是許多新作業系統的設計基礎。
Mach微核心中有幾個基礎概念:
Tasks,擁有一組系統資源的物件,允許"thread"在其中執行。
Threads,執行的基本單位,擁有task的上下文,並共享其資源。
Ports,task之間通訊的一組受保護的訊息佇列;task可對任何port傳送/接收資料。
Message,有型別的資料物件集合,只可以傳送到port。
1.3 模擬Mach Message傳送
● Mach提供少量API,蘋果文件介紹較少。
// 核心中建立一個訊息佇列,獲取對應的port
mach_port_allocate();
// 授予task對port的指定許可權
mach_port_insert_right();
// 透過設定引數:MACH_RSV_MSG/MACH_SEND_MSG用於接收/傳送mach message
mach_msg();
下述程式碼模擬向Mach Port傳送Message,接收Message後做處理:
● 首先呼叫createPortAndAddListener建立Mach Port;
● 呼叫sendMachPortMessage:向已建立的Mach Port傳送訊息;
● 執行結果示例:
2018-02-27 09:33:37.797435+0800 xxx[54456:5198921] create a port: 41731
2018-02-27 09:33:37.797697+0800 xxx[54456:5198921] Send a mach message: [100].
2018-02-27 09:33:37.797870+0800 xxx[54456:5199525] Receive a mach message:[100], remote_port: 0, local_port: 41731, exception code: 28672
● 示例程式碼:
// 建立Mach Port並監聽訊息
+ (mach_port_t)createPortAndAddListener {
mach_port_t server_port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
assert(kr == KERN_SUCCESS);
NSLog(@"create a port: %d", server_port);
kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
assert(kr == KERN_SUCCESS);
[self setMachPortListener:server_port];
return server_port;
}
+ (void)setMachPortListener:(mach_port_t)mach_port {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
mach_message mach_message;
mach_message.Head.msgh_size = 1024;
mach_message.Head.msgh_local_port = server_port;
mach_msg_return_t mr;
while (true) {
mr = mach_msg(&mach_message.Head,
MACH_RCV_MSG | MACH_RCV_LARGE,
0,
mach_message.Head.msgh_size,
mach_message.Head.msgh_local_port,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (mr != MACH_MSG_SUCCESS && mr != MACH_RCV_TOO_LARGE) {
NSLog(@"error!");
}
mach_msg_id_t msg_id = mach_message.Head.msgh_id;
mach_port_t remote_port = mach_message.Head.msgh_remote_port;
mach_port_t local_port = mach_message.Head.msgh_local_port;
NSLog(@"Receive a mach message:[%d], remote_port: %d, local_port: %d, exception code: %d",
msg_id,
remote_port,
local_port,
mach_message.exception);
abort();
}
});
}
// 向指定Mach Port傳送訊息
+ (void)sendMachPortMessage:(mach_port_t)mach_port {
kern_return_t kr;
mach_msg_header_t header;
header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
header.msgh_size = sizeof(mach_msg_header_t);
header.msgh_remote_port = mach_port;
header.msgh_local_port = MACH_PORT_NULL;
header.msgh_id = 100;
NSLog(@"Send a mach message: [%d].", header.msgh_id);
kr = mach_msg(&header,
MACH_SEND_MSG,
header.msgh_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
}
1.4 捕獲Mach異常
● task_set_exception_ports() 設定核心接收Mach異常訊息的Port,替換為自定義的Port後,即可捕獲程式執行過程中產生的異常訊息。
● 執行結果示例:
2018-02-27 09:52:11.483076+0800 xxx[55018:5253531] create a port: 23299
2018-02-27 09:52:14.484272+0800 xxx[55018:5253531] ********** Make a [BAD MEM ACCESS] now. **********
2018-02-27 09:52:14.484477+0800 xxx[55018:5253611] Receive a mach message:[2405], remote_port: 23555, local_port: 23299, exception code: 1
● 示例程式碼:
+ (void)createAndSetExceptionPort {
mach_port_t server_port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
assert(kr == KERN_SUCCESS);
NSLog(@"create a port: %d", server_port);
kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
assert(kr == KERN_SUCCESS);
kr = task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS | EXC_MASK_CRASH, server_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
[self setMachPortListener:server_port];
}
// 構造BAD MEM ACCESS Crash
- (void)makeCrash {
NSLog(@"********** Make a [BAD MEM ACCESS] now. **********");
*((int *)(0x1234)) = 122;
}
1.5 Runloop
Mach Port的應用不止於核心級別,在Cocoa Foundation和Core Foundation層同樣有其應用,比如說:Runloop。
Runloop sources分兩類:
1.Input sources
Port-Based sources
Custom Input sources
2.Timer sources
其中Port-Based sources即基於Mach Port,在Runloop中完成訊息傳遞。
上述的Mach API為核心層透出介面,Cocoa Foundation和Core Foundation層分別封裝了Mach Port的介面供呼叫,參考:Apple - Runloop Programming Guard,有詳細的示例程式碼。
2. signal訊號
signal是一種軟中斷訊號,提供非同步事件處理機制。signal是程式間相互傳遞資訊的一種粗糙方法,使用場景:
程式終止相關;
終端互動;
程式設計錯誤或硬體錯誤相關,系統遇到不可恢復的錯誤時觸發崩潰機制讓程式退出,比如:除0、記憶體寫入錯誤等。
這裡我們主要考慮系統遇到不可恢復的錯誤時即Crash時,訊號相關的應用。signal訊號處理是UNIX作業系統機制,所以Android平臺理論上也是使用的,可以基於signal來捕獲Android Native Crash。
2.1 signal註冊和處理
signal()
#import<sys/signal.h>;
註冊signal handler;
呼叫成功時,會移除signo訊號當前的操作,以handler指定的新訊號處理程式替代;
訊號處理函式返回void,因為沒有地方給該函式返回。註冊自定義訊號處理函式,構造Crash後,發出訊號並執行自定義訊號處理邏輯。
【附】:Xcode Debug執行時,新增斷點,在Crash觸發前,執行pro hand -p true -s false SIGABRT命令。
(lldb) pro hand -p true -s false SIGABRT
NAME PASS STOP NOTIFY
=========== ===== ===== ======
SIGABRT true false true
2018-02-27 12:57:25.284651+0800 xxx[58061:5651844] ********** Make a 'NSRangeException' now. **********
2018-02-27 12:57:25.294945+0800 xxx[58061:5651844] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
2018-02-27 12:57:25.888332+0800 xxx[58061:5651844] [signal handler] - handle signal: 6
● 示例程式碼:
// 設定自定義訊號處理函式
+ (void)setSignalHandler {
signal(SIGABRT, test_signal_handler);
}
static void test_signal_handler(int signo) {
NSLog(@"[signal handler] - handle signal: %d", signo);
}
// 構造NSRangeException異常,觸發SIGABRT訊號傳送
- (void)makeCrash {
NSLog(@"********** Make a 'NSRangeException' now. **********");
NSArray *array = @[ @"aaa" ];
}
2.2 LLDB Debugger
Xcode Debug模式執行App時,App程式signal被LLDB Debugger偵錯程式捕獲;需要使用LLDB除錯命令,將指定signal處理拋到使用者層處理,方便除錯。
● 檢視全部訊號傳遞配置:
// process handle縮寫
pro hand
● 修改指定訊號傳遞配置:
// option:
// -P: PASS
// -S: STOP
// -N: NOTIFY
pro hand -option false 訊號名
// 例:SIGABRT訊號處理在LLDB不停止,可繼續拋到使用者層
pro hand -s false SIGABRT
2.3 可重入
向核心傳送訊號時,程式可能執行到程式碼的任意位置,例:程式在執行重要操作,中斷後可能產生不一致狀態,或程式正在處理另一訊號。因此要確保訊號處理程式只執行可重入操作:
● 寫中斷處理程式時,假定中斷程式可能處於不可重入函式中。
● 慎重修改全域性資料。
2.4 高階訊號處理
signal()函式非常基礎,只提供了最低限度的訊號管理的標準。而sigaction()系統呼叫,提供更強大的訊號管理能力。當訊號處理程式執行時,可以用來阻塞特定訊號的接收,也可以用來獲取訊號傳送時各種作業系統和程式狀態的資訊。
● 示例程式碼:
// 設定自定義訊號處理函式
+ (void)setSignalHandlerInAdvance {
struct sigaction act;
// 當sa_flags設為SA_SIGINFO時,設定sa_sigaction來指定訊號處理函式
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = test_signal_action_handler;
sigaction(SIGABRT, &act, NULL);
}
static void test_signal_action_handler(int signo, siginfo_t *si, void *ucontext) {
NSLog(@"[sigaction handler] - handle signal: %d", signo);
// handle siginfo_t
NSLog(@"siginfo: {\n si_signo: %d,\n si_errno: %d,\n si_code: %d,\n si_pid: %d,\n si_uid: %d,\n si_status: %d,\n si_value: %d\n }",
si->si_signo,
si->si_errno,
si->si_code,
si->si_pid,
si->si_uid,
si->si_status,
si->si_value.sival_int);
}
3. 參考
● Apple - Understanding and Analyzing Application Crash Reports
● Apple - Runloop Programming Guard
● Wiki - Mach
● Apple - Mach Overview
● 漫談iOS Crash收集框架
● The LLDB Debugger
iOS Mach異常和signal訊號
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69977274/viewspace-2698927/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- python 之訊號SignalPython
- Linux訊號(signal)機制Linux
- 硬中斷,軟中斷,訊號,異常
- WiFi訊號監測工具:WiFi Signal for MacWiFiMac
- 系統程式設計-訊號-總體概述和signal基本使用程式設計
- xenomai核心解析之訊號signal(二)---xenomai訊號處理機制AI
- 異常和異常呼叫鏈
- iOS 逆向 - Mach-O檔案iOSMac
- 一次訊號量引發的tomcat異常退出Tomcat
- 異常-異常的概述和分類
- 異常-編譯期異常和執行期異常的區別編譯
- Linux核心筆記009 - 中斷、異常、陷阱、Bottom half、訊號Linux筆記
- 儲存過程——異常捕獲&列印異常資訊儲存過程
- iOS逆向之五 MACH O檔案解析iOSMac
- 異常和中斷
- NoClassDefFoundError 和 ClassNotFoundException異常ErrorException
- 異常-自定義異常的實現和測試
- JAVA異常和日誌Java
- PHP錯誤和異常PHP
- 常見的參考訊號
- 異常錯誤資訊處理
- 異常監控和判斷
- python的檔案和異常Python
- 迭代器和異常處理
- Flutter之異常和錯誤Flutter
- 約束和異常處理
- 異常篇——異常記錄
- 異常篇——異常處理
- springboot下新增全域性異常處理和自定義異常處理Spring Boot
- IOS系統閃退異常(Crash)捕獲處理iOS
- iOS 【如何去除 UILabel 邊緣異常黑線/陰影】iOSUI
- Android 收集程式崩潰異常資訊Android
- 記一次訂單號重複的異常
- Java 異常(二) 自定義異常Java
- 異常-異常的注意事項
- Java 異常處理:使用和思考Java
- Java異常十一:使用throw丟擲異常物件;throw和throws的區別Java物件
- iOS GCD (四) dispatch_semaphore 訊號量iOSGC