iOS 底层第14
天的学习。在第12,13
天的学习中, dyld
已经把 images
给映射过来了。但在何时加载到内存里的我们还不清楚,接下来继续进行探究。
_objc_init 探究
void _objc_init(void)
{
// ....
// fixme defer initialization until an objc-using image is found?
// 环境变量的初始化,可以打印环境变量
environ_init();
//
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
environ_init
- 环境变量的初始化
void environ_init(void)
{
// ...
// 循环输出环境变量
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);
}
// ...
}
- 把程序运行起来打印输出👇
// ...
// 是否打印 load 方法
objc[6501]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[6501]: OBJC_PRINT_LOAD_METHODS is set
objc[6501]: OBJC_DISABLE_PREOPTIMIZATION is set
objc[6501]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[6501]: OBJC_DISABLE_TAGGED_POINTERS is set
objc[6501]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[6501]: OBJC_DISABLE_TAG_OBFUSCATION is set
// 是否启用 NONPOINTER_ISA
objc[6501]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[6501]: OBJC_DISABLE_NONPOINTER_ISA is set
// ...
- 这里的环境变量有很多,这些环境变量到底有什么用呢?就拿
OBJC_PRINT_LOAD_METHODS
和OBJC_DISABLE_NONPOINTER_ISA
来举个🌰 - 添加环境变量
- 未打开
OBJC_DISABLE_NONPOINTER_ISA
,p/t
输出isa
,发现末尾是1
(lldb) x/4gx p
0x1011b9960: 0x011d800100008141 0x0000000000000000
0x1011b9970: 0x75736956534e5b2d 0x6369506261546c61
(lldb) p/t 0x011d800100008141
(long) $4 = 0b0000000100011101100000000000000100000000000000001000000101000001
- 打开
OBJC_DISABLE_NONPOINTER_ISA
,p/t
输出isa
,发现末尾是0
,说明已经把NONPOINTER_ISA
给关闭了现在是纯isa
。
(lldb) x/4gx p
0x101505210: 0x0000000100008140 0x0000000000000000
0x101505220: 0x67616d49534e5b2d 0x6569467478655465
(lldb) p/t 0x011d800100008140
(long) $1 = 0b0000000100011101100000000000000100000000000000001000000101000000
- 我们下来把
OBJC_PRINT_LOAD_METHODS
打开 ,就能打印输出在程序调用了load
方法,这样就能更方便的优化代码和解决问题。
tls_init
- 进入
static_init
查看源码
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
-
tls_init
线程key
的绑定,每条线程数据的析构函数
static_init
- 进入
static_init
查看源码
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[I]);
init();
}
}
- 在
libobjc
里c++
函数的调用,在dyld:: doModFunction
之前就会调用。
runtime_init
- 进入
runtime_init
查看源码
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
-
runtime_init
runtime
运行时的初始化。 里面有unattachedCategories
,allocatedClasses
两张表
exception_init
- 进入
exception_init
查看源码
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
-
exception_init
对系统异常的出来,并可进行自定义handle
- 进入
_objc_terminate
static void (*old_terminate)(void) = nil;
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)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
- 查看
_objc_terminate
源码,发现当catch
时会执行uncaught_handler
,全局搜索uncaught_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
- 发现在
objc_setUncaughtExceptionHandler
有个赋值,这个赋值有何用的呢?我们弄一个Demo
来测试一下
- (void)viewDidLoad {
[super viewDidLoad];
self.dataArray = @[@"测试数据0",
@"测试数据1",
@"测试数据2",
@"测试数据3",
@"测试数据3"];
}
- (IBAction)exceptionAction:(id)sender {
NSLog(@"%@",self.dataArray[5]);
}
- 上面的代码只要点了
exception
必定会报错,数据越界
- 堆栈信息如下
- 在
objc_setUncaughtExceptionHandler
进行拦截
NSSetUncaughtExceptionHandler(&XKExceptionHandlers);
NSSetUncaughtExceptionHandler = objc_setUncaughtExceptionHandler
void XKExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
·
}
- 在程序启动时调用
NSSetUncaughtExceptionHandler
,在XKExceptionHandlers
进行拦截
-
po
打印exception
就能得到错误信息,接下来你就可以对exception
进行收集了
map_image & load_image
- 继续往下分析
void _objc_init(void)
{
// ....
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
- 这里的
&map_images
= 镜像文件映射 ,管理文件中和动态库中所有的符号,load_images
= 镜像文件加载
那为何
&map_images
比load_images
多了个&
,这是为何呢?
- 因为
&map_images
是指针传递,那为何要指针传递呢? - 因为
&map_images
内部函数 和 调用map_images
的函数里的值
要进行同步的变化,而load_images
只是单纯的镜像的加载-
&map_images
内部函数 👇
-
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
的函数 =dyld: sNotifyObjCMapped
👇
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
if ( objcImageCount != 0 ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
uint64_t t0 = mach_absolute_time();
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
uint64_t t1 = mach_absolute_time();
ImageLoader::fgTotalObjCSetupTime += (t1-t0);
}
}
那为何
map_images
要进行同步变化呢?
- 因为
map_images
这个函数非常重要,在map_images
内部流程—镜像文件的映射是非常耗时的,当中间发生任何变化都会导致数据的不同步,所以当map_images
的指针地址发生变化时,在sNotifyObjCMapped
同时也会发生变化,来确保数据的统一。
read_images
- 进入
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
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[]) {
// ... 省略部分代码
// 我们要分析的核心
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
- 进入
_read_images
,我们先按住cmd + opt + 箭头<
,全局分析一下_read_images
到底做了哪些处理。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// 省略一些参数 ...
// 1、条件控制进行第一次加载
if (!doneOnce)
{ ... }
ts.log("IMAGE TIMES: first time tasks");
// Fix up @selector references
// 2、修复预编译阶段 @selector 的混乱问题
static size_t UnfixedSelectors
{ ... }
ts.log("IMAGE TIMES: fix up selector references");
// 3. 从macho读取地址 并给 class 的赋值
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover classes");
// 4.修复映射一些没有被镜像文件加载进来的类
if (!noClassesRemapped()) {
for (EACH_HEADER) { ... }
}
ts.log("IMAGE TIMES: remap classes");
// 5.修复一些消息
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
// 6. readProtocol
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover protocols");
// 7.修复没有被加载的协议
// Fix up @protocol references
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up @protocol references");
// 8.分类的处理
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
ts.log("IMAGE TIMES: discover categories");
// 9. 类的加载和处理
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
...
realizeClassWithoutSwift(cls, nil);
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// 10.优化哪些被侵犯的类
{ ... }
ts.log("IMAGE TIMES: realize future classes");
}
- 已知
read_images
里做了那么多的事,但最重要目标是要去寻找加载镜像文件去读取 class
- 继续探索找到有关
class
的代码块
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
// { ... }
classref_t const *classlist = _getObjc2ClassList(hi, &count);
// { ... }
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
{ ... }
}
}
}
-
找到了
readClass
但不知道它到底做了什么,我们跑程序运行起来看一下
-
继续
step
发现了
cls
已经有了名字,我们可以肯定readClass
内部对类
进行了处理,那到底是怎么处理的?进入
readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
if (missingWeakSuperclass(cls)) { ... }
Class replacing = nil;
if (mangledName != nullptr) {
// future class.
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
}
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
- 查看源代码,静态分析在
readClass
有做ro
,rw
的处理,接下来进行动态调试确认分析结果是否正确 - 添加
XKStudent
只对普通class
进行分析,忽略系统class
- 动态调试分析👇
- 没有进入
if (Class newCls = popFutureNamedClass(mangledName)) { ro,rw 处理 }
而是跳过了直接来到了👇
- 由动态调试可知在
readClass
里并没有对ro
,rw
进行处理,而是来了addNamedClass
,addClassTableEntry
- 进入
addNamedClass
, 把name
,cls地址
插入到hashmap
里进行关联
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
// ...
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
// ...
}
- 进入
addClassTableEntry
, 把类的元类也加入到table
里
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
总结
- 今天我们分析了
objc_init
初始化做了哪些事情- environ_init();
- tls_init();
- static_init();
- runtime_init();
- exception_init();
- cache_t::init();
- _dyld_objc_notify_register
- 从
_dyld_objc_notify_register
开始分析read_images
- 得知
read_images
的目标就是为了读取类
- 而在
readClass
我们只是得知class地址与name进行了关联
和meta class
加入到table
里,class
里的ro
,rw
在哪里进行赋值的? 期待下一次的分析