標籤: App啓動 dyldgit
本章節 主要介紹 dyld
的加載流程,瞭解在main函數以前,底層還作了什麼bootstrap
準備工做 dyld源碼 libdispatch-1271.120.2 源碼 Libsystem-1292.60.1 objc4-818.2緩存
dyld
(the dynamic link editor)是蘋果的動態連接器
,是蘋果操做系統的重要組成部分,在app被編譯打包成可執行文件格式的Mach-O
文件後,交由dyld負責鏈接,加載程序
dyld 1
包含在NeXTStep 3.3
中,在此以前的NeXT使用靜態二進制
數據。做用並非很大,dyld 1
是在系統普遍使用C++動態庫以前編寫的,因爲C++有許多特性,例如其初始化器的工做,在靜態環境工做良好,可是在動態環境中可能會下降性能。所以大型的C++動態庫會致使dyld須要完成大量的工做,速度變慢macOS 10.0
和Cheetah
前,還增長了一個特性,即Prebinding預綁定
。咱們可使用Prebinding技術爲系統中的全部dylib
和應用程序找到固定的地址
。dyld將會加載這些地址的全部內容。若是加載成功,將會編輯全部dylib和程序的二進制數據,來得到全部預計算。當下次須要將全部數據放入相同地址時就不須要進行額外操做了,將大大的提升速度。可是這也意味着每次啓動都須要編輯這些二進制數據,至少從安全性來講,這種方式並不友好。dyld 2
從2004年發佈至今,已經通過了多個版本迭代,咱們如今常見的一些特性,例如ASLR
、Code Sign
、share cache
等技術,都是在dyld 2中引入的安全
macOS Tiger
中推出了dyld 2
dyld 2
是dyld 1
徹底重寫的版本,能夠正確支持C++初始化器語義,同時擴展了mach-o格式並更新dyld。從而得到了高效率C++庫的支持。dlopen
和dlsym
(主要用於動態加載庫和調用函數)實現,且具備正確的語義,所以棄用了舊版的API
dlopen
:打開一個庫,獲取句柄dlsym
:在打開的庫中查找符號的值dlclose
:關閉句柄。dlerror
:返回一個描述最後一次調用dlopen、dlsym,或 dlclose 的錯誤信息的字符串。dyld
的設計目標
是提高啓動速度
。所以僅進行有限的健全性檢查。主要是由於之前的惡意程序比較少減小Prebinding的工做量
。與編輯程序數據
的區別在於,在這裏咱們僅編輯系統庫,且能夠僅在軟件更新時作這些事情。所以在軟件更新過程當中,可能會看到「優化系統性能」相似的文字。這就是在更新時進行Prebinding
。如今dyld用於全部優化,其用途就是優化。所以後面有了dyld 2增長
了大量的基礎架構
和平臺
。
x86
、x86_64
、arm
、arm64
和許多的衍平生臺。iOS
、tvOS
和watchOS
,這些都須要新的dyld功能codeSigning
代碼簽名、ASLR(Address space layout randomization)
地址空間配置隨機加載:每次加載庫時,可能位於不一樣的地址bound checking
邊界檢查:mach-o文件中增長了Header的邊界檢查功能,從而避免惡意二進制數據的注入share cache
共享代碼代替ASLR
是一種防範內存損壞漏洞被利用的計算機安全技術
,ASLR經過隨機放置進程關鍵數據區域的地址空間來防止攻擊者跳轉到內存特定位置來利用函數Mac OS X Leopard 10.5
(2007年十月發行)中某些庫導入了隨機地址偏移
,但其實現並無提供ASLR所定義的完整保護能力。而Mac OS X Lion 10.7則對全部的應用程序均提供了ASLR支持。iOS 4.3
內導入了ASLR
。邊界檢查
功能,從而能夠避免惡意二進制數據的注入
share cache
最先實在iOS3.1
和macOS Snow Leopard
中被引入,用於徹底取代Prebindingshare cache
是一個單文件
,包含大多數系統dylib
,因爲這些dylib合併成了一個文件,因此能夠進行優化。
文本段(_TEXT)
和數據段(_DATA)
,並重寫整個符號表,以此來減少文件的大小,從而在每一個進程中僅掛載少許的區域。容許咱們打包二進制數據段,從而節省大量的RAMdylib預連接器
,它在RAM上的節約是顯著的,在普通的iOS程序中運行能夠節約500-1g
內存預生成數據結構
,用來供dyld和Ob-C在運行時使用。從而沒必要在程序啓動時作這些事情,這也會節約更多的RAM和時間share cache
在macOS上本地生成,運行dyld共享代碼,將大幅優化系統性能dyld 3
是2017年WWDC推出的全新的動態連接器,它徹底改變了動態連接的概念,且將成爲大多數macOS系統程序的默認設置。2017 Apple OS平臺上的全部系統程序都會默認使用dyld 3.dyld 3
最先是在2017年的iOS 11
中引入,主要用來優化系統庫。iOS 13
系統中,iOS全面採用新的dyld 3來替代以前的dyld 2,由於dyld 3徹底兼容dyld 2
,其API接口也是同樣的,因此,在大部分狀況下,開發者並不須要作額外的適配就能平滑過渡。bt
堆棧信息查看app啓動是從哪裏開始的
在load
方法處加一個斷點
,經過bt
堆棧信息查看app啓動是從哪裏開始的
經過程序運行發現,是從dyld
中的_dyld_start
開始的性能優化
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
__attribute__((constructor)) void ypyFunc(){
printf("來了 : %s \n",__func__);
}
@interface ViewController ()
@end
@implementation ViewController
+ (void)load{
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
@end
複製代碼
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000104ca5f24 002-應用程加載分析`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:17:5
frame #1: 0x00000001aafd735c libobjc.A.dylib`load_images + 984
frame #2: 0x0000000104e0a190 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 448
frame #3: 0x0000000104e1a0d8 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 512
frame #4: 0x0000000104e18520 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 184
frame #5: 0x0000000104e185e8 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 92
frame #6: 0x0000000104e0a658 dyld`dyld::initializeMainExecutable() + 216
frame #7: 0x0000000104e0eeb0 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4400
frame #8: 0x0000000104e09208 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 396
frame #9: 0x0000000104e09038 dyld`_dyld_start + 56
(lldb)
複製代碼
在dyld-852
源碼中查找_dyld_start
,查找arm64架構
發現,是由彙編實現,經過彙編註釋發現會調用dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
方法,是一個C++
方法markdown
源碼中搜索dyldbootstrap
找到命名做用空間
,再在這個文件中查找start
方法,其核心是返回值的調用了dyld
的main
函數,其中macho_header
是Mach-O
的頭部,而dyld
加載的文件就是Mach-O類型
的,即Mach-O類型是可執行文件類型
,由四部分組成:Mach-O頭部、Load Command、section、Other Data
,能夠經過MachOView
查看可執行文件信息數據結構
//
// This is code to bootstrap dyld. This work in normally done for a program by dyld and crt.
// In dyld we have to do this manually.
//
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
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);
}
複製代碼
進入dyld::_main
的源碼實現,特別長,大約600多行,若是對dyld加載流程不太瞭解的童鞋,能夠根據_main
函數的返回值進行反推,這裏就多做說明。在_main函數中主要作了一下幾件事情:架構
【第一步:條件準備:環境,平臺,版本,路徑,主機信息
】:根據環境變量設置相應的值以及獲取當前運行架構app
【第二步:加載共享緩存
】:檢查是否開啓了共享緩存,以及共享緩存是否映射到共享區域,例如UIKit
、CoreFoundation
等dom
【第三步:主程序的初始化
】:調用instantiateFromLoadedImage
函數實例化了一個ImageLoader
對象
【第四步:插入動態庫
】:遍歷DYLD_INSERT_LIBRARIES
環境變量,調用loadInsertedDylib
加載
【第五步:link 主程序
】
【第六步:link 動態庫
】
【第七步:弱引用綁定
】
【第八步:執行初始化方法
】
【第九步:尋找主程序入口
即main
函數】:從Load Command
讀取LC_MAIN
入口,若是沒有,就讀取LC_UNIXTHREAD
,這樣就來到了平常開發中熟悉的main
函數了
下面主要分析下【第三步】和【第八步】
sMainExecutable
表示主程序變量,查看其賦值,是經過instantiateFromLoadedImage
方法初始化
// instantiate ImageLoader for main executable
// 【第三步:主程序的初始化】
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
複製代碼
instantiateFromLoadedImage初始化主程序
進入instantiateFromLoadedImage
源碼,其中建立一個ImageLoader
實例對象,經過instantiateMainExecutable
方法建立
// The kernel maps in main executable before dyld gets control. We need to
// make an ImageLoader* for the already mapped in main executable.
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";
}
複製代碼
進入instantiateMainExecutable
源碼,其做用是爲主可執行文件建立映像,返回一個ImageLoader
類型的image對象,即主程序
。其中sniffLoadCommands
函數時獲取Mach-O類型文件
的Load Command
的相關信息,並對其進行各類校驗
// 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
}
複製代碼
進入initializeMainExecutable
源碼,主要是循環遍歷
,都會執行runInitializers
方法
void initializeMainExecutable() {
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
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]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
複製代碼
全局搜索runInitializers(cons
,找到以下源碼,其核心代碼是processInitializers
函數的調用
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) {
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
複製代碼
進入processInitializers
函數的源碼實現,其中對鏡像列表調用recursiveInitialization
函數進行遞歸實例化
// <rdar://problem/14412057> upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images) {
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
//在鏡像列表中的全部鏡像上調用遞歸實例化,以創建未初始化的向上依賴關係的新列表
for (uintptr_t i=0; i < images.count; ++i) {
images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.若是還有任何向上的依賴關係,請將其初始化
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
複製代碼
全局搜索recursiveInitialization(cons
函數,其源碼實現以下
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 ) {
uint8_t oldState = fState;
// break cycles 結束遞歸循環
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
// 讓objc 知道咱們要加載此鏡像
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
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);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();//遞歸解鎖
}
複製代碼
在這裏,須要分紅兩部分探索,一部分是notifySingle
函數,一部分是doInitialization
函數,首先探索notifySingle
函數
全局搜索notifySingle(
函數,其重點是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
這句
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) {
//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
if ( handlers != NULL ) {
dyld_image_info info;
info.imageLoadAddress = image->machHeader();
info.imageFilePath = image->getRealPath();
info.imageFileModDate = image->lastModified();
for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
const char* result = (*it)(state, 1, &info);
if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
//fprintf(stderr, " image rejected by handler=%p\n", *it);
// make copy of thrown string so that later catch clauses can free it
const char* str = strdup(result);
throw str;
}
}
}
if ( state == dyld_image_state_mapped ) {//是否被映射
// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
// 保存來自共享混存外部的鏡像地址 + UUID
if (!image->inSharedCache()
|| (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
dyld_uuid_info info;
if ( image->getUUID(info.imageUUID) ) {
info.imageLoadAddress = image->machHeader();
addNonSharedCacheImageUUID(info);
}
}
}
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);
}
}
// mach message csdlc about dynamically unloaded images
if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
notifyKernel(*image, false);
const struct mach_header* loadAddress[] = { image->machHeader() };
const char* loadPath[] = { image->getPath() };
notifyMonitoringDyld(true, 1, loadAddress, loadPath);
}
}
複製代碼
全局搜索sNotifyObjCInit
,發現沒有找到實現,有賦值操做
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;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
複製代碼
搜索registerObjCNotifiers
在哪裏調用了,發如今_dyld_objc_notify_register
進行了調用注意:_dyld_objc_notify_register
的函數須要在libobjc
源碼中搜索
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);
}
複製代碼
在objc4-818.2
源碼中搜索_dyld_objc_notify_register
,發如今_objc_init
源碼中調用了該方法,並傳入了參數,因此sNotifyObjCInit
的賦值
的就是objc
中的load_images
,而load_images
會調用全部的+load
方法。因此綜上所述,notifySingle
是一個回調函數
/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);//重點
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
複製代碼
load函數加載
下面咱們進入load_images
的源碼看看其實現,以此來證實load_images
中調用了全部的load
函數
經過objc源碼中_objc_init源碼實現,進入load_images
的源碼實現
void load_images(const char *path __unused, const struct mach_header *mh) {
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
複製代碼
進入call_load_methods
源碼實現,能夠發現其核心是經過do-while
循環調用+load
方法
/*********************************************************************** * call_load_methods * Call all pending class and category +load methods. * Class +load methods are called superclass-first. * Category +load methods are not called until after the parent class's +load. * * This method must be RE-ENTRANT, because a +load could trigger * more image mapping. In addition, the superclass-first ordering * must be preserved in the face of re-entrant calls. Therefore, * only the OUTERMOST call of this function will do anything, and * that call will handle all loadable classes, even those generated * while it was running. * * The sequence below preserves +load ordering in the face of * image loading during a +load, and make sure that no * +load method is forgotten because it was added during * a +load call. * Sequence: * 1. Repeatedly call class +loads until there aren't any more * 2. Call category +loads ONCE. * 3. Run more +loads if: * (a) there are more classes to load, OR * (b) there are some potential category +loads that have * still never been attempted. * Category +loads are only run once to ensure "parent class first" * ordering, even if a category +load triggers a new loadable class * and a new loadable category attached to that class. * * Locking: loadMethodLock must be held by the caller * All other locks must not be held. **********************************************************************/
void call_load_methods(void) {
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
複製代碼
進入call_class_loads
源碼實現,瞭解到這裏調用的load
方法證明咱們前文說起的類的load
方法
/*********************************************************************** * call_class_loads * Call all pending class +load methods. * If new classes become loadable, +load is NOT called for them. * * Called only by call_load_methods(). **********************************************************************/
static void call_class_loads(void) {
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
複製代碼
因此,load_images
調用了全部的load
函數,以上的源碼分析過程正好對應堆棧的打印信息
【總結】load的源碼鏈爲:_dyld_start
--> dyldbootstrap::start
--> dyld::_main
--> dyld::initializeMainExecutable
--> ImageLoader::runInitializers
--> ImageLoader::processInitializers
--> ImageLoader::recursiveInitialization
--> dyld::notifySingle
(是一個回調處理) --> sNotifyObjCInit
--> load_images(libobjc.A.dylib)
那麼問題又來了,_objc_init是何時調用的呢?請接着往下看
走到objc
的_objc_init
函數,發現走不通了,咱們回退到recursiveInitialization
遞歸函數的源碼實現,發現咱們忽略了一個函數doInitialization
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 ) {
uint8_t oldState = fState;
// break cycles 結束遞歸循環
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
// 讓objc 知道咱們要加載此鏡像
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
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);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();//遞歸解鎖
}
複製代碼
進入doInitialization
函數的源碼實現這裏也須要分紅兩部分,一部分是doImageInit
函數,一部分是doModInitFunctions
函數
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
源碼實現,其核心主要是for循環加載方法的調用
,這裏須要注意的一點是,libSystem
的初始化必須先運行
void ImageLoaderMachO::doImageInit(const LinkContext& context) {
if ( fHasDashInit ) {
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_ROUTINES_COMMAND:
Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
#if __has_feature(ptrauth_calls)
func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( ! dyld::gProcessInfo->libSystemInitialized ) {//libSystem初始化程序必須先運行,優先級很高
// <rdar://problem/17973316> libSystem initializer must run first
dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
複製代碼
進入doModInitFunctions
源碼實現,這個方法中加載了全部Cxx
文件能夠經過測試程序的堆棧信息來驗證,在C++方法處加一個斷點
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) {
if ( fHasInitializers ) {
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
if ( type == S_MOD_INIT_FUNC_POINTERS ) {....}
else if ( type == S_INIT_FUNC_OFFSETS ) {....}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
複製代碼
走到這裏,仍是沒有找到_objc_init的調用?怎麼辦呢?放棄嗎?固然不行,咱們還能夠經過_objc_init
加一個符號斷點來查看調用_objc_init前的堆棧信息,
_objc_init
加一個符號斷點,運行程序,查看_objc_init
斷住後的堆棧信息
在libsystem
Libsystem-1292.60.1 中查找libSystem_initializer
,查看其中的實現
libSystem_initializer源碼實現
// libsyscall_initializer() initializes all of libSystem.dylib
// <rdar://problem/4892197>
__attribute__((constructor))
static void libSystem_initializer(int argc, const char* argv[], const char* envp[], const char* apple[], const struct ProgramVars* vars) {
....
_libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_START);
__libkernel_init(&libkernel_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(KERNEL);
__libplatform_init(NULL, envp, apple, vars);
_libSystem_ktrace_init_func(PLATFORM);
__pthread_init(&libpthread_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(PTHREAD);
_libc_initializer(&libc_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(LIBC);
// TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
// Note that __malloc_init() will also initialize ASAN when it is present
__malloc_init(apple);
_libSystem_ktrace_init_func(MALLOC);
#if TARGET_OS_OSX
/* <rdar://problem/9664631> */
__keymgr_initializer();
_libSystem_ktrace_init_func(KEYMGR);
#endif
_dyld_initializer();//dyld 初始化
_libSystem_ktrace_init_func(DYLD);
libdispatch_init();// dispatch 初始化
_libSystem_ktrace_init_func(LIBDISPATCH);
#if !TARGET_OS_DRIVERKIT
_libxpc_initializer();
_libSystem_ktrace_init_func(LIBXPC);
#if CURRENT_VARIANT_asan
setenv("DT_BYPASS_LEAKS_CHECK", "1", 1);
#endif
#endif // !TARGET_OS_DRIVERKIT
// must be initialized after dispatch
_libtrace_init();
_libSystem_ktrace_init_func(LIBTRACE);
#if !TARGET_OS_DRIVERKIT
#if defined(HAVE_SYSTEM_SECINIT)
_libsecinit_initializer();
_libSystem_ktrace_init_func(SECINIT);
#endif
#if defined(HAVE_SYSTEM_CONTAINERMANAGER)
_container_init(apple);
_libSystem_ktrace_init_func(CONTAINERMGR);
#endif
__libdarwin_init();
_libSystem_ktrace_init_func(DARWIN);
#endif // !TARGET_OS_DRIVERKIT
__stack_logging_early_finished(&malloc_funcs);
.....
}
複製代碼
根據前面的堆棧信息,咱們發現走的是libSystem_initializer
中會調用libdispatch_init
函數,而這個函數的源碼是在libdispatch
開源庫中的, libdispatch-1271.120.2 源碼 在libdispatch
中搜索 libdispatch_init
DISPATCH_EXPORT DISPATCH_NOTHROW void libdispatch_init(void) {
dispatch_assert(sizeof(struct dispatch_apply_s) <=
DISPATCH_CONTINUATION_SIZE);
if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
_dispatch_mode |= DISPATCH_MODE_STRICT;
}
#if DISPATCH_DEBUG || DISPATCH_PROFILE
#if DISPATCH_USE_KEVENT_WORKQUEUE
if (getenv("LIBDISPATCH_DISABLE_KEVENT_WQ")) {
_dispatch_kevent_workqueue_enabled = false;
}
#endif
#endif
#if HAVE_PTHREAD_WORKQUEUE_QOS
dispatch_qos_t qos = _dispatch_qos_from_qos_class(qos_class_main());
_dispatch_main_q.dq_priority = _dispatch_priority_make(qos, 0);
#if DISPATCH_DEBUG
if (!getenv("LIBDISPATCH_DISABLE_SET_QOS")) {
_dispatch_set_qos_class_enabled = 1;
}
#endif
#endif
#if DISPATCH_USE_THREAD_LOCAL_STORAGE
_dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
_dispatch_thread_key_create(&dispatch_priority_key, NULL);
_dispatch_thread_key_create(&dispatch_r2k_key, NULL);
_dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
_dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
_dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
_dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
_dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
NULL);
_dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
_dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
_dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
_dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
_dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
_dispatch_thread_key_create(&dispatch_deferred_items_key,
_dispatch_deferred_items_cleanup);
#endif
pthread_key_create(&_os_workgroup_key, _os_workgroup_tsd_cleanup);
#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
_dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif
_dispatch_queue_set_current(&_dispatch_main_q);
_dispatch_queue_set_bound_thread(&_dispatch_main_q);
#if DISPATCH_USE_PTHREAD_ATFORK
(void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
dispatch_atfork_parent, dispatch_atfork_child));
#endif
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();//重點
_voucher_init();
_dispatch_introspection_init();
}
複製代碼
進入_os_object_init
源碼實現,其源碼實現調用了_objc_init
函數結合上面的分析,從初始化_objc_init
註冊的_dyld_objc_notify_register
的參數2,即load_images
,到sNotifySingle
--> sNotifyObjCInie=參數2
到sNotifyObjcInit()
調用,造成了一個閉環
void
_os_object_init(void)
{
_objc_init();// 重點
Block_callbacks_RR callbacks = {
sizeof(Block_callbacks_RR),
(void (*)(const void *))&objc_retain,
(void (*)(const void *))&objc_release,
(void (*)(const void *))&_os_objc_destructInstance
};
_Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}
複製代碼
因此能夠簡單的理解爲sNotifySingle
這裏是添加通知即addObserver
,_objc_init
中調用_dyld_objc_notify_register
至關於發送通知,即push
,而sNotifyObjcInit
至關於通知的處理函數,即selector
【總結】:_objc_init的源碼鏈:_dyld_start
--> dyldbootstrap::start
--> dyld::_main
--> dyld::initializeMainExecutable
--> ImageLoader::runInitializers
--> ImageLoader::processInitializers
--> ImageLoader::recursiveInitialization
--> doInitialization
-->libSystem_initializer
(libSystem.B.dylib) --> _os_object_init
(libdispatch.dylib) --> _objc_init
(libobjc.A.dylib)
彙編調試,能夠看到顯示來到+[ViewController load]
方法
繼續執行,來到ypyFunc
的C++函數
點擊stepover
,繼續往下,跑完了整個流程,會回到_dyld_start
,而後調用main()
函數,經過彙編完成main
的參數賦值等操做dyld
彙編源碼實現
彙編調試回到_dyld_start LC_MAIN case, set up stack for call to main()
#if __arm64__ && !TARGET_OS_SIMULATOR
.text
.align 2
.globl __dyld_start
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
mov x0, #0
mov x1, #0
stp x1, x0, [sp, #-16]! // make aligned terminating frame
mov fp, sp // set up fp to point to terminating frame
sub sp, sp, #16 // make room for local variables
#if __LP64__
ldr x0, [x28] // get app's mh into x0
ldr x1, [x28, #8] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
add x2, x28, #16 // get argv into x2
#else
ldr w0, [x28] // get app's mh into x0
ldr w1, [x28, #4] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
add w2, w28, #8 // get argv into x2
#endif
adrp x3,___dso_handle@page
add x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
mov x4,sp // x5 has &startGlue
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
mov x16,x0 // save entry point address in x16
#if __LP64__
ldr x1, [sp]
#else
ldr w1, [sp]
#endif
cmp x1, #0
b.ne Lnew
// LC_UNIXTHREAD way, clean up stack and jump to result
#if __LP64__
add sp, x28, #8 // restore unaligned stack pointer without app mh
#else
add sp, x28, #4 // restore unaligned stack pointer without app mh
#endif
#if __arm64e__
braaz x16 // jump to the program's entry point
#else
br x16 // jump to the program's entry point
#endif
// LC_MAIN case, set up stack for call to main()
Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib
#if __LP64__
ldr x0, [x28, #8] // main param1 = argc
add x1, x28, #16 // main param2 = argv
add x2, x1, x0, lsl #3
add x2, x2, #8 // main param3 = &env[0]
mov x3, x2
Lapple: ldr x4, [x3]
add x3, x3, #8
#else
ldr w0, [x28, #4] // main param1 = argc
add x1, x28, #8 // main param2 = argv
add x2, x1, x0, lsl #2
add x2, x2, #4 // main param3 = &env[0]
mov x3, x2
Lapple: ldr w4, [x3]
add x3, x3, #4
#endif
cmp x4, #0
b.ne Lapple // main param4 = apple
#if __arm64e__
braaz x16
#else
br x16
#endif
#endif // __arm64__ && !TARGET_OS_SIMULATOR
複製代碼
dyld中main部分的彙編源碼實現
注意:main
是寫定的函數,寫入內存,讀取到dyld
,若是修改了main函數的名稱
,會報錯
因此,綜上所述,最終dyld加載流程
,以下圖所示,圖中也詮釋了前文中的問題:爲何是load-->Cxx-->main
的調用順序
🌹 喜歡就點個贊吧👍🌹
🌹 以爲有收穫的,能夠來一波,收藏+關注,評論 + 轉發,以避免你下次找不到我😁🌹
🌹歡迎你們留言交流,批評指正,互相學習😁,提高自我🌹