首页 » 软件开发 » 揭秘iPhone上的App是怎么运行起来的(一)(加载函数缓存共享调用)

揭秘iPhone上的App是怎么运行起来的(一)(加载函数缓存共享调用)

雨夜梧桐 2024-07-24 16:29:52 0

扫一扫用手机浏览

文章目录 [+]

也就是内核读取完MachO的Header之后,就交给DYLD了。

load之前的函数调用栈

我们都知道,程序的入口是main()函数。
但其实在main()之前还是有很多步骤要做,执行完成这些操作才能进入到我们的程序中来。

我们知道load方法是在main()之前调用,因为通过断点查看main(),不能看到过多的信息,我们就断点一下load方法。

揭秘iPhone上的App是怎么运行起来的(一)(加载函数缓存共享调用) 软件开发
(图片来自网络侵删)

如下是断点出来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 arrayconst char envp = &argv[argc+1];// kernel sets up apple pointer to be just past end of envp arrayconst 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 dyldrunDyldInitializers(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 librariesif( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {for (const char const lib = sEnv.DYLD_INSERT_LIBRARIES; lib != NULL; ++lib) loadInsertedDylib(lib);}

以上就是DYLD如何配置相关环境,加载加载共享缓存,初始化MachO,加载插入共享缓存。
这些都是再为我们的主程序加载做准备,下一篇文章,将介绍如何加载我们的主程序的,这才是重点,请查看。

标签:

相关文章

语言中的借用,文化交融的桥梁

自古以来,人类社会的交流与发展离不开语言的传播。在漫长的历史长河中,各民族、各地区之间的文化相互碰撞、交融,产生了许多独特的语言现...

软件开发 2025-01-01 阅读1 评论0

机顶盒协议,守护数字生活的新卫士

随着科技的飞速发展,数字家庭逐渐走进千家万户。在这个时代,机顶盒成为了连接我们与丰富多彩的数字世界的重要桥梁。而机顶盒协议,作为保...

软件开发 2025-01-01 阅读1 评论0

语言基础在现代社会的重要性及方法步骤

语言是人类沟通的桥梁,是社会发展的基础。语言基础作为语言学习的基石,对于个人、社会乃至国家的发展具有重要意义。本文将从语言基础在现...

软件开发 2025-01-01 阅读2 评论0

粤语电影,传承文化,点亮时代之光

粤语电影,作为中国电影产业的一朵奇葩,以其独特的地域特色、丰富的文化内涵和鲜明的艺术风格,赢得了广大观众的喜爱。本文将从粤语电影的...

软件开发 2025-01-01 阅读3 评论0

苹果游戏语言,塑造未来娱乐体验的基石

随着科技的飞速发展,游戏产业逐渐成为全球娱乐市场的重要支柱。在我国,游戏产业更是蓬勃发展,吸引了无数玩家和投资者的目光。而在这其中...

软件开发 2025-01-01 阅读1 评论0