今天我們研究的是應(yīng)用程序的加載過程驯遇,先做一下準(zhǔn)備工作。
新建一個(gè)iphone工程
侧甫,添加下面代碼:
ViewController.m
+ (void)load{
NSLog(@"%s",__func__);
}
main.m
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
NSLog(@"333333");
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
__attribute__((constructor)) void lyFunc(){
printf("來了 : %s \n",__func__);
}
運(yùn)行查看這三個(gè)打印的順序:
+[ViewController load]
來了 : lyFunc
333333
程序加載過程這些方法的調(diào)用順序是:
load ——> C++ ——> main
main
函數(shù)作為程序的入口,為什確實(shí)最后執(zhí)行的,我們需要研究的是在main
函數(shù)之前诺核,程序到底做了什么抄肖?
程序編譯過程
預(yù)編譯
:預(yù)編譯又稱為預(yù)處理,主要做些代碼文本的替換工作窖杀,處理#開頭的指令漓摩。比如拷貝#include、#import包含的文件代碼入客,#define宏定義的替換管毙,條件編譯等,就是為編譯做預(yù)備工作的階段桌硫,主要處理#開始的預(yù)編譯指令锅风。生成.i文件
編譯
:將高級(jí)語言轉(zhuǎn)換為機(jī)器能識(shí)別的匯編語言。生成.s文件
匯編
:將匯編文件轉(zhuǎn)換成機(jī)器碼文件鞍泉。生成.o文件
鏈接
:對(duì).o文件中引用的其他的庫進(jìn)行引入皱埠,生成可執(zhí)行文件。
動(dòng)態(tài)庫 和 靜態(tài)庫
-
靜態(tài)庫
:在鏈接階段咖驮,將可匯編生成目標(biāo)程序與它所引用的庫一起鏈接打包到可執(zhí)行文件中边器。此時(shí)靜態(tài)庫就不會(huì)改變了,靜態(tài)庫是直接拷貝托修,復(fù)制到目標(biāo)程序忘巧。
-
優(yōu)點(diǎn)
:編譯完成,庫文件就沒有作用了睦刃,運(yùn)行時(shí)可以直接使用 -
缺點(diǎn)
:不同的靜態(tài)庫中引用了相同的文件砚嘴,如上圖中的B、D涩拙,他就會(huì)拷貝兩份相同的庫文件际长,導(dǎo)致目標(biāo)程序體積增大
,對(duì)性能兴泥、內(nèi)存會(huì)有一定的影響工育。
-
-
動(dòng)態(tài)庫
:編譯時(shí)相同的庫并不會(huì)拷貝到目標(biāo)程序,而是在目標(biāo)程序載入的時(shí)候搓彻,把那些相同的庫用一份共享實(shí)例加載進(jìn)來如绸。-
優(yōu)點(diǎn)
:-
減少打包的體積
:共享內(nèi)存,節(jié)約資源 -
更新動(dòng)態(tài)庫可以直接更新程序
:由于運(yùn)行時(shí)才載入的特性旭贬,因此可以隨時(shí)對(duì)下層庫進(jìn)行更換怔接,而我們的代碼卻不用改變
-
-
缺點(diǎn)
:動(dòng)態(tài)載入會(huì)帶來一部分性能損失,如果當(dāng)前環(huán)境缺少動(dòng)態(tài)庫稀轨,或者版本不正確扼脐,會(huì)導(dǎo)致程序無法運(yùn)行
-
dyld加載流程
dyld是蘋果的動(dòng)態(tài)連接器
,加載到程序中的動(dòng)態(tài)庫靶端,主要靠dyld
來鏈接管理谎势。
下面我們通過源碼分析dyld的加載流程凛膏,首先下載一份dyld源碼,這篇文章使用的是750.6
脏榆。
拿到了源碼猖毫,那么我們從哪里開始分析呢?借助一下上面的工程须喂,在load
方法處加上斷點(diǎn)吁断,打印堆棧信息:
bt
我們可以看到堆棧的第一個(gè)信息是
_dyld_start
,也可以直接在左邊窗口查看坞生。
- 打開dyld源碼仔役,搜索
_dyld_start
找到__arm64__
環(huán)境下的源碼
往下查找發(fā)現(xiàn)它調(diào)起了dyldbootstrap::start
方法
- 搜索
dyldbootstrap
找到start
方法
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
// Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
rebaseDyld(dyldsMachHeader);
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
其核心就是dyld::_main
,返回調(diào)用了dyld
的main
函數(shù)是己。從左邊的堆棧又兵,也可以清晰的看到。其中macho_header
就是Mach-o
的頭部卒废。
- 點(diǎn)擊跳轉(zhuǎn)到
dyld::_main
函數(shù)
我們發(fā)現(xiàn)這個(gè)main
函數(shù)有600多行代碼沛厨,在這里就不詳細(xì)解釋,感興趣的同學(xué)可以自行研究摔认。_main
函數(shù)主要做了以下事情:
1.環(huán)境變量配置
2.共享緩存:(UIKit逆皮、CoreFoundation等)
3.主程序的初始化
4.加入動(dòng)態(tài)庫
5.link主程序
6.link動(dòng)態(tài)庫
7.綁定弱引用
8.initialize初始化
9.main()
-環(huán)境變量配置
-
共享緩存
-
主程序初始化
-
加載動(dòng)態(tài)庫
-
link主程序
-
link動(dòng)態(tài)庫
-
弱引用綁定主程序
-
run initialize初始化方法
image.png -
進(jìn)入main()函數(shù)
image.png
initialize
我們主要分析第八步,看看run all initializers里面做了什么参袱?
-
點(diǎn)擊進(jìn)入
initializeMainExecutable
源碼
-
點(diǎn)擊
runInitializers
image.png
主要是其中的processInitializers
方法 -
點(diǎn)擊進(jìn)入
processInitializers
-
進(jìn)入
recursiveInitialization
源碼
重點(diǎn)查看notifySingle
和doInitialization
方法
notifySingle
-
進(jìn)入
notifySingle
源碼
根據(jù)傳入?yún)?shù)dyld_image_state_dependents_initialized
和dyld_image_state_initialized
找到下段代碼
-
sNotifyObjCInit
直接搜索sNotifyObjCInit
沒有找到相應(yīng)的源碼电谣,只找到一段賦值代碼。我們繼續(xù)跟registerObjCNotifiers
流程
-
搜索
_dyld_objc_notify_register
只找到一處調(diào)用的地方抹蚀,接著我們搜索查看_dyld_objc_notify_register
剿牺,并沒有發(fā)現(xiàn)調(diào)用的地方。_dyld_objc_notify_register
實(shí)際上是在objc
源碼中調(diào)用的.
-
在
objc-781
源碼中搜索_dyld_objc_notify_register
在_objc_init
中找到况鸣,從這可知賦給sNotifyObjCInit
的值其實(shí)就是load_images
-
接著查看
load_images
源碼
主要放大是call_load_methods
牢贸,調(diào)用所有+load
方法 點(diǎn)擊
call_load_methods
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
//遍歷所有的類,調(diào)用 + load 方法
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
//加載所有的category
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
看到這里想必大家都已經(jīng)明白了镐捧,sNotifyObjCInit
實(shí)際上調(diào)用的是libobjc
里的load_images
方法,作用是調(diào)用所有類的+load
方法和加載category
臭增。
doInitialization
- 點(diǎn)擊進(jìn)入
doInitialization
這里的doImageInit
和doModInitFunctions
這兩個(gè)方法是關(guān)鍵懂酱。我們先看doImageInit
。
-
點(diǎn)擊進(jìn)入
doImageInit
這里主要進(jìn)行動(dòng)態(tài)庫的初始化工作誊抛,值得注意的是libSystem.dylib
必須第一個(gè)初始化列牺。 -
點(diǎn)擊進(jìn)入
doModInitFunctions
這里主要是加載C++
方法,我們可以在上面的demo
中打一個(gè)斷點(diǎn)進(jìn)行驗(yàn)證拗窃。
C++方法調(diào)用之前瞎领,確實(shí)調(diào)用了doModInitFunctions
至此我們整個(gè)的runInitializers
執(zhí)行完畢了泌辫。大致流程是initializeMainExecutable ——> runInitializers ——> processInitializers ——> processInitializers ——> recursiveInitialization ——> notifySingle ——> load_images ——> call_load_methods ——> call_class_loads ——> call_category_loads ——> doInitialization ——> doImageInit ——> doModInitFunctions ——> main()
不知道大家有沒有注意到notifySingle
調(diào)用load_images
時(shí),必須先由_objc_init ——> _dyld_objc_notify_register ——> registerObjCNotifiers
中才給了notifySingle
賦值load_images
這個(gè)賦值必須要在notifySingle
調(diào)用之前九默,那么_objc_init
在什么時(shí)候調(diào)用了呢震放?這個(gè)我們并沒有找到。
探索_objc_init調(diào)用時(shí)機(jī)
我們?cè)趏bjc-781源碼中_objc_init
方法打上斷點(diǎn)驼修。查看堆棧情況
我們發(fā)現(xiàn)在
_objc_init
之前殿遂,調(diào)用了libSystem_initializer
和 libdispatch_initializer
方法。接下來我們要分別查看
libSystem
和libdispatch
的源碼乙各。開源庫地址我下載的是Libsystem-1281.100.1
和libdispatch-1173.100.2
libSystem
- 打開
libSystem
源碼墨礁,搜索libSystem_initializer
static void
libSystem_initializer(int argc,
const char* argv[],
const char* envp[],
const char* apple[],
const struct ProgramVars* vars)
{
_libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_START);
__libkernel_init(&libkernel_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(KERNEL);
__libplatform_init(NULL, envp, apple, vars);
_libSystem_ktrace_init_func(PLATFORM);
__pthread_init(&libpthread_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(PTHREAD);
_libc_initializer(&libc_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(LIBC);
// TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
__malloc_init(apple);
_libSystem_ktrace_init_func(MALLOC);
#if TARGET_OS_OSX
/* <rdar://problem/9664631> */
__keymgr_initializer();
_libSystem_ktrace_init_func(KEYMGR);
#endif
// No ASan interceptors are invoked before this point. ASan is normally initialized via the malloc interceptor:
// _dyld_initializer() -> tlv_load_notification -> wrap_malloc -> ASanInitInternal
_dyld_initializer();
_libSystem_ktrace_init_func(DYLD);
libdispatch_init();
_libSystem_ktrace_init_func(LIBDISPATCH);
....已省略部分代碼.....
}
發(fā)現(xiàn)在其中調(diào)用了__malloc_init、 _dyld_initializer耳峦、 libdispatch_init
恩静。
libdispatch
- 打開
libdispatch
源碼,搜索libdispatch_init
void
libdispatch_init(void)
{
...已省略部分代碼....
dispatch_assert(sizeof(struct dispatch_apply_s) <=
DISPATCH_CONTINUATION_SIZE);
if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
_dispatch_mode |= DISPATCH_MODE_STRICT;
}
#if DISPATCH_USE_THREAD_LOCAL_STORAGE
_dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
_dispatch_thread_key_create(&dispatch_priority_key, NULL);
_dispatch_thread_key_create(&dispatch_r2k_key, NULL);
_dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
_dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
_dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
_dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
_dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
NULL);
_dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
_dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
_dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
_dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
_dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
_dispatch_thread_key_create(&dispatch_deferred_items_key,
_dispatch_deferred_items_cleanup);
#endif
#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
_dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif
_dispatch_queue_set_current(&_dispatch_main_q);
_dispatch_queue_set_bound_thread(&_dispatch_main_q);
#if DISPATCH_USE_PTHREAD_ATFORK
(void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
dispatch_atfork_parent, dispatch_atfork_child));
#endif
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
最后我們看到libdispatch
調(diào)用了_os_object_init
蹲坷。
- 繼續(xù)搜索
_os_object_init
果然_objc_init
就在其中驶乾,所有的猜想都得到了驗(yàn)證。