下載objc818可調試源碼c++
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
//讀取影響運行時的環境變量,如配置log的打印
environ_init();
//關於線程key的綁定 - 好比每線程數據的析構函數
tls_init();
//運行C ++靜態構造函數。在dyld調用咱們的靜態構造函數以前,`libc` 會調用 _objc_init(), 所以咱們必須本身作
static_init();
//runtime運行時環境初始化,裏面主要是:unattachedCategories,allocatedClasses 後面會分析
runtime_init();
//初始化libobjc的異常處理系統
exception_init();
#if __OBJC2__
// 緩存條件初始化
cache_t::init();
#endif
//啓動回調機制。一般這不會作什麼,由於全部的初始化都是惰性的,可是對於某些進程,咱們會火燒眉毛地加載trampolines dylib
_imp_implementationWithBlock_init();
//註冊通知,須要參數map_images()、load_images()
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
// map_images()
// load_images()
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
複製代碼
環境變量在調試的時候能夠控制日誌的輸出git
environ_init
部分代碼:github
//只有知足 PrintHelp 或 PrintOptions 纔會進行_objc_inform打印
if (PrintHelp || PrintOptions) {
.....省略部分代碼
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
複製代碼
去掉if
判斷編程
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
_objc_inform("%s: %s", opt->env, opt->help);
_objc_inform("%s is set", opt->env);
}
複製代碼
運行程序就會打印不少環境變量,以下:數組
objc[3443]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[3443]: OBJC_PRINT_IMAGES is set
objc[3443]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[3443]: OBJC_PRINT_IMAGE_TIMES is set
objc[3443]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods objc[3443]: OBJC_PRINT_LOAD_METHODS is set objc[3443]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods objc[3443]: OBJC_PRINT_INITIALIZE_METHODS is set objc[3443]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod: objc[3443]: OBJC_PRINT_RESOLVED_METHODS is set objc[3443]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup objc[3443]: OBJC_PRINT_CLASS_SETUP is set objc[3443]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup objc[3443]: OBJC_PRINT_PROTOCOL_SETUP is set objc[3443]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars objc[3443]: OBJC_PRINT_IVAR_SETUP is set objc[3443]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables objc[3443]: OBJC_PRINT_VTABLE_SETUP is set objc[3443]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods objc[3443]: OBJC_PRINT_VTABLE_IMAGES is set objc[3443]: OBJC_PRINT_CACHE_SETUP: log processing of method caches objc[3443]: OBJC_PRINT_CACHE_SETUP is set objc[3443]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging objc[3443]: OBJC_PRINT_FUTURE_CLASSES is set objc[3443]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache objc[3443]: OBJC_PRINT_PREOPTIMIZATION is set objc[3443]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables objc[3443]: OBJC_PRINT_CXX_CTORS is set objc[3443]: OBJC_PRINT_EXCEPTIONS: log exception handling objc[3443]: OBJC_PRINT_EXCEPTIONS is set objc[3443]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw() objc[3443]: OBJC_PRINT_EXCEPTION_THROW is set objc[3443]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers objc[3443]: OBJC_PRINT_ALT_HANDLERS is set objc[3443]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations objc[3443]: OBJC_PRINT_REPLACED_METHODS is set objc[3443]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions objc[3443]: OBJC_PRINT_DEPRECATION_WARNINGS is set objc[3443]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools objc[3443]: OBJC_PRINT_POOL_HIGHWATER is set objc[3443]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods objc[3443]: OBJC_PRINT_CUSTOM_CORE is set objc[3443]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods objc[3443]: OBJC_PRINT_CUSTOM_RR is set objc[3443]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods objc[3443]: OBJC_PRINT_CUSTOM_AWZ is set objc[3443]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields objc[3443]: OBJC_PRINT_RAW_ISA is set objc[3443]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded objc[3443]: OBJC_DEBUG_UNLOAD is set objc[3443]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses objc[3443]: OBJC_DEBUG_FRAGILE_SUPERCLASSES is set objc[3443]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization objc[3443]: OBJC_DEBUG_NIL_SYNC is set objc[3443]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars objc[3443]: OBJC_DEBUG_NONFRAGILE_IVARS is set objc[3443]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use objc[3443]: OBJC_DEBUG_ALT_HANDLERS is set objc[3443]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak objc[3443]: OBJC_DEBUG_MISSING_POOLS is set objc[3443]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools objc[3443]: OBJC_DEBUG_POOL_ALLOCATION is set objc[3443]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present objc[3443]: OBJC_DEBUG_DUPLICATE_CLASSES is set objc[3443]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing objc[3443]: OBJC_DEBUG_DONT_CRASH is set objc[3443]: OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated objc[3443]: OBJC_DEBUG_POOL_DEPTH is set objc[3443]: OBJC_DISABLE_VTABLES: disable vtable dispatch objc[3443]: OBJC_DISABLE_VTABLES is set objc[3443]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache objc[3443]: OBJC_DISABLE_PREOPTIMIZATION is set objc[3443]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al. objc[3443]: OBJC_DISABLE_TAGGED_POINTERS is set objc[3443]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers objc[3443]: OBJC_DISABLE_TAG_OBFUSCATION is set objc[3443]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields objc[3443]: OBJC_DISABLE_NONPOINTER_ISA is set objc[3443]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork objc[3443]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set objc[3443]: OBJC_DISABLE_FAULTS: disable os faults objc[3443]: OBJC_DISABLE_FAULTS is set objc[3443]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches objc[3443]: OBJC_DISABLE_PREOPTIMIZED_CACHES is set objc[3443]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers objc[3443]: OBJC_DISABLE_AUTORELEASE_COALESCING is set objc[3443]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy objc[3443]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set 複製代碼
輸出了環境變量,能夠用於查詢配置。也可用終端輸出環境變量:緩存
如何使用環境變量呢?服務器
好比使用其中的OBJC_DISABLE_NONPOINTER_ISA
。ISA
分爲兩種,一隻是存指針nonpointer
是0
,另外一種是NONPOINTER_ISA
,nonpointer
是1
(這裏有講解)。markdown
配置環境變量,不使用NONPOINTER_ISA
。Edit Scheme
> Arguments
app
變成了存指針函數
又如配置打印load
方法的環境變量OBJC_PRINT_LOAD_METHODS
打印出實現load
方法的出處
在此處定義一個全局構造函數
lldb
調試
在此處就調用了全局構造函數,並無在dyld
階段調用。
C++
靜態構造函數在dyld
調用咱們的靜態構造函數以前,libc
會調用 _objc_init()
, 所以咱們必須本身作
void runtime_init(void)
{
objc::unattachedCategories.init(32);
//已經被開闢過表
objc::allocatedClasses.init();
}
複製代碼
void exception_init(void) {
old_terminate = std::set_terminate(&_objc_terminate);
}
複製代碼
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
//出現異常會這些這裏,調用uncaught_handler
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
複製代碼
在發生異常的時候會調用uncaught_handler
,這是個回調,經過給回調賦值,可實現異常攔截。
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
`objc_setUncaughtExceptionHandler`就至關因而`uncaught_handler`
複製代碼
使用LGUncaughtExceptionHandle
對異常進行攔截
2.6.1.首先須要攔截註冊
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//註冊異常攔截
[LGUncaughtExceptionHandle installUncaughtSignalExceptionHandler];
return YES;
}
複製代碼
2.6.2.實現installUncaughtSignalExceptionHandler
方法
+ (void)installUncaughtSignalExceptionHandler{
NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}
void LGExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
}
複製代碼
objc_setUncaughtExceptionHandler
至關於上層代碼的NSSetUncaughtExceptionHandler
若是出現異常會被LGExceptionHandlers
攔截。
LGUncaughtExceptionHandle
完整代碼會在文章末尾貼出。
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
複製代碼
在_objc_init
中_dyld_objc_notify_register
有三個參數
map_images
:管理文件中和動態庫中全部的符號(class
,Protocol
,selector
,category
)load_images
:加載執行load
方法unmap_image
:dyld
釋放資源void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) {
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
複製代碼
map_images_nolock
函數比較多,有一百多行,重點看_read_images
_read_images
代碼有三百多行,這裏看總體流程:
_read_images
總體流程流程以下:
1.條件控制進行一次的加載
2.修復與編譯階段的@selector
的混亂
3.錯誤混亂類的處理
4.修復一些消息
5.當咱們類裏有協議的時候:readProtocol
6.修復沒有被加載的協議
7.分類處理
8.類的加載處理
9.沒有被處理的類 優化那些被侵犯的類
1.判斷進行一次的加載,建立總表
gdb_objc_realized_classes
是個總表,無論是否實現 runtime_init
中的 表allocatedClasses
是已經被開闢過的表
2.修復預編譯階段的 @selector
的混亂問題
兩個sel
地址不一樣,_getObjc2SelectorRefs
是表裏面讀的macho
裏,有相對地址和偏移地址,會變化,sel_registerNameNoLock
是dyld
讀出來的,是連接整個程序的,要以它爲準,因此要將表裏的sel
替換爲連接事後的sel
。
3.錯誤混亂的類處理
在內存裏面有個原則,當類的內存發生了移動,會把原始那塊內存刪掉,若是沒被刪掉,就須要上面的處理。
在readClass
裏面加一些判斷代碼,目的是隻研究LGPerson
類的加載
接着往下看
將cls
和name
加到總表中。以key-value
形式加入,key
是name
,value
是cls
。
經過readClass
的處理,知道了這個地址是哪一個類,其餘的詳細信息還不清楚,這時可採起試探的方法。
根據map_images
總體流程的分析,可以知道類的加載在第八步和第九步,找到相關位置,添加試探代碼:
const char *mangledName = cls->nonlazyMangledName();
//若是是LGPerson就打印調用信息
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
printf("%s Realize newly-resolved future classes: - %s\n",__func__,mangledName);
}
複製代碼
LGPeron
類中添加load
方法後 運行程序,斷點首先斷在了一張圖。重點是實現類的方法realizeClassWithoutSwift
。若是LGPeron
類中沒有添加load
方法,也會調用realizeClassWithoutSwift
。 添加試探代碼,並斷點調試,打印調用棧:
調用順序:lookUpImpOrForward
>initializeAndMaybeRelock
>realizeClassMaybeSwiftMaybeRelock
>realizeClassWithoutSwift
接下來就能夠分析類的實現realizeClassWithoutSwift
方法了
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGUncaughtExceptionHandle : NSObject
@property (nonatomic) BOOL dismissed;
+ (void)installUncaughtSignalExceptionHandler;
@end
NS_ASSUME_NONNULL_END
複製代碼
#import "LGUncaughtExceptionHandle.h"
#import <SCLAlertView.h>
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#include <stdatomic.h>
NSString * const LGUncaughtExceptionHandlerSignalExceptionName = @"LGUncaughtExceptionHandlerSignalExceptionName";
NSString * const LGUncaughtExceptionHandlerSignalExceptionReason = @"LGUncaughtExceptionHandlerSignalExceptionReason";
NSString * const LGUncaughtExceptionHandlerSignalKey = @"LGUncaughtExceptionHandlerSignalKey";
NSString * const LGUncaughtExceptionHandlerAddressesKey = @"LGUncaughtExceptionHandlerAddressesKey";
NSString * const LGUncaughtExceptionHandlerFileKey = @"LGUncaughtExceptionHandlerFileKey";
NSString * const LGUncaughtExceptionHandlerCallStackSymbolsKey = @"LGUncaughtExceptionHandlerCallStackSymbolsKey";
atomic_int LGUncaughtExceptionCount = 0;
const int32_t LGUncaughtExceptionMaximum = 8;
const NSInteger LGUncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger LGUncaughtExceptionHandlerReportAddressCount = 5;
@implementation LGUncaughtExceptionHandle
/// Exception
void LGExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
if (exceptionCount > LGUncaughtExceptionMaximum) {
return;
}
// 獲取堆棧信息 - model 編程思想
NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
[userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
[userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
[userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
[userInfo setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
[[[LGUncaughtExceptionHandle alloc] init]
performSelectorOnMainThread:@selector(lg_handleException:)
withObject:
[NSException
exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
+ (void)installUncaughtSignalExceptionHandler{
NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}
- (void)lg_handleException:(NSException *)exception{
// 保存上傳服務器
NSDictionary *userinfo = [exception userInfo];
[self saveCrash:exception file:[userinfo objectForKey:LGUncaughtExceptionHandlerFileKey]];
SCLAlertView *alert = [[SCLAlertView alloc] initWithNewWindowWidth:300.f];
[alert addButton:@"奔潰" actionBlock:^{
self.dismissed = YES;
}];
[alert showSuccess:exception.name subTitle:exception.reason closeButtonTitle:nil duration:0.0f];
}
/// 保存奔潰信息或者上傳
- (void)saveCrash:(NSException *)exception file:(NSString *)file{
NSArray *stackArray = [[exception userInfo] objectForKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];// 異常的堆棧信息
NSString *reason = [exception reason];// 出現異常的緣由
NSString *name = [exception name];// 異常名稱
// 或者直接用代碼,輸入這個崩潰信息,以便在console中進一步分析錯誤緣由
// NSLog(@"crash: %@", exception);
NSString * _libPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];
if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]){
[[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[dat timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%f", a];
NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"保存崩潰日誌 sucess:%d,%@",sucess,savePath);
}
/// 獲取函數堆棧信息
+ (NSArray *)lg_backtrace{
void* callstack[128];
int frames = backtrace(callstack, 128);//用於獲取當前線程的函數調用堆棧,返回實際獲取的指針個數
char **strs = backtrace_symbols(callstack, frames);//從backtrace函數獲取的信息轉化爲一個字符串數組
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = LGUncaughtExceptionHandlerSkipAddressCount;
i < LGUncaughtExceptionHandlerSkipAddressCount+LGUncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
@end
複製代碼