目的
- 探索iOS Crash分類及捕獲流程
- 了解Crash文件結構及段含義
- 了解Mach-o文件結構
- 分析Crash堆棧地址與符號表還原流程
Crash分類
Crash的主要原因是你的應用收到了未處理的信號。未處理信號可能來源于三個地方:kernel、其他進程、以及App本身。因此,crash異常也分為三種:
-
Mach異常:底層內核級異常。
用戶態的開發者可以直接通過Mach API設置thread,task,host的異常端口,來捕獲Mach異常。 -
Unix信號:又稱BSD信號。
它是UNIX層的異常信號標準,通過方法threadsignal()將信號投遞到出錯線程。可以通過方法signal(x, SignalHandler)來捕獲single。 -
NSException:應用級異常。
它是未被捕獲的Objective-C異常,導致程序向自身發送了SIGABRT信號而崩潰,對于未捕獲的Objective-C異常,是可以通過try catch來捕獲的,或者通過NSSetUncaughtExceptionHandler()機制來捕獲。
三者關系
Mach異常、Unix信號、NSException異常是什么?它們之間有什么相互關系?
Darwin是Mac OS和iOS的操作系統,而XNU是Darwin操作系統的內核部分。XNU是混合內核,兼具宏內核和微內核的特性,而Mach即為其微內核。
Mac可執行下述命令查看
Darwin版本號
。
~$ system_profiler SPSoftwareDataType
Software:
System Software Overview:
System Version: macOS 10.15 (19A583)
Kernel Version: Darwin 19.0.0
Boot Volume: Macintosh HD
Boot Mode: Normal
Computer Name: 58的MacBook Pro (2)
User Name: lltree (lltree)
Secure Virtual Memory: Enabled
System Integrity Protection: Enabled
Time since boot: 5 days 23:48
關系:
- 1 Mach異常是內核態的異常,屬于底層異常。
- 2 轉換Unix信號是為了兼容更為流行的POSIX標準(SUS規范),這樣不必了解Mach內核也可以通過Unix信號的方式來兼容開發。
- 3 因為硬件產生的信號(通過CPU陷阱)被Mach層捕獲,然后才轉換為對應的Unix信號;蘋果為了統一機制,于是操作系統和用戶產生的信號(通過調用kill和pthread_kill)也首先沉下來被轉換為Mach異常,再轉換為Unix信號。
Carsh傳遞流程:
硬件產生信號或者kill或pthread_kill信號 --> Mach異常 --> Unix信號(SIGABRT)
Mach異常捕獲
- 1、硬件處理器陷阱產生的信號被
Mach層
捕獲 - 2、Mach異常處理程序
exception_triage()
通過調用exception_deliver()
首先嘗試將異常拋給thread端口、然后嘗試拋給task端口,最后再拋給host端口(默認端口),exception_deliver
通過調用mach_exception_raise
,觸發異常; - 3、異常在內核中以消息機制進行處理,通過
task_set_exception_posrts()
設置自定義的接收Mach異常消息的端口,相當于插入了一個exception
處理程序。
實現:mach異常
以消息機制處理
而不是通過函數調用
,exception messages
可以被轉發到先前注冊的Mach exception處理程序
。這意味著你可以插入一個exception處理程序
,而不干擾現有的無論是調試器或Apple's crash reporter。
知道以上這些,那我們來嘗試撲捉一下Mach異常。系統讓用戶很少關心底層實現細節,因此Mach提供少量API
// 內核中創建一個消息隊列,獲取對應的port
mach_port_allocate();
// 授予task對port的指定權限
mach_port_insert_right();
//將端口設置為接受異常的端口
task_set_exception_ports()
代碼示例:異常捕獲
static bool installExceptionHandler()
{
KSLOG_DEBUG("Installing mach exception handler.");
bool attributes_created = false;
pthread_attr_t attr;
kern_return_t kr;
int error;
//使用mach_task_self獲取當前任務進程
const task_t thisTask = mach_task_self();
//Mach層異常類型
exception_mask_t mask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC |
EXC_MASK_SOFTWARE |
EXC_MASK_BREAKPOINT;
KSLOG_DEBUG("Backing up original exception ports.");
//備份異常端口
kr = task_get_exception_ports(thisTask,
mask,
g_previousExceptionPorts.masks,
&g_previousExceptionPorts.count,
g_previousExceptionPorts.ports,
g_previousExceptionPorts.behaviors,
g_previousExceptionPorts.flavors);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("task_get_exception_ports: %s", mach_error_string(kr));
goto failed;
}
if(g_exceptionPort == MACH_PORT_NULL)
{
KSLOG_DEBUG("Allocating new port with receive rights.");
/*
創建一個具有接受權限的異常端口
因為Mach異常其實是一個消息轉發的異常,
所以需要消息接收權限,在初始化異常端口的時候就賦予RECEIVE
*/
kr = mach_port_allocate(thisTask,
MACH_PORT_RIGHT_RECEIVE,
&g_exceptionPort);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr));
goto failed;
}
KSLOG_DEBUG("Adding send rights to port.");
//給端口添加發送權限
kr = mach_port_insert_right(thisTask,
g_exceptionPort,
g_exceptionPort,
MACH_MSG_TYPE_MAKE_SEND);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("mach_port_insert_right: %s", mach_error_string(kr));
goto failed;
}
}
KSLOG_DEBUG("Installing port as exception handler.");
//將端口設置為接受異常的端口
kr = task_set_exception_ports(thisTask,
mask,
g_exceptionPort,
(int)(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
THREAD_STATE_NONE);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr));
goto failed;
}
pthread_attr_init(&attr);
attributes_created = true;
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//創建輔助異常線程
KSLOG_DEBUG("Creating secondary exception thread (suspended).");
error = pthread_create(&g_secondaryPThread,
&attr,
&handleExceptions,
kThreadSecondary);
if(error != 0)
{
KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
goto failed;
}
g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
ksmc_addReservedThread(g_secondaryMachThread);
//創建主要異常線程
KSLOG_DEBUG("Creating primary exception thread.");
error = pthread_create(&g_primaryPThread,
&attr,
&handleExceptions,
kThreadPrimary);
if(error != 0)
{
KSLOG_ERROR("pthread_create: %s", strerror(error));
goto failed;
}
pthread_attr_destroy(&attr);
g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
ksmc_addReservedThread(g_primaryMachThread);
KSLOG_DEBUG("Mach exception handler installed.");
return true;
//失敗則退出
failed:
KSLOG_DEBUG("Failed to install mach exception handler.");
if(attributes_created)
{
pthread_attr_destroy(&attr);
}
uninstallExceptionHandler();
return false;
}
代碼示例:異常處理
static void* handleExceptions(void* const userData)
{
MachExceptionMessage exceptionMessage = {{0}};
MachReplyMessage replyMessage = {{0}};
char* eventID = g_primaryEventID;
const char* threadName = (const char*) userData;
pthread_setname_np(threadName);
if(threadName == kThreadSecondary)
{
KSLOG_DEBUG("This is the secondary thread. Suspending.");
thread_suspend((thread_t)ksthread_self());
eventID = g_secondaryEventID;
}
for(;;)
{
KSLOG_DEBUG("Waiting for mach exception");
// Wait for a message.
//等待mach exception,否則會阻塞
kern_return_t kr = mach_msg(&exceptionMessage.header,
MACH_RCV_MSG,
0,
sizeof(exceptionMessage),
g_exceptionPort,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
//捕獲到mach exception,跳出循環
if(kr == KERN_SUCCESS)
{
break;
}
// Loop and try again on failure.
KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
}
KSLOG_DEBUG("Trapped mach exception code 0x%llx, subcode 0x%llx",
exceptionMessage.code[0], exceptionMessage.code[1]);
if(g_isEnabled)
{
thread_act_array_t threads = NULL;
mach_msg_type_number_t numThreads = 0;
ksmc_suspendEnvironment(&threads, &numThreads);
g_isHandlingCrash = true;
kscm_notifyFatalExceptionCaptured(true);
KSLOG_DEBUG("Exception handler is installed. Continuing exception handling.");
// Switch to the secondary thread if necessary, or uninstall the handler
// to avoid a death loop.
if(ksthread_self() == g_primaryMachThread)
{
KSLOG_DEBUG("This is the primary exception thread. Activating secondary thread.");
// TODO: This was put here to avoid a freeze. Does secondary thread ever fire?
restoreExceptionPorts();
if(thread_resume(g_secondaryMachThread) != KERN_SUCCESS)
{
KSLOG_DEBUG("Could not activate secondary thread. Restoring original exception ports.");
}
}
else
{
KSLOG_DEBUG("This is the secondary exception thread.");// Restoring original exception ports.");
// restoreExceptionPorts();
}
// Fill out crash information
KSLOG_DEBUG("Fetching machine state.");
KSMC_NEW_CONTEXT(machineContext);
KSCrash_MonitorContext* crashContext = &g_monitorContext;
crashContext->offendingMachineContext = machineContext;
kssc_initCursor(&g_stackCursor, NULL, NULL);
if(ksmc_getContextForThread(exceptionMessage.thread.name, machineContext, true))
{
kssc_initWithMachineContext(&g_stackCursor, 100, machineContext);
KSLOG_TRACE("Fault address %p, instruction address %p",
kscpu_faultAddress(machineContext), kscpu_instructionAddress(machineContext));
if(exceptionMessage.exception == EXC_BAD_ACCESS)
{
crashContext->faultAddress = kscpu_faultAddress(machineContext);
}
else
{
crashContext->faultAddress = kscpu_instructionAddress(machineContext);
}
}
KSLOG_DEBUG("Filling out context.");
crashContext->crashType = KSCrashMonitorTypeMachException;
crashContext->eventID = eventID;
crashContext->registersAreValid = true;
crashContext->mach.type = exceptionMessage.exception;
crashContext->mach.code = exceptionMessage.code[0] & (int64_t)MACH_ERROR_CODE_MASK;
crashContext->mach.subcode = exceptionMessage.code[1] & (int64_t)MACH_ERROR_CODE_MASK;
if(crashContext->mach.code == KERN_PROTECTION_FAILURE && crashContext->isStackOverflow)
{
// A stack overflow should return KERN_INVALID_ADDRESS, but
// when a stack blasts through the guard pages at the top of the stack,
// it generates KERN_PROTECTION_FAILURE. Correct for this.
crashContext->mach.code = KERN_INVALID_ADDRESS;
}
crashContext->signal.signum = signalForMachException(crashContext->mach.type, crashContext->mach.code);
crashContext->stackCursor = &g_stackCursor;
kscm_handleException(crashContext);
KSLOG_DEBUG("Crash handling complete. Restoring original handlers.");
g_isHandlingCrash = false;
ksmc_resumeEnvironment(threads, numThreads);
}
KSLOG_DEBUG("Replying to mach exception message.");
// Send a reply saying "I didn't handle this exception".
replyMessage.header = exceptionMessage.header;
replyMessage.NDR = exceptionMessage.NDR;
replyMessage.returnCode = KERN_FAILURE;
mach_msg(&replyMessage.header,
MACH_SEND_MSG,
sizeof(replyMessage),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
return NULL;
}
Signal異常捕獲
Signal是Unix標準下的處理機制,讓開發者不必關系底層內核相關。為了維護一個統一的機制,操作系統和用戶嘗試的信號首先被轉換為Mach異常,然后再轉換為信號(Signals)。
Mach 異常在Mach層被捕獲并拋出后,會在BSD層被catch_mach_exception_raise
處理,并通過ux_exception()
將異常轉換為對應的UNIX信號,并通過threadsignal()
將信號投遞到出錯線程,iOS中的 POSIX API 就是通過 Mach 之上的 BSD 層實現的
代碼示例:注冊過程
void InstallSignalHandler(void){
signal(SIGHUP, SignalExceptionHandler);
signal(SIGINT, SignalExceptionHandler);
signal(SIGQUIT, SignalExceptionHandler);
signal(SIGABRT, SignalExceptionHandler);
signal(SIGILL, SignalExceptionHandler);
signal(SIGSEGV, SignalExceptionHandler);
signal(SIGFPE, SignalExceptionHandler);
signal(SIGBUS, SignalExceptionHandler);
signal(SIGPIPE, SignalExceptionHandler);
}
代碼示例:捕獲處理
void SignalExceptionHandler(int signal){
NSMutableString *mstr = [[NSMutableString alloc] init];
[mstr appendString:@"Stack:\n"];
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i <frames; ++i) {
[mstr appendFormat:@"%s\n", strs[I]];
}
[SignalHandler saveCreash:mstr];
}
NSException異常捕獲
NSException
異常屬于OC層異常
。該異常在OC層如果有對應的NSException(OC異常)
,就轉換成OC異常
,OC異常可以在OC層得到處理;如果OC異常一直得不到處理,程序會強行發送SIGABRT信號中斷程序。在OC層如果沒有對應的NSException,就只能讓Unix標準的signal機制來處理了。
代碼示例:注冊過程
NSSetUncaughtExceptionHandler(&handleUncaughtException);
代碼示例:捕獲處理
void HandleException(NSException *exception){
// 異常的堆棧信息
NSArray *stackArray = [exception callStackSymbols];
// 出現異常的原因
NSString *reason = [exception reason];
// 異常名稱
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
NSLog(@"%@", exceptionInfo);
[UncaughtExceptionHandler saveCreash:exceptionInfo];
}
Crash崩潰堆棧符號化
為探究崩潰堆棧符號化過程,我們提供一個簡單的Demo,點擊屏幕上按鈕執行onCrash
方法,主動觸發崩潰。
而我們收集iOS的崩潰信息時,獲取到的崩潰堆棧一般是如下的形式,全是十六進制的內存地址形式:
Last Exception Backtrace:
(0x1a8f9580c 0x1a8cbdfa4 0x1a8e9502c 0x102b298b4 0x1acfffab0
0x1aca378ac 0x1aca37c10 0x1aca36c2c 0x1ad039288 0x1ad03a5c8
0x1ad016b78 0x1ad08eef8 0x1ad091454 0x1ad08a2c8 0x1a8f137c4
0x1a8f1371c 0x1a8f12eb4 0x1a8f0e000 0x1a8f0d8a0 0x1b2e65328
0x1acffe740 0x102b2a3e4 0x1a8d98360)
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x00000001a8d8dec4 0x1a8d69000 + 151236
1 libsystem_pthread.dylib 0x00000001a8ca9774 0x1a8ca7000 + 10100
2 libsystem_c.dylib 0x00000001a8bfd844 0x1a8b8a000 + 473156
3 libc++abi.dylib 0x00000001a8d567d4 0x1a8d55000 + 6100
4 libc++abi.dylib 0x00000001a8d569c4 0x1a8d55000 + 6596
5 libobjc.A.dylib 0x00000001a8cbe258 0x1a8cb8000 + 25176
6 Simple-Example 0x0000000102b3e7cc 0x102b24000 + 108492
7 libc++abi.dylib 0x00000001a8d63304 0x1a8d55000 + 58116
8 libc++abi.dylib 0x00000001a8d62ed8 0x1a8d55000 + 57048
9 libobjc.A.dylib 0x00000001a8cbe158 0x1a8cb8000 + 24920
10 CoreFoundation 0x00000001a8f0d910 0x1a8e6a000 + 669968
11 GraphicsServices 0x00000001b2e65328 0x1b2e62000 + 13096
12 UIKitCore 0x00000001acffe740 0x1ac5fd000 + 10491712
13 Simple-Example 0x0000000102b2a3e4 0x102b24000 + 25572
14 libdyld.dylib 0x00000001a8d98360 0x1a8d97000 + 4960
這樣的格式我們很難看出實際含義,無法定位問題代碼,只有將它們轉化為可讀的形式才有意義:
Bugly異常上報解析結果
Last Exception Backtrace:
0 CoreFoundation 0x1a8f9580c __exceptionPreprocess + 220
1 libobjc.A.dylib 0x1a8cbdfa4 objc_exception_throw + 55
2 CoreFoundation 0x1a8e9502c -[__NSArray0 objectAtIndex:] + 107
3 Simple-Example 0x102c318b4 -[ViewController onCrash:] + 22708 (ViewController.m:28)
4 UIKitCore 0x1acfffab0 -[UIApplication sendAction:to:from:forEvent:] + 95
5 UIKitCore 0x1aca378ac -[UIControl sendAction:to:forEvent:] + 239
6 UIKitCore 0x1aca37c10 -[UIControl _sendActionsForEvents:withEvent:] + 407
7 UIKitCore 0x1aca36c2c -[UIControl touchesEnded:withEvent:] + 519
8 UIKitCore 0x1ad039288 -[UIWindow _sendTouchesForEvent:] + 2323
9 UIKitCore 0x1ad03a5c8 -[UIWindow sendEvent:] + 3351
10 UIKitCore 0x1ad016b78 -[UIApplication sendEvent:] + 335
11 UIKitCore 0x1ad08eef8 __dispatchPreprocessedEventFromEventQueue + 5879
12 UIKitCore 0x1ad091454 __handleEventQueueInternal + 4923
13 UIKitCore 0x1ad08a2c8 __handleHIDEventFetcherDrain + 107
14 CoreFoundation 0x1a8f137c4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 23
15 CoreFoundation 0x1a8f1371c __CFRunLoopDoSource0 + 79
16 CoreFoundation 0x1a8f12eb4 __CFRunLoopDoSources0 + 179
17 CoreFoundation 0x1a8f0e000 __CFRunLoopRun + 1079
18 CoreFoundation 0x1a8f0d8a0 CFRunLoopRunSpecific + 463
19 GraphicsServices 0x1b2e65328 GSEventRunModal + 103
20 UIKitCore 0x1acffe740 UIApplicationMain + 1935
21 Simple-Example 0x102c323e4 main + 25572 (main.m:16)
22 libdyld.dylib 0x1a8d98360 start + 3
或者Xcode中的打印窗口輸出結果
如上所示,我們一眼就能看明白,這次崩潰發生在
ViewController.m
文件的28
行,對應的方法是onCrash:
,調用[__NSArray0 objectAtIndex:]
引起的崩潰。那么這樣的符號化又是如何實現的呢?
認識符號表
符號表是內存地址與函數名、文件名、行號的映射表。符號表元素如下所示:
<起始地址> <結束地址> <函數>
[<文件名>:<行號>
],
有了符號表能快速并準確地定位用戶APP發生Crash的代碼位置,
我們可以使用符號表對APP發生Crash的程序堆棧進行解析和還原
什么是dSYM文件?
iOS平臺中,dSYM文件是指具有調試信息的目標文件,文件名通常為: com.公司名.dSYM。如下圖所示:
為直觀呈現符號表,使用Bugly中的提取符號表命令從dSYM中提取符號表,類似如下結構:
5848 5880 -[ViewController onCrash:] ViewController.m:24
5880 58a0 -[ViewController onCrash:] ViewController.m:26
58a0 58a4 -[ViewController onCrash:] ViewController.m:26
58a4 58a8 -[ViewController onCrash:] ViewController.m:28
58a8 58b4 -[ViewController onCrash:] ViewController.m:28
58b4 58c0 -[ViewController onCrash:] ViewController.m:28
58c0 58f0 -[ViewController onCrash:] ViewController.m:29
符號化的依據來自dSYM文件, dSYM文件也是Mach-o格式,我們按照Mach-o格式一步一步解析即可。
Mach-O文件類型分類:
- Executable:應用可執行的二進制文件,如.m/.h文件經過編譯后會生成對應的Mach-O文件
- Dylib Library:動態鏈接庫
- Static Library:靜態鏈接庫
- Bundle:不能被鏈接 Dylib,只能在運行使用dlopen()加載
- Relocatable Object File:可重定向文件類型
從圖上我們可以大概的看出Mach-O可以分為3個部分
- Header
- Segment
- section
如圖所示,header后面是segment,然后再跟著section,而一個segment是可以包含多個section的。為更加直觀的呈現dSYM內容,我們把dSYM文件放入可視化工具:
其中的LC_SEGMENT_64(_DEARF)
及Section64 Header(_debug_line)
則是我們目標分析對象。
Crash日志格式:
iOS的crash報告日志可以分為:
- 頭部(Header)
- 異常信息(Exception Information)
- 診斷信息(Additional Diagnostic Information)
- 線程堆棧(Backtraces)
- 線程狀態(Thread State)
- 庫信息(Binary Images)
這個六個部分。實例如下:
符號化解析流程
我們從剛才不管是上報到存儲到手機端的Crash文件或者Xcode崩潰打印的信息中,在回溯堆棧中都離不開調用堆棧地址
。如下所示:
Last Exception Backtrace:
(0x1a8f9580c 0x1a8cbdfa4 0x1a8e9502c 0x102b298b4 0x1acfffab0
0x1aca378ac 0x1aca37c10 0x1aca36c2c 0x1ad039288 0x1ad03a5c8
0x1ad016b78 0x1ad08eef8 0x1ad091454 0x1ad08a2c8 0x1a8f137c4
0x1a8f1371c 0x1a8f12eb4 0x1a8f0e000 0x1a8f0d8a0 0x1b2e65328
0x1acffe740 0x102b2a3e4 0x1a8d98360)
我們如何把實際運行中的調用堆棧地址
通過dSYM文件
關聯起來,解析出來崩潰信息呢?例如
0x102b298b4
與解析完的
3 Simple-Example 0x102c318b4 -[ViewController onCrash:] + 22708 (ViewController.m:28)
如何形成對應關系?
蘋果為了杜絕黑客攻擊App,因此在App啟動的時候,引入
ASLR地址空間布局隨機化
ASLR:Address space layout randomization,將可執行程序隨機裝載到內存中,這里的隨機只是偏移,而不是打亂,具體做法就是通過內核將 Mach-O的平移
某個隨機系數。slide 正是ASLR引入的偏移
每次使用隨機偏移地址加載App程序,這樣針對該行代碼ViewController.m:28
每次App運行的時候實際運行地址(例如:0x102b298b4)
都是不一樣的。
在Crash
崩潰信息中,存在每個庫信息,示例如下:
Binary Images:
0x102b24000 - 0x102baffff Simple-Example arm64 <86bf04850a8a344d8a426ed6859383e3> /var/containers/Bundle/Application/3D7D3552-E687-4007-B166-055B28C53F4C/Simple-Example.app/Simple-Example
0x102d14000 - 0x102d1ffff libobjc-trampolines.dylib arm64 <ef2f193f10af36428d9b573e1d256368> /usr/lib/libobjc-trampolines.dylib
0x102dcc000 - 0x102e2ffff dyld arm64 <330227f4c8da3dcebfea057e7770ae9a> /usr/lib/dyld
庫信息都存在,程序內存占用地址空間例如Simple-Example
本次隨機偏移后的地址為0x102b24000 - 0x102baffff
。因此我們可以根據dSYM文件
中程序加載地址計算出偏移slide
。
偏移slide = 0x102b24000 - 0x100000000 = 0x2b24000
根據實際崩潰地址
減去偏移slide
則可計算除符號表中地址0x1000058b4
0x102c318b4 - 0x2b24000 = 0x1000058b4
可以通過以下命令查詢崩潰信息
~ lltree$ dwarfdump --arch arm64 --lookup 0x1000058b4 Simple-Example.app.dSYM
查詢結果如下
Simple-Example.app.dSYM/Contents/Resources/DWARF/Simple-Example: file format Mach-O arm64
0x00046f0d: Compile Unit: length = 0x00000783 version = 0x0002 abbr_offset = 0x0000 addr_size = 0x08 (next unit at 0x00047694)
0x00046f18: DW_TAG_compile_unit
DW_AT_producer ("Apple clang version 11.0.0 (clang-1100.0.33.12)")
DW_AT_language (DW_LANG_ObjC)
DW_AT_name ("/Users/XXX/Desktop/iOS\346\272\220\344\273\243\347\240\201\347\240\224\347\251\266/KSCrash-master-2/iOS/Simple-Example/ViewController.m")
DW_AT_stmt_list (0x0000a5bc)
DW_AT_comp_dir ("/Users/XXX/Desktop/iOS\346\272\220\344\273\243\347\240\201\347\240\224\347\251\266/KSCrash-master-2/iOS")
DW_AT_GNU_pubnames (0x01)
DW_AT_APPLE_major_runtime_vers (0x02)
DW_AT_low_pc (0x0000000100005710)
DW_AT_high_pc (0x00000001000059b0)
0x00047081: DW_TAG_subprogram
DW_AT_low_pc (0x0000000100005848)
DW_AT_high_pc (0x00000001000058f0)
DW_AT_frame_base (DW_OP_reg29 W29)
DW_AT_object_pointer (0x000470a0)
DW_AT_name ("-[ViewController onCrash:]")
DW_AT_decl_file ("/Users/XXX/Desktop/iOS XXX/iOS/Simple-Example/ViewController.m")
DW_AT_decl_line (24)
DW_AT_prototyped (0x01)
Line info: file 'ViewController.m', line 28, column 8, start line 24
該信息如何在dSYM文件中體現呢?
DWARF 中的調試信息被放在一個叫作 .debug_info 的段中,該段與 DWARF 中其它的段類似,可以看成是一個表格狀的結構,表中每一條記錄叫作一個 DIE(debugging information entry), 一個 DIE 由一個 tag 及 很多 attribute 組成,其中 tag 用于表示當前的 DIE 的類型,類型指明當前 DIE 用于描述什么東西,如函數,變量,類型等,而 attribute 則是一對對的 key/value 用于描述其它一些信息,在 linux 下我們可以用如下命令來查看 ELF 中的調試信息:
為此我們使用如下命令,提取出
dwarfdump -p -debug-info /Users/XXX/Desktop/crash分析/Simple-Example.app.dSYM/Contents/Resources/DWARF/Simple-Example >line-e.txt
line-e.txt
結果如下:
該文件中,該DIE段信息中與共計7段信息,與從dSYM中使用Bugly提取符號表命令提取符號表段信息相符
5848 5880 -[ViewController onCrash:] ViewController.m:24
5880 58a0 -[ViewController onCrash:] ViewController.m:26
58a0 58a4 -[ViewController onCrash:] ViewController.m:26
58a4 58a8 -[ViewController onCrash:] ViewController.m:28
58a8 58b4 -[ViewController onCrash:] ViewController.m:28
58b4 58c0 -[ViewController onCrash:] ViewController.m:28
58c0 58f0 -[ViewController onCrash:] ViewController.m:29
目前遺留問題:
每段信息58b4
與虛擬內存起始地址0x0000000100005848
之間關系,如何計算出來,暫時未知?
至此:結束