本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
背景
static_init()方法我们已经谈论的很多了。今天是最后一篇讲解static_init()文章,做一个补充。我们再回顾一下方法static_init():
static void static_init()
{
size_t count;
Initializer *inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
其中,_mh_dylib_header
之前的类型是mach_header_64
之前也有讲解过。不过这里不需要再去赋值,因为它是个extern对象(全局对象)。
接下来着重解决读者可能存在的几个问题:
- Initializer是什么
- initsi; 这么写的依据(含义)
分析
Initializer的声明如下:
using Initializer = void(*)(void);
这里 using的用法稍加介绍,主要有三个作用:
- 命名空间的使用
常见的如:
using namespace std;
- 在子类中引用基类的成员
class Person {
public:
Person() :value(55) {}
virtual ~Person() {}
void test1() { cout << "Person test1..." << endl; }
protected:
int value;
};
class Man : private Person {
public:
//using Main::test1;
//using Main::value;
void test2() { cout << "value is " << value << endl; }
基类中成员变量value是protected,在private继承之后,对于外界这个值为private,也就是说Person的对象无法使用这个value。
- 别名指定
即是本例中的用法,这个让我们想起了typedef。那么using 跟typedef有什么区别呢?哪个更好用些呢?这里不展开讲了,有兴趣的读者可以参考这篇文章
继续,我们还是看Initializer 定义:
using Initializer = void(*)(void);
里的void(*)(void);作用。要说明这个问题,我们可以一步步分析:
void f(void)
这个不用多介绍,声明一个返回类型为空,参数为空的函数 f;
那么
void (*p)(void)
就是声明一个指针p,该指针指向一个返回类型为空,参数为空的函数。
最后,继续:
(void (*)(void))
就是表明这个指针是个指向一个返回类型为空,参数为空的函数。这下大家应该了解了,
inits[i]();
的含义就是调用其中的函数。而且我们知道调用的函数都是类似全局变量的函数。这个之前的文章笔者有说过这里不细说了。笔者也提过这里的方法其实都是section为__mod_init_func
中的数据。而通过我们之前的分析也知道,__mod_init_func
中的函数是先于main函数执行的。其实在runtime库中,甚至咸鱼load方法,哈哈哈哈哈哈。
那这就给我们优化App启动提供了一个思路。幸好这个想法不是笔者特有,有位iOS的同僚已经做了相关工作,这里笔者就大概介绍一下他的思路。
原文在这里:
一种 hook C++ static initializers 的方法
其想法很简单,就是在load方法中hook__mod_init_func
的方法。部分源代码如下:
+ (void)load{
sInitInfos = [NSMutableArray new];
g_initializer = new std::vector();
g_cur_index = -1;
g_aslr = 0;
hookModInitFunc();
}
以及
static void hookModInitFunc(){
Dl_info info;
dladdr((const void *)hookModInitFunc, &info);
#ifndef __LP64__
// const struct mach_header *mhp = _dyld_get_image_header(0); // both works as below line
const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
unsigned long size = 0;
MemoryType *memory = (uint32_t*)getsectiondata(mhp, "__DATA", "__mod_init_func", & size);
#else /* defined(__LP64__) */
const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
unsigned long size = 0;
MemoryType *memory = (uint64_t*)getsectiondata(mhp, "__DATA", "__mod_init_func", & size);
#endif /* defined(__LP64__) */
for(int idx = 0; idx < size/sizeof(void*); ++idx){
MemoryType original_ptr = memory[idx];
g_initializer->push_back(original_ptr);
memory[idx] = (MemoryType)myInitFunc_Initializer;
}
NSLog(@"zero mod init func : size = %@",@(size));
[sInitInfos addObject:[NSString stringWithFormat:@"ASLR=%p",mhp]];
g_aslr = (MemoryType)mhp;
}
其实大部分代码我们都已经可以看懂了,还有疑问的读者可以自行查阅。通过这个实用工具,笔者可以轻松抓出App中启动项的优化空间:
如图所示,查看func函数,可以轻松知道其所在的文件是main.mm中。然后进入笔者的main.mm函数中发现声明了一个带构造函数的类,并引用了该类:
于是进行优化即可。
参考
C++ 中using 的使用
https://stackoverflow.com/questions/20357106/what-does-c-expression-voidvoid0-mean
广告
我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。