前言
涉及內(nèi)容較多,很多細(xì)節(jié)需要進一步探索框喳,希望同學(xué)們多多批評指正佣渴。
XNU加載app
參考資料:
iOS 系統(tǒng)內(nèi)核 XNU:App 如何加載透硝?
XNU源碼
- fork 新進程
- 為Mach-O分配內(nèi)存
- 解析Mach-O
- 讀取Mach-O 頭文件
- 遍歷load command信息,將Mach-O映射到內(nèi)存宪肖,設(shè)置執(zhí)行app的入口點表制。
- 啟動dyld
總體來說,XNU加載就是為Mach-O創(chuàng)建一個新進程控乾,建立虛擬內(nèi)存空間么介,解析Mach-O文件,最后映射到存空間蜕衡,設(shè)置執(zhí)行App的入口點壤短。
設(shè)置完入口點后會通過 load_dylinker() 函數(shù)來解析加載 dyld,然后將入口點地址改成 dyld 的入口地址慨仿。這一步完后久脯,內(nèi)核部分就完成了 Mach-O 文件的加載。剩下的就是用戶態(tài)層 dyld 加載 App 了
dyld
參考資料:
dyld: Dynamic Linking On OS X
鏈接器:符號是怎么綁定到地址上的
dyld3-wwdc
iOS應(yīng)用的啟動流程和優(yōu)化詳解
dyld 是英文the dynamic link editor的簡寫镰吆,也就是動態(tài)鏈接器帘撰,是蘋果操作系統(tǒng)的一個重要組成部分。在iOS/Mac OSX系統(tǒng)中万皿,僅有很少量進程只需要內(nèi)核就能完成加載骡和,基本上所有的進程都需要動態(tài)鏈接的相赁。
Mach-O鏡像文件中會有很多對外部的庫和符號的引用,但是這些引用并不能直接用(對于動態(tài)庫的符號慰于,是undefined)钮科,這個填補工作就是由動態(tài)鏈器dyld來完成。
(undefined) external _NSLog (from Foundation)
(undefined) external _OBJC_CLASS_$_NSObject (from CoreFoundation)
概括講婆赠,dyld主要做了下面幾件事
1. loading
先執(zhí)行Mach-O文件绵脯,根據(jù)Mach-O中的undefined符號加載對應(yīng)的dylib,系統(tǒng)會設(shè)置一個共享緩存來解決加載的遞歸依賴問題(在load函數(shù)有一個算法做相應(yīng)的事情),把dylib映射到進程內(nèi)存休里。
2. linking
- rebase 修復(fù)指向當(dāng)前鏡像內(nèi)部的資源指針
ASLR:是Address Space Layout Randomization(地址空間布局隨機化)的簡稱蛆挫。App在被啟動的時候,程序和dylib會被映射到邏輯地址空間妙黍,這個邏輯地址空間有一個起始地址悴侵,ASLR技術(shù)讓這個起始地址是隨機的。這個地址如果是固定的拭嫁,黑客很容易就用起始地址+函數(shù)偏移地址找到對應(yīng)的函數(shù)地址可免。
Code Sign:就是蘋果代碼加密簽名機制,但是在Code Sign操作的時候做粤,加密的哈希不是針對整個文件浇借,而是針對每一個Page的。這個就保證了dyld在加載的時候怕品,可以對每個page進行獨立的驗證
- bind 將符號綁定到動態(tài)庫里對應(yīng)的地址上妇垢,bind指向的是鏡像外部的資源指針(跨鏡像)
3. static initailizers
- 當(dāng)我們dylib被映射到進程內(nèi)存,并被鏈接肉康,就需要對這些資源進行必要的初始化闯估,在iOS系統(tǒng)libsystem,libdispatch吼和,libObjc睬愤,這幾個庫有較高的優(yōu)先級,會被確保最先被初始化纹安。
- libObjc會向dyld注冊回調(diào)函數(shù)尤辱,被加載的庫包括我們的主程序 通過回調(diào)函數(shù)把oc類,符號等內(nèi)容(比如我們聲明的自定義class和selector)交給libObjc庫管理厢岂。這也是我們下一篇內(nèi)容要研究的重點光督。
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
4.skip main
至此,可執(zhí)行文件和動態(tài)庫都已被加載到內(nèi)存塔粒,各種資源指針都被修復(fù)指向正確的內(nèi)存地址结借,必要的初始化完成。dyld跳轉(zhuǎn)到main函數(shù)并執(zhí)行卒茬。
dyld代碼流程
用模擬跑的船老,真機流程不一樣的
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
/**
1. rebase
*/
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
_subsystem_init(apple);
// 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);
}
- rebaseDyld
- runDyldIntializers
- runDyldInitializers
- call dyld:_main
發(fā)現(xiàn)沒有咖熟,一個庫要被進程使用,需要做的就是那么幾件事柳畔,
load映射進內(nèi)存馍管,
rebase/bind進行必要的資源指針修復(fù),
一些環(huán)境變量的設(shè)置,
Initializer必要的初始化薪韩,
然后這個庫就可以被使用了确沸,dyld也不例外,然后dyld可以開心去加載其他動態(tài)庫了
dyld::_main
代碼非常長,貼幾個重要的片段俘陷,有興趣可以下載源碼看下
配置環(huán)境變量
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0);
}
//Check and see if there are any kernel flags
dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));
#if __has_feature(ptrauth_calls)
// Check and see if kernel disabled JOP pointer signing (which lets us load plain arm64 binaries)
if ( const char* disableStr = _simple_getenv(apple, "ptrauth_disabled") ) {
if ( strcmp(disableStr, "1") == 0 )
sKeysDisabled = true;
}
else {
// needed until kernel passes ptrauth_disabled for arm64 main executables
if ( (mainExecutableMH->cpusubtype == CPU_SUBTYPE_ARM64_V8) || (mainExecutableMH->cpusubtype == CPU_SUBTYPE_ARM64_ALL) )
sKeysDisabled = true;
}
#endif
// Grab the cdHash of the main executable from the environment
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
unsigned bufferLenUsed;
if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
}
getHostInfo(mainExecutableMH, mainExecutableSlide);
#if !TARGET_OS_SIMULATOR
// Trace dyld's load
notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
// Trace the main executable's load
notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
// Set the platform ID in the all image infos so debuggers can tell the process type
// FIXME: This can all be removed once we make the kernel handle it in rdar://43369446
// The host may not have the platform field in its struct, but there's space for it in the padding, so always set it
{
__block bool platformFound = false;
((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
if (platformFound) {
halt("MH_EXECUTE binaries may only specify one platform");
}
gProcessInfo->platform = (uint32_t)platform;
platformFound = true;
});
if (gProcessInfo->platform == (uint32_t)dyld3::Platform::unknown) {
// There were no platforms found in the binary. This may occur on macOS for alternate toolchains and old binaries.
// It should never occur on any of our embedded platforms.
#if TARGET_OS_OSX
gProcessInfo->platform = (uint32_t)dyld3::Platform::macOS;
#else
halt("MH_EXECUTE binaries must specify a minimum supported OS version");
#endif
}
}
#if TARGET_OS_OSX
// Check to see if we need to override the platform
const char* forcedPlatform = _simple_getenv(envp, "DYLD_FORCE_PLATFORM");
if (forcedPlatform) {
dyld_platform_t forcedPlatformType = 0;
if (strncmp(forcedPlatform, "6", 1) == 0) {
forcedPlatformType = PLATFORM_MACCATALYST;
} else if (strncmp(forcedPlatform, "2", 1) == 0) {
forcedPlatformType = PLATFORM_IOS;
} else {
halt("DYLD_FORCE_PLATFORM is only supported for platform 2 or 6.");
}
const dyld3::MachOFile* mf = (dyld3::MachOFile*)sMainExecutableMachHeader;
if (mf->allowsAlternatePlatform()) {
gProcessInfo->platform = forcedPlatformType;
}
}
// if this is host dyld, check to see if iOS simulator is being run
const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
if ( (rootPath != NULL) ) {
// look to see if simulator has its own dyld
char simDyldPath[PATH_MAX];
strlcpy(simDyldPath, rootPath, PATH_MAX);
strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
int fd = dyld3::open(simDyldPath, O_RDONLY, 0);
if ( fd != -1 ) {
//TODO:模擬器流程分支return
const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result);
if ( errMessage != NULL )
halt(errMessage);
return result;
}
}
else {
((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
if ( dyld3::MachOFile::isSimulatorPlatform(platform) )
halt("attempt to run simulator program outside simulator (DYLD_ROOT_PATH not set)");
});
}
#endif
CRSetCrashLogMessage("dyld: launch started");
//TODO:設(shè)置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
//TODO: 獲取可執(zhí)行路徑
sExecPath = _simple_getenv(apple, "executable_path");
// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
if (!sExecPath) sExecPath = apple[0];
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
// <rdar://54095622> kernel is not passing a real path for main executable
if ( strncmp(sExecPath, "/var/containers/Bundle/Application/", 35) == 0 ) {
if ( char* newPath = (char*)malloc(strlen(sExecPath)+10) ) {
strcpy(newPath, "/private");
strcat(newPath, sExecPath);
sExecPath = newPath;
}
}
#endif
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;
}
}
// Remember short name of process for later logging
sExecShortName = ::strrchr(sExecPath, '/');
if ( sExecShortName != NULL )
++sExecShortName;
else
sExecShortName = sExecPath;
#if TARGET_OS_OSX && __has_feature(ptrauth_calls)
// on Apple Silicon macOS, only Apple signed ("platform binary") arm64e can be loaded
sOnlyPlatformArm64e = true;
// internal builds, or if boot-arg is set, then non-platform-binary arm64e slices can be run
if ( const char* abiMode = _simple_getenv(apple, "arm64e_abi") ) {
if ( strcmp(abiMode, "all") == 0 )
sOnlyPlatformArm64e = false;
}
#endif
//設(shè)置進程限制條件
configureProcessRestrictions(mainExecutableMH, envp);
很多check/比較/set/get,對一些環(huán)境變量讀取罗捎,校驗,設(shè)置拉盾。
加載共享緩存
從iOS3.1開始桨菜,為了提高性能,絕大部分的系統(tǒng)動態(tài)庫文件都打包存放到了一個緩存文件中捉偏。共享緩存中存的都是系統(tǒng)級別的動態(tài)庫倒得。自己常見的動態(tài)庫或者第三方動態(tài)庫不會放到共享緩存中。
// load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache(mainExecutableSlide);
#else
mapSharedCache(mainExecutableSlide);
#endif
// If this process wants a different __DATA_CONST state from the shared region, then override that now
if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && (gEnableSharedCacheDataConst != sharedCacheDataConstIsEnabled) ) {
uint32_t permissions = gEnableSharedCacheDataConst ? VM_PROT_READ : (VM_PROT_READ | VM_PROT_WRITE);
sSharedCacheLoadInfo.loadAddress->changeDataConstPermissions(mach_task_self(), permissions,
(gLinkContext.verboseMapping ? &dyld::log : nullptr));
}
}
- 核心函數(shù)mapSharedCache(mainExecutableSlide)
- mapSharedCache中調(diào)用loadDyldCache加載共享緩存
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
results->loadAddress = 0;
results->slide = 0;
results->errorMessage = nullptr;
#if TARGET_OS_SIMULATOR
// simulator only supports mmap()ing cache privately into process
return mapCachePrivate(options, results);
#else
if ( options.forcePrivate ) {
// mmap cache into this process only
return mapCachePrivate(options, results);
}
else {
// fast path: when cache is already mapped into shared region
bool hasError = false;
if ( reuseExistingCache(options, results) ) {
hasError = (results->errorMessage != nullptr);
} else {
// slow path: this is first process to load cache
hasError = mapCacheSystemWide(options, results);
}
return hasError;
}
#endif
}
- 強制私有
- 共享緩存已有
- 第一次加載 這里會進入到// should be in mach/shared_region.h
dyld3 或 dyld2
//判斷是否使用閉包模式也是dyld3的模式啟動 ClosureMode::on 用dyld3 否則使用dyld2
if ( sClosureMode == ClosureMode::Off ) {
//dyld2
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
//dyld3 DYLD_LAUNCH_MODE_USING_CLOSURE 用閉包模式
sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
const dyld3::closure::LaunchClosure* mainClosure = nullptr;
dyld3::closure::LoadedFileInfo mainFileInfo;
mainFileInfo.fileContent = mainExecutableMH;
mainFileInfo.path = sExecPath;
...
// 首先到共享緩存中去找是否有dyld3的mainClosure
if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
...
}
...
//如果共享緩存中有告私,然后去驗證closure是否是有效的
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo,
、mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
bool allowClosureRebuilds = false;
if ( sClosureMode == ClosureMode::On ) {
allowClosureRebuilds = true;
}
...
//如果沒有在共享緩存中找到有效的closure 此時就會自動創(chuàng)建一個closure
if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
...
if ( mainClosure == nullptr ) {
// 創(chuàng)建一個mainClosure
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp,
bootToken);
if ( mainClosure != nullptr )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
}
}
// try using launch closure
// dyld3 開始啟動
if ( mainClosure != nullptr ) {
CRSetCrashLogMessage("dyld3: launch started");
...
//啟動 launchWithClosure
bool launched = launchWithClosure(mainClosure,
sSharedCacheLoadInfo.loadAddress,(dyld3::MachOLoaded*)mainExecutableMH,...);
//啟動失敗
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// closure is out of date, build new one
// 如果啟動失敗 重新去創(chuàng)建mainClosure
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo,
envp, bootToken);
if ( mainClosure != nullptr ) {
...
//dyld3再次啟動
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress,
(dyld3::MachOLoaded*)mainExecutableMH,...);
}
}
if ( launched ) {
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
//啟動成功直接返回main函數(shù)的地址
result = (uintptr_t)&fake_main;
return result;
}
else {
//啟動失敗
}
}
}
- dyld3優(yōu)化
- 加載速度
1.1. 一個deamon進程的解析器承桥,預(yù)處理所有可能影響啟動速度的search path驻粟,@path和環(huán)境變量
1.2. 然后分析Mach-O的header和依賴,并完成所有符號查找的工作
1.3. 然后將這些結(jié)構(gòu)創(chuàng)建成一個啟動閉包凶异,系統(tǒng)app的啟動閉包被構(gòu)建在sharedCache中蜀撑,第三方的app,在程序安裝或者更新的時候構(gòu)建這個啟動閉包剩彬,這些都在程序啟動前已經(jīng)被完成
1.4 閉包被構(gòu)建在shared cache中酷麦,我們甚至不需要打開一個單獨的文件,加載速度很快 - 安全性
加載閉包喉恋,并驗證啟動閉包的安全性沃饶,在dyld3之前在程序啟動時,dyld遞歸分析mach-oheader的依賴轻黑,可能修改并注入依賴庫的問題 -
看個官方的對比圖
實例化主程序
dyld3 和 dyld2走的流程差不多糊肤,dyld3 用的是閉包模式,更快氓鄙,更安全馆揉。imge是鏡像文件的意思,鏡像文件就是從磁盤映射到內(nèi)存的mach-O文件抖拦∩ǎ可以理解為只要是加載到內(nèi)存的mach-o文件就叫鏡像文件舷暮。
//TODO: 實例化主程序,返回imageLoader對象,并交給dyld管理
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
//主程序賦值給glinkContext
gLinkContext.mainExecutable = sMainExecutable;
//主程序是否代碼簽名
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
For each executable file (dynamic shared object) in use, an ImageLoader is instantiated.
- 主程序在dyld之前已經(jīng)被系統(tǒng)內(nèi)核映射到進程緩存噩茄。從上面這段官方注釋可知下面,任何一個可執(zhí)行文件要被使用,需要實例化一個imageLoader巢墅。
- 實例化主程序的作用是為主可執(zhí)行文件實例化為一個ImageLoaderMachO對象诸狭,可以看做把主可執(zhí)行文件抽象為ImageLoaderMachO的實例,交給dyld管理君纫,并被程序使用驯遇。
- 添加到了dyld管理的MappedRanges主列表-addImage()
實例化動態(tài)庫-加載插入的動態(tài)庫
// load any inserted libraries
// TODO:加載插入的庫
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// record count of inserted libraries so that a flat search will look at
// inserted libraries, then main, then others.
sInsertedDylibCount = sAllImages.size()-1;
//核心函數(shù)-load
ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
//實例化一個ImageLoader
// map in file and instantiate an ImageLoader
static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* path, const LoadContext& context)
// create image by mapping in a mach-o file
ImageLoader* ImageLoaderMachO::instantiateFromFile
//添加到dyld管理的主列表
static ImageLoader* checkandAddImage(ImageLoader* image, const LoadContext& context)
- 做的事情跟實例化主程序差不多,就是把插入的動態(tài)庫都是實例化一個imageLoader,并添加到dyld管理的主列表
- load方法實現(xiàn)了一個算法蓄髓,避免重復(fù)的庫文件加載
鏈接主程序
// TODO:鏈接主程序
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
// previous link() on main executable has already adjusted its internal pointers for ASLR
// work around that by rebasing by inverse amount
sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
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;
}
鏈接動態(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
// TODO:鏈接動態(tài)庫 循環(huán)
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);
}
}
}
鏈接主程序和鏈接動態(tài)庫邏輯基本一樣叉庐,核心函數(shù)是link()
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
//dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
// clear error strings
(*context.setErrorStrings)(0, NULL, NULL, NULL);
uint64_t t0 = mach_absolute_time();
//遞歸loadLibraries
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
// we only do the loading step for preflights
if ( preflightOnly )
return;
uint64_t t1 = mach_absolute_time();
context.clearAllDepths();
this->updateDepth(context.imageCount());
__block uint64_t t2, t3, t4, t5;
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
t2 = mach_absolute_time();
//遞歸rebase
this->recursiveRebaseWithAccounting(context);
context.notifyBatch(dyld_image_state_rebased, false);
t3 = mach_absolute_time();
//初始化主程序時,賦值為true
if ( !context.linkingMainExecutable )
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
t4 = mach_absolute_time();
if ( !context.linkingMainExecutable )
this->weakBind(context);
t5 = mach_absolute_time();
}
- link()函數(shù)主要做的事情
- recursiveLoadLibraries()会喝,保存一個依賴庫的數(shù)組陡叠,方便在內(nèi)存中找到自己的依賴庫,后面符號綁定時候也會用到這個數(shù)組肢执。
- recursiveRebaseWithAccounting(context)
主程序和動態(tài)庫的綁定
// Bind and notify for the main executable now that interposing has been registered
uint64_t bindMainExecutableStartTime = mach_absolute_time();
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
uint64_t bindMainExecutableEndTime = mach_absolute_time();
ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
gLinkContext.notifyBatch(dyld_image_state_bound, false);
// Bind and notify for the inserted images now interposing has been registered
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
}
}
// <rdar://problem/12186933> do weak binding only after all inserted images linked
// TODO:主程序弱綁定(after all inserted images linked)
sMainExecutable->weakBind(gLinkContext);
gLinkContext.linkingMainExecutable = false;
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
- recursiveBind()
- 對主可執(zhí)行文件和動態(tài)庫進行符號綁定操作枉阵,用到保存的libImages數(shù)組
- 數(shù)據(jù)fixup完成后把一些數(shù)據(jù)段設(shè)為只讀
運行初始化方法
所有鏡像文件都已加載,并且資源指針也都修復(fù)完畢预茄,可以做一些必要的初始化了兴溜。
// TODO:主程序初始化
initializeMainExecutable();
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
// 運行所有的dylibs中的initialzers方法
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
//先運行動態(tài)庫的初始化方法
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
// 運行主程序的初始化方法
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
...
}
- 這里是我們關(guān)注的重點
- _objc_init在這里別調(diào)用,向dyld注冊回調(diào)函數(shù)耻陕,通過回調(diào)函數(shù)拙徽,各個執(zhí)行文件的oc class,協(xié)議诗宣,方法膘怕,符號等內(nèi)容將交給libObjc處理,包括我們主可執(zhí)行文件(也就是我們自己code出來的oc代碼)召庞。篇幅有限岛心,下一篇我們再著重講解。
返回main函數(shù)
if (sSkipMain) {
notifyMonitoringDyldMain();
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);
}
ARIADNEDBG_CODE(220, 1);
result = (uintptr_t)&fake_main;
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
}
return result;
總結(jié):
分析了main函數(shù)之前篮灼,iOS程序的加載過程
- 首先內(nèi)核fork進程鹉梨,分配進程內(nèi)存,把主可執(zhí)行文件map到內(nèi)存,并啟動dyld穿稳,這一步從內(nèi)核態(tài)過渡到用戶態(tài)
- dyld 首先會rebaseSelf,并做必要的環(huán)境設(shè)置存皂,然后分析mainExecutableMH查找可用的共享緩存,并加載。這里我們提到了
dyld2 和 dyld3旦袋,以及dyld3相對于dyld2在啟動速度和安全上做的優(yōu)化骤菠。 - dyld的主要作用是,分析主程序mach-o文件疤孕,動態(tài)加載三方庫商乎,映進內(nèi)存,并對他們進行管理祭阀。由于ASLR及代碼簽名的原因鹉戚,需要對image進行rebase和binding操作,目的是讓程序內(nèi)的資源指針指向正確的內(nèi)存地址专控。在所有資源修復(fù)完畢之后抹凳,執(zhí)行主可執(zhí)行文件的初始化。也就是loading伦腐,rebase/binding,initializer三件套赢底。
- 在initializer中,dyld會保證最下層的動態(tài)庫libsystem被最先初始化柏蘑,libDispatch/libObjc也會很早被調(diào)用初始化幸冻。libObjc的初始化,會向dyld注冊回調(diào)函數(shù)咳焚,用于管理所有可執(zhí)行文件的OC部分洽损。放在下一篇來分析
思考:
我們已經(jīng)知道了main函數(shù)之前,程序的啟動的大致流程革半,我們可以從那幾個方面來提升程序的啟動速度碑定?
以下內(nèi)容,來自wwdc
- less dylib (減少庫加載督惰,必要時可以合并庫)
- less classed and methods (在加載時不傅,需要被管理旅掂,修復(fù)指針)
- less initializer (初始化主程序時赏胚,會調(diào)用所有的動態(tài)庫的initializer和c++構(gòu)造函數(shù))
- more swift (no initializer,不允許特定類型的未對齊數(shù)據(jù))
- less load
總之商虐,你寫越少的代碼觉阅,程序啟動越快??????。這篇內(nèi)容寫得還漫長秘车,能力有限典勇,中間可能有不正確或者不準(zhǔn)確的地方,希望大家能在評論區(qū)多多留言交流叮趴。