想要了解应用程序加载,我们需要了解下面几个问题:
- 我们写的代码是如何加载到内存的?
- 我们使用的动静态库是如何加载到内存的?
- objc是如何启动的?
我们程序执行都会依赖很多库,比如UIKit
、CoreFoundatapproachion
、libsystem
等,这些库其实就是可执行的二进制文件,能够被操作系统加载到内存。库分为两种:动态库和静态库。
整个编译过程如下图:

首先我们的代码会经历预编译阶段,进行词法和语法树的分析,然后交给编译器编译,会生成相应的汇编文件,把这些内容链接装载到内存,生成我们的可执行文件。
动态库苹果xr和静态库区别一个是动架构师和程序员的区别态链接,一个是静态链接。

静态链接:一个一个装载进内存,会有重复的问题,浪费相函数调用时的实参和形参之间传递应性application能。 动态链接:并不是直接加载,通过映射函数调用可以嵌套吗加载到内存,内存是共享的只会有一份,大部分苹果的库都为动态库。
静态库和动态库是如何加载到内存的呢?需要连接器dyld
,dyld
作用如下图:

下面我们开始研二进制转化为十进制究dyld
,先在main
函数位置打一个断点,架构图怎么制作执行程苹果13序:

看到函数调用栈在main
函二进制怎么算数之前会执行start
方法,点击可以看到苹果手机start
就是执行dyld
中的start
方法:

我们再添加一个名字为start
的符号断点,再执行程序,发现断点并没有断approach到start
位置

为什么没有停在start
?证二进制八进制十进制十六进制转换明在下层真正调用不是start
而是其他方法。我们知道load
方法是在程序执行之前就跑,我们添加个load
方法并打一个断点,

看函数调用栈。点击最下面的_dyld_start

dyld
源码可以在官网下载。
打开源码工程,首先苹果7全局搜索_dyld_star函数调用可以作为独立的语句存在t
,发现是汇编实现的,根据架构不同,代码不架构图怎么做word同,我们直接看真机ar苹果官网m64

在这架构图里我们分析主要代码逻辑即可架构图怎么做word,配合注释找到start
方法调用:

全局搜索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 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); }
这里面最重要的就是最后一函数调用语句行,main
函数,点击进去,发现main
函数代码非常多,有将近1000行代码,看这种代码需要点技巧,我们知道这个函数是有返回值的,那就先看下返回值是什么,有什么对应操作。
返回值是result
,赋值的地方有:
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
result = (uintptr_t)sMainExecutab函数调用可以作为一个函数的形参le->getEntryFromLC_UNIXTHREAD();
result = (uintptr_t)&fake_main;
上面三个地方有对result
赋值,可以看到最主要就是sMainExecutable
相关处理,找一下s函数调用可以作为一个函数的形参MainExecutable
初始化:
/// instantiate ImageLoader for main executable sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
看到注释写的也很明白,初始化苹果范冰冰镜像文件。点击进去:
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
,看到sniapplicationffLoadCommands(m函数调用后必须带回返回值吗h, path, false, &compressed, &am二进制转换器p;segCount, &libCount, context, &codeSigCmd, &encryptCmd);
这个代码就是按照machO
格式进行加载文苹果8plus件。
共享缓存相关处理:
// load shared cache checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
加载插入的动态库:
// load any inserted libraries if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); }
链接主程序:appearance
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
链接插入的动态库:
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
弱引用绑定主程序:
// <rdar://problem/12186933> do weak binding only after all inserted images linked sMainExecutable->weakBind(gLinkContext);
初始化主程序:
// run all initializers initializeMainExecutable();
回调函数:
// notify any montoring proccesses that this process is about to enter main() notifyMonitoringDyldMain();
initializeM苹果手机ainExecutable
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]); }
首苹果8先拿到所有镜像文件个数,循环跑起来。点击进入runInitializers
看下如何初始化
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); }
这里最重要的是processInitializer架构图怎么做words
函数,点进去
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
,找到这个方法的实现void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, InitializerTimingList& timingInfappointmento, UninitedUpwards& uninitUps)
,
里面最重要的代码如图:

先对依赖文件进行初始化,然后再初始化自己。notifySingle
全局搜索找到这个方法实现,观察里面最重要代码:(*sNotifyObjCInit)(image->getR苹果7ealPath(), image->machHeader());
。
全局搜索sNoti架构图怎么做wordfyObjCInit
,发现在registerObjCN架构otifiers
方法中有赋值,再搜索看哪调用这二进制转八进制个方法:_dyld_objc_no架构师tify_register
,这个方法和objc
源码中objc_init
里面的方法名字是一模一样的。apple但其实苹果7从dyld
加载到objc
这条线到现在已经断了,需要再找下架构工程师别的方法。
打开objc
源码工程,在源码中_objc_init
打一个断点,查看架构函数调用栈:

从下往上看执行流函数调用的三种方式程:
- _dyld_start
- dyldboo苹果官网tstrap::start
- dyld::_main
- dyld::initia二进制亡者列车lizeMainExecutable()
- runInitializers
- processInitialiapp小胖子zers
- recursiveInitialization
- doInitialization
- doModInitFunctions
到这是dyld
处理的流程,在往上看就不知道是什么流程,我们现在从上往下看,架构图怎么做word先走libdispatch
的_os_objec苹果7t_in架构it
方法appreciate,我们下载libdispatch
源码,全局搜索_os_ob苹果官网ject_init
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 }
可以看到_os_object_init
确实调用了_objc_init
,再全局搜二进制转化为十进制索_os_o函数调用方法bject_init
,发现是在libd苹果范冰冰ispatch_init
有调用,再全局搜索libdispatch_init
发现没有调用地方,看上面截图函数调用栈,libSystem
调用过来的,我们下载libSystem
源码,打开libSystem
源码工程全局搜索函数调用可以作为独立的语句存在libdispa苹果8tch_init
,发现是libSystem_initial二进制小数转十进制izer
方法进行调用,而libSystem_initializer
这个方法没有调用,看函数调用栈回到dyld
源码进行搜索,没找到相关信息,全局搜索doMod苹果xrInitFunct二进制亡者列车ion架构师工资s
看这个方法实现,可以看到框住就是调用libSy函数调用可以出现在表达式中吗stem_initializer
相关的方法:

doModInitFunctions
是由doInitialization
方法调用,最后找到位置:二进制转化为十进制

到此,整个项目从dyld
如何加载到o二进制转化为十进制bjc_init
的流程已经分析完了。
我们可以看到_dyld_objc_notify_register(&map_i架构图怎么做wordmag苹果xes, load_images, unmap_image);
方法调用,map_im函数调用语句ages
和load_image
都是参数,函数调用关系图那么他们什么时候调用的呢,时机肯定在别的地方,继续探索。
在dyld
源码中搜索_dyld_objc_notify_regis架构图模板ter
,发现有好几个调用地方,到苹果手机底真实的是执行哪个呢?我们新建个工程,下一个符号断点_dyld_objc_notify_reapplegister
,运行:

所以_dyld_objc_notify_register
调用的代码应该是下面代码:
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) { if ( gUseDyld3 ) return dyld3::_dyld_objc_notify_register(mapped, init, unmapped); DYLD_LOCK_THIS_BLOCK; typedef bool (*funcType)(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped); static funcType __ptrauth_dyld_function_ptr p = NULL; if(p == NULL) dyld_func_lookup_and_resign("__dyld_objc_notify_register", &p); p(mapped, init, unmapped); }
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) { log_apis("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped); gAllImages.setObjCNotifiers(mapped, init, unmapped); }

想要找到map_images
和load_images
调用,就需要找到_objcNotifyMapped
和_objcNotifyInit
调用。
之前分析notifySingle
调用sNotifyObjCInit
,找到sNotifyappleObjCInit
赋值的地方,

_dyld_objc_notify_regi架构图ster
调用的registerObjCNo函数调用方法tifiers
,也就是说_dyld_objc_notify_regis苹果7ter
会对三个参数进行初始化,也就是说notifySingle
会对将要执app小胖子行的方法进行初始化赋值,但是具体调用还不知道。
之前我们看到有_dyld_objc_notify_register
方法:
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); }
这个方法架构三个参数对应着objc
源码中同名方法的map_二进制转十六进制公式images
、load_images
、umap_imag苹果12e
,进去看赋值:
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()); } } }
也就是我们现在需要找sNotifyObjCMapped()
、sNotifyObjCInit()
、sNotifyObjCUnmapped()
,这三个函数调用才会执行我们最关心的map_images
、load_images
、umap_image
。
- 全局搜索
sNotifyObjCMapped
找调用的地方,发现_dyld_objc_notify_register
->registerOb苹果xjCNotifiers
->notifyBatchPartial
-&二进制亡者列车gt;sNotifyObjCMapped
这个流程会调用,也就是函数执行时候,就会执行第一个参数对应的方法。 - 全局搜索
sNotifyObjCIni函数调用方法t
,有两个调用地方,a(dyld_image_state_initialized
):_dyl二进制d_objc_notify_re苹果7gister
->registerObjC二进制转十六进制公式Notifier苹果7s
->s函数调用语句NotifyObjCInit()
,加载自己的库的时候会架构是什么意思直接调用load_images
。b(dyld_iappearancemage_state_dependents_iapproachnitialized
):notifySingle
->sNo函数调用的三种方式tifyObjCInit()
,加载自己依赖库的时候,依赖库是通函数调用可以嵌套吗过notifySingle
来调用load_images
。 - 全局二进制转换器搜索
sN架构师证书otifyObjCUnmapped
,发现removeImage
才会调用,移除镜像文件时候,暂时不深入分析。
总结下从dyappleld
开始怎么加载到objc
以及map_images
和load_imaAPPges
调用的流程图:

load C++ main 执行顺序
接下来我们再看一个有意思的现象,在main
函数中输出Hello, World!
,并且在main
函数二进制计算器中appreciate添加一个C++方法:
int main(int argc, const char * argv[]) { @autoreleasepool { SJLog(@"Hello, World!"); } return 0; } __attribute__ ((constructor)) void sjFunc() { printf("C++ 来了 %s \n", __func__); }
添加SJPerson
类,并实现loa苹果d
方法:
@implementation SJPerson + (void)load { SJLog(@"%s", __func__); } @end
运行程序,可以看到打印结果如下:

首先执行load
方法,然后执行Cappear++方法,最后才会走苹果7到main
函数里面。
研究方法执行顺序,首先我们看下load
方法是怎么调用:APP
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(); }
首先会prepare_load_methods架构图怎么制作
准备所有架构是什么意思的load
方法,
void prepare_load_methods(const headerType *mhdr) { size_t count, i; runtimeLock.assertLocked(); classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count); for (i = 0; i < count; i++) { schedule_class_load(remapClass(classlist[i])); } category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count); for (i = 0; i < count; i++) { category_t *cat = categorylist[i]; Class cls = remapClass(cat->cls); if (!cls) continue; // category for ignored weak-linked class if (cls->isSwiftStable()) { _objc_fatal("Swift class extensions and categories on Swift " "classes are not allowed to have +load methods"); } realizeClassWithoutSwift(cls, nil); ASSERT(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); } }
这里实现准备和调用类、分类的load
方法。
下一步C++方法为什么会调用呢?在C++方法打个断点,调苹果手机试打印函数调用栈:

到dyld
源码中查看doInitialization
函数调用:

这个位置我们很熟悉了吧,上面已经解释过了,也就是doInitialization
会调用我们写的C++方法。
在搞一个骚操作,我们在objc-os.mm
文件中,添加一个函数调用的三种方式C++方法,再执行程序:
__attribute__ ((constructor)) void sjFunc() { printf("objc C++ 来了 %s \n", __func__); }
看到执行的二进制转十六进制公式结果:

也就是说,在同一个镜像文件中,才会approach先执行load
方法,再执行C++方法。
doInitialization
加载所有镜像文件的初始化,并不包含工程的初始化。最后一个C++方法是在工程里面的。
执行完dyld
然后是如何走到我们工程里面函数调用关系图的main
函数呢?
在C++方法打断点,进入断点后显函数调用可以作为一个函数的形参示汇编:


读取寄存器:

汇编最后执行jump rax
,rax
寄存器存的就是我们工程的苹果8plusmain
函数。
评论(0)