iOS 應用程序加載
1. APP 加載分析
1.1 動靜態庫
-
app依賴很多底層庫,底層庫是很什麼?
可執行的代碼的二進制,可以被操作系統寫入到內存
-
庫分爲幾種?
靜態庫: .a .lib,動態庫: framework .so .dll -
動靜態庫的區別?
靜態庫:在鏈接階段,會將彙編生成的目標文件與引用的庫一起鏈接打包到可執行文件中,可能會重複編譯多次
動態庫:程序編譯並不會鏈接到目標代碼中,而是程序運行時才被載入。
優勢:減少打包之後APP的大小,共享內容,節約資源,通過更新動態庫,達到更新程序的目的。
常見動態庫:UIKit,libdispatch、libobj.dyld
編譯過程:
動靜態庫示例:
1.2 加載過程
2._dyld_start
分析
通過在+ (void)load
方法中添斷點,查看調用堆棧,通過彙編查看,在程序啓動的時,調用dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*)
,那麼在這之前還有一系列操作,
通過bt
,查看調用堆棧
接下來我們分析一下_dyld_start
,查看dyld
源碼,全局搜索_dyld_start
,發現會跳轉dyldbootstrap::start
方法,爲c++
方法,
全局搜索start(
,找到start
方法,
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[],
intptr_t slide, const struct macho_header* dyldsMachHeader,
uintptr_t* startGlue)
{
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
slide = slideOfMainExecutable(dyldsMachHeader);
bool shouldRebase = slide != 0;
#if __has_feature(ptrauth_calls)
shouldRebase = true;
#endif
if ( shouldRebase ) {
rebaseDyld(dyldsMachHeader, slide);
}
// allow dyld to use mach messaging
mach_init();
// 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(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
進入dyld::_main()
,
-
環境變量相關處理,先 從環境中獲取主可執行文件的
cdHash
,checkEnvironmentVariables(envp);
,然後defaultUninitializedFallbackPaths(envp);
-
加載共享資源
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
-
將
dyld
本身,添加到UUID
列表
-
reloadAllImages
-
運行所有初始化程序
initializeMainExecutable()
-
通知監聽
dyld
的main
,然後進入main
函數。
2.1 reloadAllImages
分析
1.實例化主程序
CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
instantiateFromLoadedImage
方法中
先讀取image
, addImage()
讀取加載鏡像文件。
- 加載任何插入動態庫
loadInsertedDylib(*lib)
,讀取爲image
。
- 鏈接庫,遍歷代碼
iamge
,然後link
。
在link
的過程中,會遞歸,插入任何動態加載的鏡像文件
2.2 initializeMainExecutable
運行所有初始化程序
initializeMainExecutable
方法:
在initializeMainExecutable
方法中,runInitializers
運行主程序的可執行文件,在runInitializers
方法中,代碼如下:
在processInitializers
方法中,進行初始化準備,遍歷iamge.count
,遞歸一個個開始初始化條件images[i]->recursiveInitialization
,代碼如下:
在recursiveInitialization
中,通過上下文的notifySingle
方法,通知要進行單個鏡像的初始化。
而
notifySingle
方法是怎麼進行通知呢?
通過查看notifySingle()
方法,在此方法中,通過(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
獲取鏡像文件的真實地址,如下圖:
那麼
sNotifyObjCInit
指針地址又是什麼時候傳進來的呢?
通過搜索發現sNotifyObjCInit
是在registerObjCNotifiers()
方法中進行賦值,而registerObjCNotifiers()
方法又是在_dyld_objc_notify_register()
方法中調用,通過傳進來的init
地址,回調函數,最終加載鏡像文件
那麼
_dyld_objc_notify_register
方法是在什麼時候調用,給sNotifyObjCInit
賦值,讓(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
回調函數有意義,將鏡像文件傳遞回去呢?接下來我們查看一下libobjc
源碼
由此猜測:是在
_objc_init()
方法中,註冊通知,傳入函數地址,然後最終回調鏡像文件的。那麼這個回調地址又是怎樣在dyld
庫和libobjc
之間傳遞的呢?
在_objc_init()
方法中斷點,然後bt
,打印堆棧信息如下:
這也從另一個方面進一步驗證了上面所說的啓動流程,當在
recursiveInitialization
方法中通過notifySingle
進行通知要初始化鏡像文件,此時,sNotifyObjCInit
爲nil
,則調用無意義,無法完成通知。
那麼在notifySingle
之後,調用了this->doInitialization(context)
,也驗證了調用堆棧,
在
doInitialization
方法中,
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
先調用doImageInit(context)
,確保libSystem
庫必須提前初始化完成。
再調用doModInitFunctions()
方法,必然會調用libSystem
庫中的libSystem_initializer
方法。
在libSystem_initializer
方法中調用libdispatch_init
,
然後在
libdispatch
庫中,調用libdispatch_init
函數。
libdispatch_init
部分代碼:
_os_object_init
代碼:
最終調用到
libobjc
庫中的_objc_init
的方法,調用_dyld_objc_notify_register
,傳入地址給sNotifyObjCInit
,然後回調鏡像文件。
而先調用
notifySingle
方法,在調用doInitialization
方法,而notifySingle
方法中的sNotifyObjCInit
指針是從_objc_init
方法中_dyld_objc_notify_register(&map_images, load_images, unmap_image)
的load_images
傳遞過去的,這也解釋了C++函數和構造函數的調用在load
方法之後的原因。
總結
-
APP加載過程:程序啓動依次加載
dyld
、libSystem
、libdispathc.dyld
、libobjc
動態庫,最終調用_objc_init()
方法,在此方法中Runtime
向dyld
註冊回調函數,加載新的image
,執行map_images
、load_images
,imageLoader
加載image
,調用main
函數 -
在
dyld
中,__dyld_start
鏈接開始,調用start()
方法,調用dyld::_main()
方法。在此方法中,- 環境變量相關處理,先獲取可執行文件的
cdHash
,checkEnvironmentVariables(envp)
、defaultUninitializedFallbackPaths(envp)
- 加載共享緩存,通過
checkSharedRegionDisable()
驗證共享緩存路徑,然後mapSharedCache()
,加載共享緩存。 - 將
dyld
本身,添加到UUID
列表,addDyldImageToUUIDList()
。 - 然後加載所有的鏡像文件,
reloadAllImages
。 - 運行所有初始化程序
initializeMainExecutable()
- 通知監聽
dyld
的main
,然後進入main
函數,notifyMonitoringDyldMain()
。
- 環境變量相關處理,先獲取可執行文件的
-
reloadAllImages
加載鏡像文件的步驟:- 實例化主程序
instantiateFromLoadedImage()
,內核會映射到主要可執行文件中,我們需要爲映射到主可執行文件的文件,創建ImageLoader
。在此方法中,然後讀取image
,然後addImage()
讀取加載鏡像文件。會先在instantiateMainExecutable()
中,會確認此mach-o文件中是否具有壓縮的LINKEDIT
以及段數。 - 加載插入任何動態庫
loadInsertedDylib(*lib)
,將其讀取爲鏡像文件iamge
。 - 鏈接庫。先遍歷,讀取
image
,然後link
。在link
中,遞歸插入動態加載的鏡像文件。
- 實例化主程序
-
initializeMainExecutable()
運行所有初始化程序步驟:runInitializers()
processInitializers
初始化準備。processInitializers
中,遍歷iamge.count
,遞歸一個個開始初始化條件images[i]->recursiveInitialization
。- 在遞歸開始初始化條件中
recursiveInitialization
,通過notifySingle
方法,對單個鏡像通知開始初始化。獲取鏡像文件的真實地址(*sNotifyObjCInit)(image->getRealPath(), image->machHeader())
, 而notifySingle
中的sNotifyObjCInit
是在objc_init()
中註冊傳遞過來的,所以只有當objc_init()
調用時,重新加載image
。 notifySingle
方法之後,遍歷初始化this->doInitialization(context)
- 在
doInitialization
方法中,先調用doImageInit(context)
,確保libSystem
庫必須提前初始化完成。再調用doModInitFunctions()
方法,對 C++和構造函數處理,然後調用libSystem_initializer
方法,調用libdispatch_init
,調用_os_object_init
,最終調用_objc_init
方法。 _objc_init
方法來註冊回調函數,重新加載images
,執行map_images
、load_images
,imageLoader
加載image
,調用main
函數。