如何分析调用html初始化调用js函数函数时dyld的图像加载的时间

&&409 阅读
原文出自【听云技术博客】:
最近看 ObjC的runtime 是怎么实现 +load 钩子函数的实现。进而引申分析了 dyld 处理 Mach-O 的这部分机制。
1.简单分析 Mach-O 在dyld 中是如何被加载到内存中的;
2.分析了 +load 的 特殊加载时机;
上图的调用栈告诉我们哪些函数被调用了。
dyld 是Apple 的动态链接器;在 xnu 内核为程序启动做好准备后,就会将 PC 控制权交给 dyld 负责剩下的工作 (dyld 是运行在 用户态的, 这里由 内核态 切到了用户态)。
每当有新的镜像加载之后,都会执行 load-images 方法进行回调,这里的回调是在整个ObjC runtime 初始化时 -objc-init 注册的 :
有新的镜像被 map 到 runtime 时,调用 load-images 方法,并传入最新镜像的信息列表 infoList:
这里的镜像就是 一些 System framework 的二进制。
进入 下图函数 load-images-nolock 查找 load 函数
调用 prepare-load-methods 对 load 方法的调用进行准备(将需要调用 load 方法的类添加到一个列表中)
调用 -getObjc2NonlazyClassList 获取所有的类的列表之后,会通过 remapClass 获取类对应的指针,然后调用 schedule-class-load 递归地 将当前类和没有调用 + load 父类进入列表。
在执行 add-class-to-loadable-list(cls) 将当前类加入加载列表之前,会先把父类加入待加载的列表,保证父类在子类前调用 load 方法。
在将镜像加载到运行时、对 load 方法的准备就绪,执行 call-load-methods,开始调用 load 方法:
其中 call-class-loads 会从一个待加载的类列表 loadable-classes 中寻找对应的类,然后找到 @selector(load) 的实现并执行。
分析到这里,已经能得知 load 函数是如何被调用的。
接下来分析 dyld 这部分怎么加载镜像的
1.1 数据结构
mach-o 文件头 操作。
1.2 ImageLoader
每一个加载的 Mach-O 文件都会存在这样一个ImageLoader 的 实例,上图可以看出 这里ImageLoader是一个抽象类,每一种具体的Mach-O 文件都会继承 ImageLoader类, 继承关系 如下图:
在加载时会根据Mach-O的格式不同选择生成不用的实例。
在调用-main 函数之后,做了一下几件事情:
选择运行环境(iOS 模拟器)
初始化数据、设置全局变量、上下文信息
检查文件是否Restricted
走完这些流程,就会调用 instantiateFromLoadedImage 函数,开始加载Mach-O 并且实例化 为 ImageLoader。
1.4 instantiateFromLoadedImage
这个函数做了三件事情:
检查Mach-O 文件是否合法
初始化 ImageLoader 实例
调用addImage 函数添加 初始化后的实例到管理模块中
1.5 isCompatibleMachO
Mach-O 文件的合法性检查:
mach-header 中的 cputype与当前运行的CPU 版本是否支持
mach-header 中的 subtype 在该CPU 架构下的所有版本都可以支持
cputype 就是CPU 平台, x86,ARM ,POWERPC 等, 而subtype 就是同一个平台下的不同版本, 例如:arm7,arm7.
1.6 ImageLoaderMachO: : instantiateMainExecutable
该函数主要通过 sniffLoadCommands 函数来判断 Mach-O 文件是否是压缩过的,然后分别 选择不同的 子类实例化。
1.7 sniffLoadCommands
这个函数主要做两件事情
判断Mach-O文件是classic的还是compressed的。
获取mach-O文件的segment的数量。
1.8 ImageLoaderMachOClassic: :instantiateMainExecutable
classic 与 compressed 的初始化大同小异,先分析Classic 的实现
可以看到加载的核心代码 还在 instantiateStart 函数中
1.9 instantiateStart
这里仍然没有出现加载的核心代码,只是根据之前获得的数据申请分配了内存,并计算 segments的 指针。 ImageLoaderMachOClassic 的构造函数才是加载 的核心逻辑。
2.0 ImageLoaderMachOClassic
根据Mach-O 文件 segments 将数据加载到 内存中, 任何返回 调用 addImage 函数。
2.1 addImage
这个函数只是做了数据更新
将image 添加到管理容器中
更新了内存分布的信息
整个加载过程基本分为三个步骤:
解析Mach-O文件头信息,将segments 的具体信息 构建到image 的实例中
添加image 到管理容器
根据 dyld的源代码的粗略分析, 更多信息需要分析 xnu 内核代码。
ObjC runtime 源代码
dyld 源代码
《Mac OSX and iOS Internals》
听云是国内领先的应用性能管理(APM)解决方案提供商,是入选Gartner APM魔力象限的唯一中国企业。拥有听云App、听云Network、听云Server、听云Sys、听云Browser五条重要产品线。从移动客户端到服务器端再到网络层面,全方位帮助客户实时监控定位从崩溃、报错、代码效率质量低下、交互过慢、第三方API调用失败、到网络环境出错等多维度复杂的性能问题。趣探 Mach-O:加载过程 - CSDN博客
趣探 Mach-O:加载过程
这是Mach-O系列的第二篇,是本文的一个基础
我们都知道&Mach-O是&OS
X&系统的可执行文件,说到可执行文件肯定离不开进程。在&Linux&中,我们会通过&Fork()来新创建子进程,然后执行镜像通过exec()来替换为另一个可执行程序,至于为什么这么做,解释如下
原因阐述:这是基于操作系统方面的分析。进程可以通过fork()系统调用来创建子进程,子进程得到与父进程地址空间相同的(但是独立的)一份拷贝,包括文本、数据和bss段、堆以及用户栈等,但是新线程只会复制调用fork的线程。所有父进程中别的线程,到了子进程中都是突然“蒸发”掉的。
我们在线程问题中经常会提到锁,每个锁都有一个持有者(最后一次lock它的线程)。为了性能,锁对象会因为fork复制到子进程中,但是子进程只复制调用fork的线程,很可能并不拥有锁持有者线程,那么就没有办法解开锁,导致死锁问题、内存泄漏
避免死锁的方法:在子线程中马上调用exec函数,一个进程一旦调用exec类函数,它本身就”死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。
综上所述,我们在用户态会通过exec*系列函数来加载一个可执行文件,同时exec*都只是对系统调用execve的封装,那我们加载Mach-O的流程,就从execve说起。Mach-O有多种文件类型,比如MH_DYLIB文件、MH_BUNDLE文件、MH_EXECUTE文件(这些需要dyld动态加载),MH_OBJECT(内核加载)等。所以一个进程往往不是只需要内核加载器就可以完成加载的,需要dyld来进行动态加载配合。考虑内核加载和dyld加载两种情况,就会有如下流程图
这个函数只是直接调用&__mac_execve(),对于源码内部实现细节,可以看
__mac_execve()
源码可以参考:bsd/kern/kern_exec.c
主要是为加载镜像进行数据的初始化,以及资源相关的操作,在其内部会执行exec_activate_image(),镜像加载的工作都是由它完成的
123456789101112int__mac_execve(proc_t p, struct __mac_execve_args *uap, int32_t *retval){&&&&&struct image_params *imgp;&&&&&// 初始化imgp数据&&&&.......&&&&&exec_activate_image(imgp);&}exec_activate_image源码可以参考:bsd/kern/kern_exec.c主要是拷贝可执行文件到内存中,并根据不同的可执行文件类型选择不同的加载函数,所有的镜像的加载要么终止在一个错误上,要么最终完成加载镜像。在OS X中专门处理可执行文件格式的程序叫execsw镜像加载器OS X有三种可执行文件,mach-o由exec_mach_imgact处理,fat binary由exec_fat_imgact处理,interpreter(解释器)由exec_shell_imgact处理exec_mach_imgact源码可以参考:bsd/kern/kern_exec.c主要是用来对Mach-O做检测,会检测Mach-O头部,解析其架构、检查imgp等内容,并拒绝接受Dylib和Bundle这样的文件,这些文件会由dyld负责加载然后把Mach-O映射到内存中去,调用load_machfile()load_machfile源码可以参考:bsd/kern/mach_loader.cload_machfile会加载Mach-O中的各种load monmand命令。在其内部会禁止数据段执行,防止溢出漏洞攻击,还会设置地址空间布局随机化(ASLR),还有一些映射的调整。真正负责对加载命令解析的是parse_machfile()parse_machfile源码可以参考:bsd/kern/mach_loader.cparse_machfile会根据load_command的种类选择不同的函数来加载,内部是一个Switch语句来实现的常见的命令有LC_SEGMENT_64、LC_LOAD_DYLINKER、LC_CODE_SIGNATURE、LC_UUID等,更多命令可以查看Mach-O文件格式,也可以查询这篇文章-对于命令的加载会进行多次扫描,当扫描三次之后,并且存在dylinker_command命令时,会执行&load_dylinker(),启动动态链接器 (dyld)动态链接过程动态链接也有区分,一种是加载主程序(很多博客里这么写),是由load commands指定的dylib,以静态的方式存放在二进制文件里,一种是由DYLD_INSERT_LIBRARIES动态指定。下面这种就是提前指定在二进制文件中的动态库,下面的阐述主要是站在前者的角度,对于动态指定的后期再研究你可以在&load&方法处打一个断点看一下,通过查看调用栈可以发现:
0&&+[XXObject
1&&call_class_loads()
2&&call_load_methods
3&&load_images
4&&dyld::notifySingle(dyld_image_states,
ImageLoader
_dyld_start
_dyld_start甚是耀眼,觉得是dyld的入口,然后就去看dyld的,全局搜了一下_dyld_start,就发现注释,于是顺着注释往下阅读
源码可以参考:dyld/src/dyld.cpp
内核会加载dyld并调用dyld_start方法,随后dyld_start会调用_main(),在_main函数中对数据进行一通初始化之后,就会调用instantiateFromLoadedImage函数初始化ImageLoader实例
12// instantiate ImageLoader for main executablesMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);instantiateFromLoadedImage
The kernel maps in main executable before dyld gets control.&&We need to
make an ImageLoader* for the already mapped in main executable.
ImageLoader*
instantiateFromLoadedImage(const
macho_header*
try mach-o loader
isCompatibleMachO((const
uint8_t*)mh,
&&&&&&&&ImageLoader*
ImageLoaderMachO::instantiateMainExecutable(mh,
gLinkContext);
&&&&&&&&addImage(image);
&&&&&&&&return
&main executable not a
known format&;
instantiateFromLoadedImage这个函数内的代码比较容易理解,检测Mach-O是否合法,合法的话就初始化ImageLoader实例,然后将其加入到一个全局的管理ImageLoader的数组中去
isCompatibleMachO会对Mach-O头部的一些信息与当前平台进行比较,判断其合法性。
ImageLoader
12345678910111213//// ImageLoader is an abstract base class.&&To support loading a particular executable// file format, you make a concrete subclass of ImageLoader.//// For each executable file (dynamic shared object) in use, an ImageLoader is instantiated.//// The ImageLoader base class does the work of linking together images, but it knows nothing// about any particular file format.////class ImageLoader {&}注释可得,ImageLoader是一个抽象基类,每一个动态加载的可执行文件都会初始化一个ImageLoader实例instantiateMainExecutable源码可以参考:dyld/src/ImageLoaderMachO.cpp
create image for main executable
ImageLoader*
ImageLoaderMachO::instantiateMainExecutable(const
macho_header*
LinkContext&
compressed;
&&&&unsigned
&&&&unsigned
linkedit_data_command*
codeSigCmd;
encryption_info_command*
encryptCmd;
&&&&sniffLoadCommands(mh,
&compressed,
&segCount,
&libCount,
&codeSigCmd,
&encryptCmd);
instantiate concrete class based on content of load commands
compressed
&&&&&&&&return
ImageLoaderMachOCompressed::instantiateMainExecutable(mh,
#if SUPPORT_CLASSIC_MACHO
&&&&&&&&return
ImageLoaderMachOClassic::instantiateMainExecutable(mh,
&&&&&&&&throw
&missing LC_DYLD_INFO load
可以这里会有一个Bool类型的compressed作为判断,然后返回不同的实例。
这两种实例都是做什么?
ImageLoaderMachOCompressed与ImageLoaderMachOClassic均继承于ImageLoaderMachO,ImageLoaderMachO&继承于ImageLoader
sniffLoadCommands会对Mach-O是classic还是compressed的做一个判断
instantiateMainExecutable是对ImageLoaderMachOCompressed或ImageLoaderMachOClassic做初始化,并加载load
comond命令,之中调用过程也比较简单,可以参考网络的这个图片,比较清晰,具体内容可以看源代码
至此,主二进制文件的加载就结束,然后会链接各种DYLD_INSERT_LIBRARIES动态库,以上就是非常粗略的了解了下加载Mach-O的过程,后期有需要再深入研究
写的比较仓促,难免有错误的地方,望包涵并告知
本文已收录于以下专栏:
相关文章推荐
X安全方面的知识需要对mach-o加载的流程需要有一个比较完整的理解,断断续续一个月的时间里面,通过对源码的阅读对mach-o的加载有一个比较基本的认识,在遇到各个具...
之前深入了解过,过去了一年多的时间。现在花些时间好好总结下,毕竟好记性不如烂笔头。其次还有一个目的,对于mach-o文件结构,关于动态加载信息那个数据区中,命令含义没有深刻掰扯清除,希望有同学能...
在Mac的开发中, 有没有想过当我们点击可执行文件之后,Mac究竟做了什么事情才让我们的程序运行起来? 对于应用层开发人员或者普通的用户而言, 其实无需知道的这么详细;但是对于内核开发人员而言, 如果...
以最简单的Hello world为例
编译Hello world
1.首先找到编译器:
arm-apple-darwin10-llvm-gcc-4.2 就是了。
为了方便起...
真心觉得这哥们写的不错!受用了
文章出处:/ioss-mach-o.html
我们知道...
Mach-O为Mach Object文件格式的缩写,它是一种用于可执行文件,目标代码,动态库,内核转储的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性,并提升了符号表中信息的访问速...
近来在学习osx和ios方面的东西,简单熟悉了下oc的语法后打算学习下osx和ios下的文件格式。
--------------------------------------------------...
原文链接: Daniel
翻译: 伯乐在线 - 一水流年
译文链接: /51527/
当我们在Xcode中构建一...
Mach-O为Mach
Object文件格式的缩写,它是一种用于可执行文件,目标代码,动态库,内核转储的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性,并提升了符号表中信息的...
IOS安全–了解Mach-o文件结构
AloneMonkey 日 0
我们知道Windows下的文件都是PE文件,同样在OS X和iOS中可执行文件是Mach-o格式...
他的最新文章
讲师:钟钦成
讲师:宋宝华
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)一个 iOS App 的&main&函数位于 main.m 中,这是我们熟知的程序入口。但对
objc 了解更多之后发现,程序在进入我们的 main 函数前已经执行了很多代码,比如熟知的&+ load&方法等。本文将跟随程序执行顺序,刨根问底,从&dyld&到&runtime&,看看
main 函数之前都发生了什么。
从dyld开始
动态链接库
iOS 中用到的所有系统 framework 都是动态链接的,类比成插头和插排,静态链接的代码在编译后的静态链接过程就将插头和插排一个个插好,运行时直接执行二进制文件;而动态链接需要在程序启动时去完成“插插销”的过程,所以在我们写的代码执行前,动态连接器需要完成准备工作。
这个是在 Xcode 中看到的 Link 列表:
这些 framework 将会在动态链接过程中被加载,另外还有隐含 link 的 framework,可以测试出来:先找到可执行文件,我这里叫 TestMain 的工程,模拟器路径下找到 TestMain.app,可执行文件默认同名,再通过&otool命令:
$ otool -L TestMain
-L&参数打印出所有 link 的 framework(去掉了版本信息如下)
/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
/System/Library/Frameworks/UIKit.framework/UIKit
/System/Library/Frameworks/Foundation.framework/Foundation
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.dylib
除了多了的CoreGraphics(被 UIKit 依赖)外,有两个默认添加的 lib:libobjc&即
objc 和 runtime,libSystem&中包含了很多系统级别 lib,列几个熟知的:
libdispatch ( GCD )libsystem_c ( C语言库 )libsystem_blocks ( Block )libcommonCrypto ( 加密库,比如常用的 md5 函数 )
这些 lib 都是dylib格式(如 windows 中的 dll ),系统使用动态链接有几点好处:
代码共用:很多程序都动态链接了这些 lib,但它们在内存和磁盘中中只有一份易于维护:由于被依赖的 lib 是程序执行时才 link 的,所以这些 lib 很容易做更新,比如libSystem.dylib&是&libSystem.B.dylib&的替身,哪天想升级直接换成&libSystem.C.dylib然后再替换替身就行了减少可执行文件体积:相比静态链接,动态链接在编译时不需要打进去,所以可执行文件的体积要小很多
dyld(the dynamic link editor),Apple 的动态链接器,系统 kernel 做好启动程序的初始准备后,交给 dyld 负责,援引并翻译对 dyld 作用顺序的概括:
从 kernel 留下的原始调用栈引导和启动自己将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制non-lazy 符号立即 link 到可执行文件,lazy 的存表里Runs static initializers for the executable找到可执行文件的 main 函数,准备参数并调用程序执行中负责绑定 lazy 符号、提供 runtime dynamic loading services、提供调试器接口程序main函数 return 后执行 static terminator某些场景下 main 函数结束后调 libSystem 的&_exit&函数
得益于 dyld 是开源的,,我们可以从源码一探究竟。
一切源于dyldStartup.s这个文件,其中用汇编实现了名为__dyld_start的方法,汇编太生涩,它主要干了两件事:
调用dyldbootstrap::start()方法(省去参数)上个方法返回了 main 函数地址,填入参数并调用 main 函数
这个步骤随手就能验证出来,设置一个符号断点断在_objc_init:
这个函数是runtime的初始化函数,后面会提到。程序运行在很早的时候断住,这时候看调用栈:
看到了栈底的dyldbootstrap::start()方法,继而调用了dyld::_main()方法,其中完成了刚才说的递归加载动态库过程,由于libSystem默认引入,栈中出现了libSystem_initializer的初始化方法。
ImageLoader
当然这个&image&不是图片的意思,它大概表示一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等,所以 ImageLoader 作用是将这些文件加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。
在程序运行时它先将动态链接的 image 递归加载 (也就是上面测试栈中一串的递归调用的时刻)再从可执行文件 image 递归加载所有符号
当然所有这些都发生在我们真正的main函数执行前。
刚才讲到&libSystem&是若干个系统 lib 的集合,所以它只是一个容器 lib 而已,而且它也是开源的,里面实质上就一个文件,,由&libSystem_initializer&逐步调用到了&_objc_init,这里就是
objc 和 runtime 的初始化入口。
除了 runtime 环境的初始化外,_objc_init中绑定了新 image 被加载后的 callback:
dyld_register_image_state_change_handler(
dyld_image_state_bound, 1, &map_images);
dyld_register_image_state_change_handler(
dyld_image_state_dependents_initialized, 0, &load_images);
可见 dyld 担当了&runtime&和&ImageLoader&中间的协调者,当新
image 加载进来后交由 runtime 大厨去解析这个二进制文件的符号表和代码。继续上面的断点法,断住神秘的&+load&函数:
清楚的看到整个调用栈和顺序:
dyld 开始将程序二进制文件初始化交由 ImageLoader 读取 image,其中包含了我们的类、方法等各种符号由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class 的 +load 方法和其 Category 的 +load 方法
至此,可执行文件中和动态库所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被 runtime 所管理,再这之后,runtime 的那些方法(动态添加 Class、swizzle 等等才能生效)
关于 +load 方法的几个 QA
Q: 重载自己 Class 的 +load 方法时需不需要调父类?
A: runtime 负责按继承顺序递归调用,所以我们不能调 super
Q: 在自己 Class 的 +load 方法时能不能替换系统 framework(比如 UIKit)中的某个类的方法实现
A: 可以,因为动态链接过程中,所有依赖库的类是先于自己的类加载的
Q: 重载 +load 时需要手动添加 @autoreleasepool 么?
A: 不需要,在 runtime 调用 +load 方法前后是加了&objc_autoreleasePoolPush()&和objc_autoreleasePoolPop()&的。
Q: 想让一个类的 +load 方法被调用是否需要在某个地方 import 这个文件
A: 不需要,只要这个类的符号被编译到最后的可执行文件中,+load 方法就会被调用(Reveal SDK 就是利用这一点,只要引入到工程中就能工作)
整个事件由 dyld 主导,完成运行环境的初始化后,配合 ImageLoader 将二进制文件按格式加载到内存,
动态链接依赖库,并由 runtime 负责加载成 objc 定义的结构,所有初始化工作结束后,dyld 调用真正的 main 函数。
值得说明的是,这个过程远比写出来的要复杂,这里只提到了 runtime 这个分支,还有像&GCD、XPC等重头的系统库初始化分支没有提及(当然,有缓存机制在,它们也不会玩命初始化),总结起来就是
main 函数执行之前,系统做了茫茫多的加载和初始化工作,但都被很好的隐藏了,我们无需关心。
当这一切都结束时,dyld 会清理现场,将调用栈回归,只剩下:
孤独的 main 函数,看上去是程序的开始,确是一段精彩的终结
References
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:545447次
积分:6517
积分:6517
排名:第4093名
原创:406篇
转载:426篇
评论:50条
(8)(15)(6)(23)(35)(58)(41)(22)(20)(5)(5)(39)(9)(6)(249)(117)(66)(13)(42)(2)(1)(6)(19)(6)(19)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'iOS程序main函数之前发生了什么 - CSDN博客
iOS程序main函数之前发生了什么
一个iOS app的main()函数位于main.m中,这是我们熟知的程序入口。但对objc了解更多之后发现,程序在进入我们的main函数前已经执行了很多代码,比如熟知的+ load方法等。本文将跟随程序执行顺序,刨根问底,从dyld到runtime,看看main函数之前都发生了什么。&
从dyld开始&
动态链接库&
iOS中用到的所有系统framework都是动态链接的,类比成插头和插排,静态链接的代码在编译后的静态链接过程就将插头和插排一个个插好,运行时直接执行二进制文件;而动态链接需要在程序启动时去完成“插插销”的过程,所以在我们写的代码执行前,动态连接器需要完成准备工作。&
这个是在xcode中看到的Link列表:
这些framework将会在动态链接过程中被加载,另外还有隐含link的framework,可以测试出来:先找到可执行文件,我这里叫TestMain的工程,模拟器路径下找到TestMain.app,可执行文件默认同名,再通过otool命令:&
$ otool -L TestMain
-L参数打印出所有link的framework(去掉了版本信息):&
TestMain: /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
/System/Library/Frameworks/UIKit.framework/UIKit
/System/Library/Frameworks/Foundation.framework/Foundation
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
/usr/lib/libobjc.A.dylib /usr/lib/libSystem.dylib
除了多了的CoreGraphics(被UIKit依赖)外,有两个默认添加的lib。libobjc即objc和runtime,libSystem中包含了很多系统级别lib,列几个熟知的:libdispatch(GCD),libsystem_c(C语言库),libsystem_blocks(Block),libcommonCrypto(常用的md5函数)等等。这些lib都是dylib格式(如windows中的dll),系统使用动态链接有几点好处:&
代码共用:很多程序都动态链接了这些lib,但它们在内存和磁盘中中只有一份&易于维护:由于被依赖的lib是程序执行时才link的,所以这些lib很容易做更新,比如libSystem.dylib是libSystem.B.dylib的替身,哪天想升级直接换成libSystem.C.dylib然后再替换替身就行了&减少可执行文件体积:相比静态链接,可执行文件的体积要小很多&
dyld&- the dynamic link editor(这缩写对应的很奇怪,我感觉是DYnamic Linker Daemon呢- -?)apple的动态链接器,系统kernel做好启动程序的初始准备后,交给dyld负责,援引并翻译对dyld作用顺序的概括:&
从kernel留下的原始调用栈引导和启动自己&将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制&non-lazy符号立即link到可执行文件,lazy的存表里&Runs static initializers for the executable&找到可执行文件的main函数,准备参数并调用&程序执行中负责绑定lazy符号、提供runtime dynamic loading services、提供调试器接口&程序main函数return后执行static terminator&某些场景下main函数结束后调libSystem的_exit函数&
得益于dyld是开源的,,我们可以从源码一探究竟。&
一切源于dyldStartup.s这个文件,其中用汇编实现了名为__dyld_start的方法,汇编太生涩,它主要干了两件事:&
调用dyldbootstrap::start()方法(省去参数)&上个方法返回了main函数地址,填入参数并调用main函数&
这个步骤随手就能验证出来,设置一个符号断点断在_objc_init:
这个函数是runtime的初始化函数,后面会提到。程序运行在很早的时候断住,这时候看调用栈:
看到了栈底的dyldbootstrap::start()方法,继而调用了dyld::_main()方法,其中完成了刚才说的递归加载动态库过程,由于libSystem默认引入,栈中出现了libSystem_initializer的初始化方法。&
ImageLoader&
当然这个image不是图片的意思,它大概表示一个二进制文件(可执行文件或so文件),里面是被编译过的符号、代码等,所以ImageLoader作用是将这些文件加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。
在程序运行时它先将动态链接的image递归加载 (也就是上面测试栈中一串的递归调用的时刻)&再从可执行文件image递归加载所有符号&
当然所有这些都发生在我们真正的main函数执行前。&
runtime与+load&
刚才讲到libSystem是若干个系统lib的集合,所以它只是一个容器lib而已,而且它也是开源的,里面实质上就一个文件,,细节不说了,由libSystem_initializer逐步调用到了_objc_init,这里就是objc和runtime的初始化入口。&
除了runtime环境的初始化外,_objc_init中绑定了新image被加载后的callback:&
dyld_register_image_state_change_handler(dyld_image_state_bound, 1/*batch*/, &map_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
可见dyld担当了runtime和ImageLoader中间的协调者,当新image加载进来后交由runtime大厨去解析这个二进制文件的符号表和代码。继续上面的断点法,断住神秘的+load函数:&
清楚的看到整个调用栈和顺序:&
dyld开始将程序二进制文件初始化&交由ImageLoader读取image,其中包含了我们的类、方法等各种符号&由于runtime向dyld绑定了回调,当image加载到内存后,dyld会通知runtime进行处理&runtime接手后调用map_images做解析和处理,接下来load_images中调用call_load_methods方法,遍历所有加载进来的Class,按继承层级依次调用Class的load方法和其Category的load方法&
至此,可执行文件中和动态库所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被runtime所管理,再这之后,runtime的那些方法(动态添加Class、方法混合等等才能生效)&
关于load方法的几个QA&
Q: 重载自己Class的load方法时需不需要调父类?
A: runtime负责按继承顺序递归调用,所以我们不能调super&
Q: 在自己Class的load方法时能不能替换系统framework(比如UIKit)中的某个类的方法实现
A: 可以,因为动态链接过程中,所有依赖库的类是先于自己的类加载的&
Q: 重载load时需要手动添加@autoreleasepool么?
A: 不需要,在runtime调用load方法前后是加了objc_autoreleasePoolPush()和objc_autoreleasePoolPop()的。&
Q: 想让一个类的load方法被调用是否需要在某个地方import这个文件
A: 不需要,只要这个类的符号被编译到最后的可执行文件中,load方法就会被调用(Reveal SDK就是利用这一点,只要引入到工程中就能工作)&
整个事件由dyld主导,完成运行环境的初始化后,配合ImageLoader将二进制文件按格式加载到内存,
动态链接依赖库,并由runtime负责加载成objc定义的结构,所有初始化工作结束后,dyld调用真正的main函数。
值得说明的是,这个过程远比写出来的要复杂,这里只提到了runtime这个分支,还有像GCD、XPC等重头的系统库初始化分支没有提及(当然,有缓存机制在,它们也不会玩命初始化),总结起来就是main函数执行之前,系统做了茫茫多的加载和初始化工作,但都被很好的隐藏了,我们无需关心。&
孤独的main函数&
当这一切都结束时,dyld会清理现场,将调用栈回归,只剩下:
孤独的main函数,看上去是程序的开始,确是一段精彩的终结
本文已收录于以下专栏:
相关文章推荐
一个 iOS App 的 main 函数位于 main.m 中,这是我们熟知的程序入口。但对
objc 了解更多之后发现,程序在进入我们的 main 函数前...
【出差外地校招,有位面试的同学是这位博主的粉丝,给我推荐了他的博客】//objc-pre-main/
首先申明,此main 函数,特指C语言所编译得到的可执行文件入口函数。
其实很多人在编程实践中都不免和main函数之前执行的代码相关概念打交道,诸如命令行参数,当前工作目录...
http://blog.csdn.net/norains/article/details/6052029
main函数之前究竟发生了什么?
1 load部分
1.1 调用堆栈
frame #0: MyApp`+[XXX load]
frame #1: libobjc.A.dylib`call_load_methods + ...
我们假定本书所用的计算机是基于 IA—32 系列 CPU, 安装了标准单色显示器、 标准键 盘、一个软驱、一块硬盘、16 MB 内存,在内存中开辟了 2 MB 内存作为虚拟盘,并在 BIOS 中设置软...
main函数之前执行代码
有的时候,需要会遇到这样的一个情况,“想要在main或者WinMain函数执行前初始化我们的系统框架,和在之后释放我们的系统框架”, 如果这样,我们该怎么做呢?笔者今天放...
谁调用了我的 main?
      
      现在最重要的是要跟得上潮流,所以套用比较时髦的话,谁动了我的奶酪。谁调用了我的 main?不过作为计算机工作者,我劝大家还是不要赶时髦,今天Ja...
1、可以定义一个全局对象,这时会调用该类的构造函数。
他的最新文章
讲师:钟钦成
讲师:宋宝华
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)

我要回帖

更多关于 html初始化调用js函数 的文章

 

随机推荐