深入iOS系统底层之映像文件操作API介绍

本文深入介绍了iOS系统中mach-o文件的映像(image)概念,包括程序加载到内存的映像与文件的关系,以及Slide机制的原因和作用。文章详细讲解了Segment和Section的结构,强调了Slide值在处理地址重定向中的关键角色。此外,还列举了一系列用于操作进程映像的API,如获取映像数量、Slide值、映像名称等,这些API可用于动态库版本检测、线程结束回调和方法交换检测等场景。
摘要由CSDN通过智能技术生成

绿树阴浓夏日长,楼台倒影入池塘。–《唐高骈·山亭夏日》

mach-o文件和进程的映像(image)

iOS系统生成的可执行程序或者动态库文件的存储布局格式被称之为mach-o格式。文件中存放着程序的代码和数据,而程序运行时系统会为其建立一个进程,以及分配虚拟内存空间。同时会把程序文件中的内容加载到虚拟内存地址空间中去,这种加载的方法一般采用内存映射文件的技术来实现。所谓的映像可以理解为将一个程序文件的内容加载到进程虚拟内存中的内容,也就是说进程的映像就是程序磁盘文件在内存中的一个副本。 一般来说一个进程中映像的内容和内存布局结构会和程序文件的内容以及存储布局结构一致,映像的首地址是一个struct mach_header的结构体指针。映像中内容的排列布局和程序文件都是以段(Segment)为单位进行排列的。但是有一些情况映像的内存布局和内容可能会和程序文件的内存布局和内容不一致:

  1. 映像中的数据段部分,因为数据段部分大多是可以被读写访问的,也就是说可以在运行时被修改,或者某些信息会进行rebase处理。因此数据段不能被进程之间共享,而是每个进程单独维护一份。当然为了效率和性能系统会采用一种称之为Copy on write的技术来实现单独副本的拷贝的。通常只有不可变的代码段部分才会是内存和文件中的内容保持一致,并且多进程共享。一个很常见的例子就是进程中加载的动态库和框架中的代码段部分通常都是所有进程共享。

  2. 即使是代码段也有可能映像中的内容和程序文件中的内容不一致。有一些映像中的某些段的内容会是系统中缓存的段,而不是程序文件对应的段。一个很有代表性的例子就是CoreLocation这个库,当这个库被加载时你就会发现其映像中的有一些代码段的内容其实是系统缓存的内容而不是程序文件中的内容。

所以说程序文件和程序被加载后在内存中映像之间并不是一一对应的。程序文件和映像之间的关系就如程序和进程之间的关系是一样的。在程序运行后对其在进程中所有的mach-o数据结构的访问都是基于映像而不是基于程序文件的。

Slide机制

构建一个程序时为了方便计算和处理会为这个程序设定一个默认在内存中加载的基地址。这样在程序中所有涉及到地址存储的代码中的地址变量都是以这个基地址为标准的。比如我们在代码中有变量保存一个函数的地址或者在rumtime中的OC类的方法结构体:struct method_t中的imp保存的函数的地址等等。正常情况下如果我们的程序加载时也是按照程序中指定的基地址加载到虚拟内存中对应的地址时则一切都正常而且也不需要做任何的改变。但实际情况则不同:

  1. 任何一个库或者可执行程序在构建时都会指定一个加载的基地址,但是却无法保证这个基地址的唯一性。和无法保证程序映像的地址区间不产生重叠。因此有可能出现多个库加载到内存时的重叠覆盖的情况。

  2. iOS系统为保证的应用安全采用了一种称之为**ASLR(Address space layout randomization)**的技术。这种技术会使得每个程序或者库每次运行加载到内存中时的基地址都不是固定而是随机的,这种机制会增加黑客的破解难度。

上面的两种情况表明一个程序或者库加载到内存时的真实的基地址和程序构建时指定的基地址是不一样的。系统会为可执行程序和每个库选择不重叠的区域进行加载。但是这样就会出现在程序中所有以构建时基地址为标准的那些地址指针出现访问异常,因为这些地址值并不是真实在内存中的地址值。

为了解决这个问题系统会在构建的程序或库中添加一个特殊的load command命令:LC_DYLD_INFO或者LC_DYLD_INFO_ONLY。这部分信息用来记录所有需要进行地址调整的位置。这样当程序被加载到内存时,加载器就会将需要调整的地址分别进行调整处理,以便转化为真实的内存地址。这个过程称之为基地址重定向(rebase)。

假设程序构建时指定的基地址为A,程序中某处保存的一个函数指针地址为x,而程序被加载到内存时的真实基地址为B。也就是说真实的基地址和构建时的基地址的偏移差就是B-A。我们称这个偏移差值为Slide值。因此真实的地址x被调整后应该是: x + (B – A)了。

一个程序在构建时的基地址值可以在程序的第一个名为__TEXT的代码段描述结构体struct segment_command中的vmaddr数据成员中获取,而程序被加载后的得到的映像的mach-o头部结构体struct mach_header指针则是映像被加载的真实的基地址,因此:

映像的Slide值 = 映像的mach_header结构体指针 –  映像的第一个__TEXT代码段描述结构体struct segmeng_command中的vmaddr数据成员的值。

当然系统也提供了接口API来获取可执行程序或者库的映像的Slide值。这个将会在下面介绍。

段(Segment)和节(Section)

mach-o文件由诸多的load command组成,每个load command所代表的是一种数据类型。比如有的load command是用来存放程序代码和全局变量数据,有的load command是用来存放符号表,有的load command是用来存放代码签名信息等。每种load command都是结构体struct load_command的扩展结构体。其中的cmd字段用来描述这种load command的类型。

类型为LC_SEGMENT或者为LC_SEGMENT_64的load command被称之为段(Segment)。一个可执行程序中的代码和全局变量数据都保存在段中。描述段的信息是一个struct segment_command结构体。一个程序中可以存在着很多的段,每个段有一个唯一的段名(segment name)。比如一个可执行程序中所有的代码都保存在名字为:__TEXT的代码段中,而所有的数据都保存在名字为:__DATA的数据段中。段以页为边界进行对齐。

每个段则由多个节(Section)组成。节是内容分类的最小管理单元。每个节的描述信息是一个称之为:struct section的结构体。每个节有一个唯一的名称用来标识这个节。比如代码段中有一个名为:__text的节用来保存程序中用户编写的源代码对应的机器指令,而一个名为:__stub_helper的节则保存所有调用的外部函数的桩代码。下面的一张图展示的就是程序中的段和节的结构布局:

image.png

进程映像(Image)操作API

对映像进行操作的API都在中声明。你可以import这个头文件来使用里面

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值