DYLD(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。
也就是内核读取完MachO的Header之后,就交给DYLD了。
load之前的函数调用栈
我们都知道,程序的入口是main()函数。但其实在main()之前还是有很多步骤要做,执行完成这些操作才能进入到我们的程序中来。
我们知道load方法是在main()之前调用,因为通过断点查看main(),不能看到过多的信息,我们就断点一下load方法。
如下是断点出来load之前的函数调用栈,其实这就是load之前,我们程序做的事情。
通过控制台 bt 命令同样可以查看堆栈信息:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1* frame #0: 0x000000010a676647 MachOTest`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:18 frame #1: 0x000000010af7978b libobjc.A.dylib`call_load_methods + 695 frame #2: 0x000000010af7a40a libobjc.A.dylib`load_images + 70 frame #3: 0x000000010a680cb7 dyld_sim`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 311 frame #4: 0x000000010a68cf88 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 322 frame #5: 0x000000010a68c15e dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 134 frame #6: 0x000000010a68c1f2 dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 74 frame #7: 0x000000010a681052 dyld_sim`dyld::initializeMainExecutable() + 196 frame #8: 0x000000010a684b6b dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4408 frame #9: 0x000000010a6803db dyld_sim`start_sim + 136 frame #10: 0x000000011603cded dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2200 frame #11: 0x000000011603a7a3 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 436 frame #12: 0x00000001160363d4 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 453 frame #13: 0x00000001160361d2 dyld`_dyld_start + 54
接下来,我们就是通过对DYLD的源码进行分析,查看程序加载的过程。
DYLD的 start 和 main 函数
从上面的函数调用栈可以看出,第一个调用的函数就来自 DYLD 的 _dyld_start 函数,接下来是 DYLD 的 main 函数。
start函数
从堆栈中看出,start函数来源于 DYLD 中的 dyldbootstrap,于是从中找到函数。
具体解释请查看下面代码块中的注释。
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); }/**上面的slide到这里,是生成一个了偏移,然后进行了重定向。*/ // allow dyld to use mach messaging// 消息初始化,OC消息初始化 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);/**最后调用了main函数*/ return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);}
main函数
start中初始化的工作完成之后就调用了下面的main函数,接下来我们查看main的作用。
main函数中做的事情特别多,首先回进行相关环境配置工作。
设置上下文 setContext(mainExecutableMH, argc, argv, envp, apple);配置进程 configureProcessRestrictions(mainExecutableMH);查看环境变量 checkEnvironmentVariables(envp);获取当前程序的架构getHostInfo(mainExecutableMH, mainExecutableSlide);
DYLD加载共享缓存
共享缓存是干嘛的呢?其实就是把系统的一些库,比如UIKit,CorFoundation等加载到一块地址空间。当多个应用都需要使用的时候从这里读取,不需要再加载一份了。比如今日头条,微信和支付宝都需要使用UIKit,那么在第一次加载过后,都从这里拿资源使用。
加载共享缓存是在 DYLD 的 main 函数中完成的,上面说了main函数环境相关配置过后,接下来就是加载共享缓存了。
第一步、检查共享缓存是否被禁用。
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
从实现中看出,iOS必须开启。
第二步、加载共享缓存。
这就是加载共享缓存的代码,具体解释看下面代码块。
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results){results->loadAddress = 0; results->slide = 0; results->errorMessage = nullptr;#if TARGET_IPHONE_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}
实例化MachO
上面看完了main函数初始化一些环境配置,加载了共享缓存过后,接下来DYLD就开始初始化我们的主程序MachO了。
第一步、MachO是DYLD加载的第一个程序。这句代码就是加载MachO的。
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
第二步、方法中调用这个方法,生成镜像后,作为第一个为加载的镜像保存起来。
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);addImage(image);
第三步、load Commands
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
第四步、实例化MachO
ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
加载插入动态库
焕然大悟:我们想一想,在之前的文章中有介绍注入Framework和dylib。程序能都执行我们注入的库,然而我们不可能修改原始MachO文件中的内容,其实让我们成功的原因就是这里,插入动态库。
DYLD会首先加载插入动态库,以保证插入的库能对MachO起到作用。
我们越狱就是修改这个值,需要修改这个值是需要root权限的,所以我们手机需要越狱后难道root权限。
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); }
以上就是DYLD如何配置相关环境,加载加载共享缓存,初始化MachO,加载插入共享缓存。这些都是再为我们的主程序加载做准备,下一篇文章,将介绍如何加载我们的主程序的,这才是重点,请查看。
网友评论