調(diào)用在main()之前
一般開發(fā)場景中,我們都是把main()函數(shù)作為程序的入口,但是這里探究一下man()函數(shù)開始之前發(fā)生了什么。
程序運行依賴很多的庫和文件,有動態(tài)庫(.so .framwork)還有靜態(tài)庫(.a .lib)以及很多的.h .m文件 他們?nèi)绾渭虞d 到程序中的呢

靜態(tài)庫:鏈接時會被完整的復(fù)制到可執(zhí)行文件中,所以如果兩個程序都用了某個靜態(tài)庫,那么每個二進(jìn)制可執(zhí)行文件里面其實都含有這份靜態(tài)庫的代碼。
動態(tài)庫: 鏈接時不復(fù)制,在程序啟動后用動態(tài)加載,然后再決議符號,所以理論上動態(tài)庫只用存在一份,好多個程序都可以動態(tài)鏈接到這個動態(tài)庫上面,達(dá)到了節(jié)省內(nèi)存,還有另外一個好處,由于動態(tài)庫并不綁定到可執(zhí)行程序上,所以我們想升級這個動態(tài)庫就很容易,windows和linux上面一般插件和模塊機制都是這樣實現(xiàn)的。
動態(tài)鏈接庫包括:
- iOS 中用到的所有系統(tǒng) framework
- 加載OC runtime方法的libobjc,
- 系統(tǒng)級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)
這些放在內(nèi)存中的共享庫文件會在app啟動后交給dyld動態(tài)連接器來進(jìn)行鏈接管理。

動靜態(tài)鏈接庫以及對于App本身的可執(zhí)行文件而言,都稱為image。
dyld則是以image為單位將這些可執(zhí)行文件加載到app中。
開始在main()之前
首先做一份斷點信息,iOS中-load()方法會在main()函數(shù)開始之前就調(diào)用,在這里設(shè)置斷點可以很好的追蹤主函數(shù)開始之前的調(diào)用信息。同樣的,使用__attribute__ ((constructor))修飾的C++屬性函數(shù)也會在main()之前被加載調(diào)用.


這里通過函數(shù)名稱大致也能推測出一些比較重要的函數(shù)名稱
_dyld_startdyldbootstrap::startdyld::maindyld::initializeMainExecutableimageLoader::****dyld:: notifySingle-
load_images等
dyld的加載流程
很明顯_dyld_start是作為整個流程的起始位置。
打開一份新鮮的objc756源碼,開始查找_dyld_start。
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
//...
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
//...
_dyld_start的源碼是用匯編寫的,但是只需要看注釋就能明白多個版本的下的_dyld_start都會來到dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)這個函數(shù)。
來看看dyldbootstrap::start內(nèi)部實現(xiàn):
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue){
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
rebaseDyld(dyldsMachHeader);
const char** envp = &argv[argc+1];
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
__guard_setup(apple);
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
出了一些參數(shù)的處理 最重要的一步就是_main()這個函數(shù)了。
_main()函數(shù)
_main()函數(shù)代碼量很大,分了很多功能,主要完成了上下文的建立,主程序初始化成ImageLoader對象,加載共享的系統(tǒng)動態(tài)庫,加載依賴的動態(tài)庫,鏈接動態(tài)庫,初始化主程序,返回主程序main()函數(shù)地址。
加載共享緩存
mapSharedCache()負(fù)責(zé)將系統(tǒng)中的共享動態(tài)庫加載進(jìn)內(nèi)存空間,比如UIKit就是動態(tài)共享庫,這也是不同的App之間能夠?qū)崿F(xiàn)動態(tài)庫共享的機制。不同App間訪問的共享庫最終都映射到了同一塊物理內(nèi)存,從而實現(xiàn)了共享動態(tài)庫。
內(nèi)部會調(diào)用loadDyldCache()
mapSharedCache()的基本邏輯就是:
- 先判斷共享動態(tài)庫是否已經(jīng)映射到內(nèi)存中了。
- 如果已經(jīng)存在,則直接返回。
- 否則打開緩存文件,并將共享動態(tài)庫映射到內(nèi)存中。
load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache();
#else
mapSharedCache();
#endif
}
生成ImageLoader對象
動靜態(tài)庫以及程序自己的.o文件都是以image的形式加載的。
ImageLoader本身是一個抽象類,用于幫助加載特定格式的可執(zhí)行文件。例如ImageLoaderMachO繼承自ImageLoader.
instantiateFromLoadedImage()會實例化一個ImageLoader對象,然后調(diào)用instantiateMainExecutable()加載文件生成image并進(jìn)行鏈接。

APP啟動過程中,相關(guān)的庫和主程序都被加載成ImageLoader對象
最后返回一個ImageLoaderMachO對象用于訪問所有的images對象
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path){
// try mach-o loader
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
}
throw "main executable not a known format";
}
加載所有插入的庫 loadInsertedDylib
遍歷sEnv.DYLD_INSERT_LIBRARIES這個環(huán)境變量中的庫,調(diào)用loadInsertedDylib來加載庫。
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
鏈接主程序
這一步最終調(diào)用的還是ImageLoader::link,內(nèi)部會調(diào)用recursiveLoadLibraries遞歸加載動態(tài)庫。
注意,先鏈接主程序,然后鏈接所有加載的庫文件。
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
if ( gLinkContext.allowInterposing ) {
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing(gLinkContext);
}
}
}
執(zhí)行初始化方法 initializeMainExecutable
initializeMainExecutable()里面先對動態(tài)庫進(jìn)行runInitializers(),然后才對主程序進(jìn)行runInitializers()。
runInitializers()內(nèi)部調(diào)用了Imageloader::recursiveInitialization
Imageloader::recursiveInitialization里面調(diào)用了如下內(nèi)容:
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps){
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
}
}
我們需要注意這幾行代碼:
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
context.notifySingle(dyld_image_state_initialized, this, NULL);
doInitialization()
doInitialization(context);這里開始做images初始化工作。
注意,在這個函數(shù)里面有一個判斷:
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// libSystem initializer must run first
dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
那就是在所有的images的初始化中,libSystem必須放在第一位。
既然libSystem這么重要,那么就看看libSystem的初始化方法:
static void
libSystem_initializer(int argc,const char* argv[],const char* envp[],const char* apple[],const struct ProgramVars* vars){
//內(nèi)核初始化
__libkernel_init(&libkernel_funcs, envp, apple, vars);
//平臺初始化
__libplatform_init(NULL, envp, apple, vars);
//線程相關(guān)初始化
__pthread_init(&libpthread_funcs, envp, apple, vars);
_libc_initializer(&libc_funcs, envp, apple, vars);
__malloc_init(apple);
//dyld的初始化,上面dyld_start之后并不代表就開始初始化。
_dyld_initializer();
//這里是重點
libdispatch_init();
_libxpc_initializer();
}
_objc_init()
libdispatch_init()這個庫的初始化應(yīng)該最熟悉了,內(nèi)部會對GCD底層進(jìn)行一些初始化工作,以及會調(diào)用一個對我們當(dāng)前探索流程很重要的一個初始化函數(shù)_os_object_init(),而_os_object_init內(nèi)部則會直接調(diào)用_objc_init()這個函數(shù),來完成_dyld_objc_notify_register(&map_images, load_images, unmap_image);函數(shù)注冊。
void
libdispatch_init(void)
{
//...
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
從_os_object_init()內(nèi)部調(diào)用來到這里
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
notifySingle()
doInitialization()完成前后,都會有一個通知信號notifySingle()
。
找到它的源碼位置
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
只發(fā)現(xiàn)了(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());這個函數(shù)的調(diào)用。
先看看它的定義:
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
全局搜索它的賦值,只有一處地方能找到:
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
//...
}
那么說明一定是在dyld的某個地方,調(diào)用了registerObjCNotifiers,然后給了sNotifyObjCInit賦值。
同樣,全局找到了唯一的調(diào)用位置:
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
那么看到這里就能明白,這里之所以能調(diào)用*sNotifyObjCInit (),就是因為在doInitialization()中完成了libSystem_initializer()的調(diào)用,而libSystem初始化的時候內(nèi)部調(diào)用了objc_init()函數(shù),完成了_dyld_objc_notify_register(&map_images, load_images, unmap_image);函數(shù)的注冊。
因此,在doInitialization()執(zhí)行完畢之后,發(fā)送一條完成的通知notifySingle(),并執(zhí)行回調(diào)函數(shù)。
dyld總體流程總結(jié):
_dyld_start開始
- 環(huán)境變量的配置
- 共享緩存
- 主程序初始化
- 加入動態(tài)庫
- link主程序
- link動態(tài)庫
- main()