iOS底层探索类的加载(一)

1,050 阅读9分钟

1.调试代码准备

下载objc818可调试源码

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() 环境变量初始化

环境变量在调试的时候可以控制日志的输出

environ_init部分代码:

//只有满足 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(这里有讲解)。

image.png 配置环境变量,不使用NONPOINTER_ISAEdit Scheme > Arguments

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