iOS 應用程序加載

1. APP 加載分析

1.1 動靜態庫

  1. app依賴很多底層庫,底層庫是很什麼?

    可執行的代碼的二進制,可以被操作系統寫入到內存

  2. 庫分爲幾種?
    靜態庫: .a .lib,動態庫: framework .so .dll

  3. 動靜態庫的區別?

    靜態庫:在鏈接階段,會將彙編生成的目標文件與引用的庫一起鏈接打包到可執行文件中,可能會重複編譯多次

    動態庫:程序編譯並不會鏈接到目標代碼中,而是程序運行時才被載入。
    優勢:減少打包之後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(),

  1. 環境變量相關處理,先 從環境中獲取主可執行文件的cdHash, checkEnvironmentVariables(envp);,然後defaultUninitializedFallbackPaths(envp);

  2. 加載共享資源checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

  3. dyld本身,添加到UUID列表

  1. reloadAllImages

  2. 運行所有初始化程序initializeMainExecutable()

  3. 通知監聽dyldmain,然後進入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()讀取加載鏡像文件。

  1. 加載任何插入動態庫loadInsertedDylib(*lib),讀取爲image

  1. 鏈接庫,遍歷代碼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進行通知要初始化鏡像文件,此時,sNotifyObjCInitnil,則調用無意義,無法完成通知。

那麼在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方法之後的原因。

總結

  1. APP加載過程:程序啓動依次加載dyldlibSystemlibdispathc.dyldlibobjc動態庫,最終調用_objc_init()方法,在此方法中Runtimedyld註冊回調函數,加載新的image,執行map_imagesload_imagesimageLoader加載image,調用main函數

  2. dyld中,__dyld_start鏈接開始,調用start()方法,調用dyld::_main()方法。在此方法中,

    • 環境變量相關處理,先獲取可執行文件的cdHashcheckEnvironmentVariables(envp)defaultUninitializedFallbackPaths(envp)
    • 加載共享緩存,通過checkSharedRegionDisable()驗證共享緩存路徑,然後mapSharedCache(),加載共享緩存。
    • dyld本身,添加到UUID列表,addDyldImageToUUIDList()
    • 然後加載所有的鏡像文件,reloadAllImages
    • 運行所有初始化程序initializeMainExecutable()
    • 通知監聽dyldmain,然後進入main函數,notifyMonitoringDyldMain()
  3. reloadAllImages加載鏡像文件的步驟:

    • 實例化主程序instantiateFromLoadedImage(),內核會映射到主要可執行文件中,我們需要爲映射到主可執行文件的文件,創建ImageLoader。在此方法中,然後讀取image,然後addImage()讀取加載鏡像文件。會先在instantiateMainExecutable()中,會確認此mach-o文件中是否具有壓縮的LINKEDIT以及段數。
    • 加載插入任何動態庫loadInsertedDylib(*lib),將其讀取爲鏡像文件iamge
    • 鏈接庫。先遍歷,讀取image,然後link。在link中,遞歸插入動態加載的鏡像文件。
  4. 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_imagesload_imagesimageLoader加載image,調用main函數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章