iOS底層探索類的加載(一)

1.調試代碼準備

下載objc818可調試源碼c++

2.objc_init分析

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
}
複製代碼

2.1.environ_init() 環境變量初始化

環境變量在調試的時候能夠控制日誌的輸出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 複製代碼

輸出了環境變量,能夠用於查詢配置。也可用終端輸出環境變量:緩存

image.png

如何使用環境變量呢?服務器

好比使用其中的OBJC_DISABLE_NONPOINTER_ISAISA分爲兩種,一隻是存指針nonpointer0,另外一種是NONPOINTER_ISAnonpointer1(這裏有講解)。markdown

image.png 配置環境變量,不使用NONPOINTER_ISAEdit Scheme > Argumentsapp

image.png image.png 變成了存指針函數

又如配置打印load方法的環境變量OBJC_PRINT_LOAD_METHODS image.png 打印出實現load方法的出處 image.png

2.2.static_init() 在dyld以前調用objc庫中的全局構造函數

在此處定義一個全局構造函數

image.png

lldb調試

image.png 在此處就調用了全局構造函數,並無在dyld階段調用。

2.3.tls_init() 關於線程key的綁定

image.png

2.4.static_init() 運行C++靜態構造函數

dyld調用咱們的靜態構造函數以前,libc 會調用 _objc_init(), 所以咱們必須本身作

image.png

2.5.runtime_init() 初始化兩張表

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    //已經被開闢過表
    objc::allocatedClasses.init();
}
複製代碼

2.6.exception_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完整代碼會在文章末尾貼出。

2.7.cache_init() 緩存條件初始化

image.png

2.8. _dyld_objc_notify_register

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
複製代碼

_objc_init_dyld_objc_notify_register有三個參數

  • map_images:管理文件中和動態庫中全部的符號(classProtocolselectorcategory
  • load_images:加載執行load方法
  • unmap_imagedyld釋放資源

2.8.1.map_images

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

2.8.2 _read_images

image.png

_read_images代碼有三百多行,這裏看總體流程:

_read_images 總體流程分析

image.png image.png image.png _read_images 總體流程流程以下:

1.條件控制進行一次的加載

2.修復與編譯階段的@selector的混亂

3.錯誤混亂類的處理

4.修復一些消息

5.當咱們類裏有協議的時候:readProtocol

6.修復沒有被加載的協議

7.分類處理

8.類的加載處理

9.沒有被處理的類 優化那些被侵犯的類

_read_images 關鍵流程分析

1.判斷進行一次的加載,建立總表

image.png gdb_objc_realized_classes是個總表,無論是否實現 runtime_init中的 表allocatedClasses是已經被開闢過的表

2.修復預編譯階段的 @selector 的混亂問題

image.png 兩個sel地址不一樣,_getObjc2SelectorRefs是表裏面讀的macho裏,有相對地址和偏移地址,會變化,sel_registerNameNoLockdyld讀出來的,是連接整個程序的,要以它爲準,因此要將表裏的sel替換爲連接事後的sel

3.錯誤混亂的類處理

image.png 在內存裏面有個原則,當類的內存發生了移動,會把原始那塊內存刪掉,若是沒被刪掉,就須要上面的處理。

image.png

readClass分析

readClass裏面加一些判斷代碼,目的是隻研究LGPerson類的加載

image.png

接着往下看

image.png

image.pngclsname加到總表中。以key-value形式加入,keynamevaluecls

經過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);
 }
複製代碼

image.png

image.png LGPeron類中添加load方法後 運行程序,斷點首先斷在了一張圖。重點是實現類的方法realizeClassWithoutSwift。若是LGPeron類中沒有添加load方法,也會調用realizeClassWithoutSwift。 添加試探代碼,並斷點調試,打印調用棧:

image.png 調用順序:lookUpImpOrForward >initializeAndMaybeRelock>realizeClassMaybeSwiftMaybeRelock>realizeClassWithoutSwift

接下來就能夠分析類的實現realizeClassWithoutSwift方法了

3.LGUncaughtExceptionHandle文件源碼

#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
複製代碼
相關文章
相關標籤/搜索