總結(jié)來說凯旋,大體分為如下步驟:
(1) 系統(tǒng)為程序啟動(dòng)做好準(zhǔn)備
- 當(dāng)kernel(內(nèi)核)做好程序的啟動(dòng)準(zhǔn)備工作之后,系統(tǒng)的執(zhí)行由內(nèi)核態(tài)轉(zhuǎn)換為用戶態(tài),由 dyld 首先開始工作
(2) 系統(tǒng)將控制權(quán)交給 Dyld呀潭,Dyld 會(huì)負(fù)責(zé)后續(xù)的工作
(3) Dyld 加載程序所需的動(dòng)態(tài)庫
(3) Dyld 對程序進(jìn)行 rebase 以及 bind 操作
(4) Objc SetUp
(5) 運(yùn)行初始化函數(shù)
(6) 執(zhí)行程序的 main 函數(shù)
dyld
dyld(the dynamic link editor), 動(dòng)態(tài)鏈接器,是專門用來加載動(dòng)態(tài)庫以及主程序的庫.當(dāng)kernel做好程序的啟動(dòng)準(zhǔn)備工作之后,系統(tǒng)的執(zhí)行由內(nèi)核態(tài)轉(zhuǎn)換為用戶態(tài),由 dyld 首先開始工作,iOS 中用到的所有系統(tǒng)framework都是動(dòng)態(tài)庫,比如最常用的UIKit.framework,Foundation.framework等都是通過dyld加載進(jìn)來的。
dyld 主要的工作有
- 初始化 App 運(yùn)行環(huán)境
- 鏈接依賴的動(dòng)態(tài)庫以及主程序
- rebase / binding
- 返回 main.m 的函數(shù)地址
接下來分析下dyld 的源碼
可以看到入口函數(shù)事在 dyid_start
方法里的dyldbootstrap::start
方法,接下來去源碼里看看. 在 dyld 源碼里找到dyldStartup.s
找到了__dyld_start
,這里只截取了arm架構(gòu)的部分.
通過注釋可以看到有調(diào)用dyldbootstrap::start
,那順著調(diào)用再往下看. 在dyldInitialization.cpp
中找到了start
- 首先通過
slideOfMainExecutable
拿到隨機(jī)地址的偏移量 - 調(diào)用
rebaseDyld
重定位 - mach_init() mach消息初始化
- __guard_setup() 棧溢出保護(hù) 接下來調(diào)用了
dyld::_main
,將返回值傳遞給__dyld_start
的調(diào)用main.m
函數(shù).
dyld::_main
dyld::_main是dyld中的關(guān)鍵方法,代碼也非常多,它的實(shí)現(xiàn)可以分為以下幾步: (關(guān)鍵部分有注釋)
- 設(shè)置運(yùn)行環(huán)境
- 加載共享緩存
- 加載主程序
- 加載動(dòng)態(tài)庫
- 鏈接主程序
- 鏈接動(dòng)態(tài)庫
- 初始化主程序
- 返回入口地址
設(shè)置運(yùn)行環(huán)境
// Grab the cdHash of the main executable from the environment
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
// 獲取主程序hash
if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
// Trace dyld's load
// 通知kernal內(nèi)核dyld文件已加載
notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
#if !TARGET_IPHONE_SIMULATOR
// Trace the main executable's load
// 通知kernal內(nèi)核mach-o文件已加載
notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif
CRSetCrashLogMessage("dyld: launch started");
//設(shè)置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
// 獲取主程序路徑
sExecPath = _simple_getenv(apple, "executable_path");
// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
if (!sExecPath) sExecPath = apple[0];
// mach-o 絕對路徑
if ( sExecPath[0] != '/' ) {
// have relative path, use cwd to make absolute
char cwdbuff[MAXPATHLEN];
if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
// maybe use static buffer to avoid calling malloc so early...
char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
strcpy(s, cwdbuff);
strcat(s, "/");
strcat(s, sExecPath);
sExecPath = s;
}
}
加載共享緩存
// load shared cache
// 判斷共享緩存庫是否被禁用
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
#if TARGET_IPHONE_SIMULATOR
// <HACK> until <rdar://30773711> is fixed
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
// </HACK>
#endif
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
// 映射共享緩存到共享區(qū)
mapSharedCache();
}
checkSharedRegionDisable是檢查共享緩存是否禁用,里面可以看到一行注釋,iOS 必須開啟共享緩存才能運(yùn)行.
static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED
// if main executable has segments that overlap the shared region,
// then disable using the shared region
if ( mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) ) {
gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
if ( gLinkContext.verboseMapping )
dyld::warn("disabling shared region because main executable overlaps\n");
}
#if __i386__
if ( !gLinkContext.allowEnvVarsPath ) {
// <rdar://problem/15280847> use private or no shared region for suid processes
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
}
#endif
#endif
// iOS cannot run without shared region
}
接下來調(diào)的mapSharedCache()
就是加載共享緩存的邏輯,就不深入了.
加載主程序
// add dyld itself to UUID list
addDyldImageToUUIDList();
CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
// 主程序?qū)嵗?// 這里調(diào)用比較深,后續(xù)再看
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
這一步將主程序 Mach-O 加載進(jìn)內(nèi)存,并實(shí)例化了一個(gè)ImageLoader.先看下instantiateFromLoadedImage的調(diào)用棧:
其中ImageLoader是一個(gè)抽象類,它的兩個(gè)子類ImageLoaderMachOCompressed、ImageLoaderMachOClassic負(fù)責(zé)把 Mach-O 實(shí)例化為 Image.但要用哪個(gè)子類來進(jìn)行實(shí)例化是通過sniffLoadCommands來判斷Mach-O 文件的 LINKEDIT 是classic或者compressed.
// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
// sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
加載動(dòng)態(tài)庫
// load any inserted libraries
// 插入動(dòng)態(tài)庫
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
遍歷DYLD_INSERT_LIBRARIES環(huán)境變量,然后調(diào)用loadInsertedDylib加載.
鏈接主程序
// link 主程序
// link調(diào)用比較深,后續(xù)來看
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;
}
調(diào)用 link鏈接主程序,內(nèi)核調(diào)用的是ImageLoader::link
函數(shù),主要是做了加載動(dòng)態(tài)庫、rebase节值、binding 等操作,代碼比較多,我就不貼了,在附件的源碼上有我寫的詳細(xì)注釋.
鏈接動(dòng)態(tài)庫
// 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
// 鏈接動(dòng)態(tài)庫
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();
}
// 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);
}
}
這一步將前面調(diào)用 addImage()函數(shù)保存在sAllImages 中的動(dòng)態(tài)庫列表循環(huán)調(diào)用 link進(jìn)行鏈接,然后調(diào)registerInterposing注冊符號替換. 注意這里的 i+1, 因?yàn)閟AllImages中第一項(xiàng)是主程序,所以取 i+1項(xiàng).
初始化主程序
CRSetCrashLogMessage("dyld: launch, running initializers");
// 初始化主程序
#if SUPPORT_OLD_CRT_INITIALIZATION
// Old way is to run initializers via a callback from crt1.o
if ( ! gRunInitializersOldWay )
initializeMainExecutable();
#else
// run all initializers
initializeMainExecutable();
#endif
// notify any montoring proccesses that this process is about to enter main()
if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
}
notifyMonitoringDyldMain();
這一步由initializeMainExecutable()完成蒋得。dyld會(huì)優(yōu)先初始化動(dòng)態(tài)庫,然后初始化主程序。該函數(shù)首先執(zhí)行runInitializers(),內(nèi)部再依次調(diào)用processInitializers()、recursiveInitialization(),在recursiveInitialization()函數(shù)里找到了 notifySingle();
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
復(fù)制代碼
再往下找到sNotifyObjCInit
,再去找它的賦值找到registerObjCNotifiers
,從函數(shù)注釋來看是用objc runtime
來調(diào)的,這塊之后再看.在查閱一些資料之后得知,這里的sNotifyObjCInit
就是調(diào)用 objc 中的 load_images,它調(diào)用所有的 load 方法,在調(diào)用完 load 方法以后調(diào)用了
bool hasInitializers = this->doInitialization(context);
復(fù)制代碼
doInitialization
又調(diào)用了doModInitFunctions
, 也就是constuctor
方法,關(guān)于這個(gè)方法可以參看鏈接.
返回入口地址
// find entry point for main executable
// 從 mach-o 中讀取程序入口, 主程序則讀取LC_UNIXTHREAD, 就是 main.m
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
// main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
else
halt("libdyld.dylib support not present for LC_MAIN");
}
else {
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
*startGlue = 0;
這里調(diào)用主程序的getEntryFromLC_MAIN
,就是從``Load Command中讀取
LC_MAIN入口,如果沒有,就讀取
LC_UNIXTHREAD,然后跳到入口處執(zhí)行,就回到了我們熟悉的
main.m`.
App啟動(dòng)邏輯
最后 dyld 會(huì)調(diào)用 main() 函數(shù)狸棍。main() 會(huì)調(diào)用 UIApplicationMain(),程序啟動(dòng)味悄。
main.m文件草戈,此處就是應(yīng)用的入口了。程序啟動(dòng)時(shí)傍菇,先執(zhí)行main函數(shù)猾瘸,main函數(shù)是ios程序的入口點(diǎn),內(nèi)部會(huì)調(diào)用UIApplicationMain函數(shù)丢习,UIApplicationMain里會(huì)創(chuàng)建一個(gè)UIApplication對象 牵触,然后創(chuàng)建UIApplication的delegate對象 —–(您的)AppDelegate ,開啟一個(gè)消息循環(huán)(main runloop)咐低,每當(dāng)監(jiān)聽到對應(yīng)的系統(tǒng)事件時(shí)揽思,就會(huì)通知AppDelegate。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
說明
帶注釋 dyld源碼地址: Github