1.调试代码准备
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
输出了环境变量,可以用于查询配置。也可用终端输出环境变量:
如何使用环境变量呢?
比如使用其中的OBJC_DISABLE_NONPOINTER_ISA
。ISA
分为两种,一只是存指针nonpointer
是0
,另一种是NONPOINTER_ISA
,nonpointer
是1
(这里有讲解)。
配置环境变量,不使用
NONPOINTER_ISA
。Edit Scheme
> Arguments
变成了存指针
又如配置打印load
方法的环境变量OBJC_PRINT_LOAD_METHODS
打印出实现
load
方法的出处
2.2.static_init() 在dyld之前调用objc库中的全局构造函数
在此处定义一个全局构造函数
lldb
调试
在此处就调用了全局构造函数,并没有在
dyld
阶段调用。
2.3.tls_init() 关于线程key的绑定
2.4.static_init() 运行C++
静态构造函数
在dyld
调用我们的静态构造函数之前,libc
会调用 _objc_init()
, 因此我们必须自己做
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() 缓存条件初始化
2.8. _dyld_objc_notify_register
_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
释放资源
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
_read_images
代码有三百多行,这里看整体流程:
_read_images 整体流程分析
_read_images
整体流程流程如下:
1.条件控制进行一次的加载
2.修复与编译阶段的@selector
的混乱
3.错误混乱类的处理
4.修复一些消息
5.当我们类里有协议的时候:readProtocol
6.修复没有被加载的协议
7.分类处理
8.类的加载处理
9.没有被处理的类 优化那些被侵犯的类
_read_images 关键流程分析
1.判断进行一次的加载,创建总表
gdb_objc_realized_classes
是个总表,不管是否实现
runtime_init
中的 表allocatedClasses
是已经被开辟过的表
2.修复预编译阶段的 @selector
的混乱问题
两个
sel
地址不同,_getObjc2SelectorRefs
是表里面读的macho
里,有相对地址和偏移地址,会变化,sel_registerNameNoLock
是dyld
读出来的,是链接整个程序的,要以它为准,所以要将表里的sel
替换为链接过后的sel
。
3.错误混乱的类处理
在内存里面有个原则,当类的内存发生了移动,会把原始那块内存删掉,如果没被删掉,就需要上面的处理。
readClass分析
在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
方法了
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