.o 文件mach-o_Mach-O可执行文件的一个奇怪案例

.o 文件mach-o

这将是一个低级的文章,但是我想您已经知道自从您登陆这里以来,对吗?

我想谈谈这个神秘的词Mach-O…

它是什么?
它是如何工作的?
真的是什么!?

要回答所有这些问题,我们必须深入挖掘并弄脏双手……

当我们使用Xcode构建应用程序时,很多事情会同时发生。 其中之一是将所有源代码转换为可执行文件。 该可执行文件包含将在CPU,iOS设备上的ARM处理器或Mac上的英特尔处理器上运行的字节码。

该可执行文件称为Mach-O。

好吧,这很简单又有趣,再见!

除非您想四处逛逛以了解内部构造😈

Mach-O格式

Mach-O是字节的二进制流,被分组为有意义的数据块。 这些块包含有关元数据的信息,例如字节顺序,CPU类型,块大小等。

有多种Mach-O类型,最典型的是您会看到这些

  • 可执行文件-主应用程序二进制文件,例如Example.app/Example
  • Dylib —动态库,如libSwiftCore.dylib

是的,我知道吧,潜伏在我们的鼻子底下!

因此,Mach-O文件分为几个部分,看起来像这样

但是在深入探讨细分市场之前,我们先来看一下其他内容

Mach-O标头

每个Mach-O文件都以定义文件结构的头结构开头。 它还包含有关文件类型和目标体系结构( armv7armv7si386等)的信息。

在标题结构的正下方是一堆加载命令,这些命令有助于文件的布局和链接。 另外,加载命令可以指定

文件在虚拟内存中的初始布局(我们将返回到此)

  • 部门名称和地址
  • 要加载的dylib
  • “主要”功能地址
  • 代码签名

而且,这就是完整标题的样子!

如您所见,Mach-O标头由一堆加载命令组成,这些命令定义了各节的地址,主函数以及要加载的从属二进制文件。

上面提到的地址实际上与加载Mach-O的内存地址有偏差。 这样做是因为,每次启动应用程序时,都会使用一种称为“ 自动空间布局随机化”的精巧技术或我们亲切地称为ASLR的启动技术来随机化起始内存地址

这意味着您的应用程序进程启动时,您不知道它将从哪个地址开始。

让我们想象一下它的含义,假设您有一个全局变量在RAM中占用了一些内存地址,但是由于您不知道进程从何处开始,因此您可能无法确定该全局变量的内存地址!

您可能已经猜到,这样做是出于安全目的,否则,如果每次启动时所有地址都相同,那么破解二进制文件将变得非常容易!

区隔

让我们看一下Mach-O文件的各个部分

__PAGEZERO

这是可执行文件的第一部分,内部没有数据,因此不占用文件空间。 该段充满零以捕获NULL指针取消引用。 您可能已经遇到了EXC_BAD_ACCESS崩溃,这恰恰是因为代码中的某些内容试图从此处访问数据,而这是不允许的。

顺便说一句,此段可能是隐藏恶意代码的好地方😉

__文本

该段包含可执行代码和只读数据。 将该段设置为只读,以便在将段映射到内存中时共享该段。 这主要用于框架,捆绑软件和共享库。

并且,由于__TEXT段是只读的,因此不需要将任何更改保存回磁盘。 如果内核需要释放内存,它将仅删除__TEXT页面并在需要时重新读取它们。

这就是iOS和OSX如此积极地缓存其动态库的原因。

__数据

该段包含可写数据(例如,全局变量,静态变量等),并且由于该段可写,因此逻辑上为与该库链接的每个进程复制框架或其他共享库的__DATA段。

如果您有使用Swift的经验,那么您必须熟悉复制 ,这实际上意味着在编辑引用的内容之前不要创建副本。 同样,在复制__DATA段时,直到某个进程对其进行修改后,该进程才真正收到该页的私有副本。

__OBJC

这是一个可选段,包含由Objective-C语言运行时支持库使用的数据。

__进口

这也是一个可选段,包含符号存根和指向可执行文件中未定义的符号的非惰性指针。 仅针对针对IA-32体系结构的可执行文件生成此段。

__LINKEDIT

此段包含链接器的原始数据( 链接e dito r ),例如符号和字符串表,压缩的动态链接信息,代码签名信息以及间接符号表-所有这些均占用装入命令指定的区域。

因此,现在我们已经了解了各个细分市场,让我们尝试着眼于全局,看一看如何将它们组合在一起。

全局图— DYLD

直到现在我们知道

  1. 如何生成Mach-O文件,以及使用其加载命令以各种方式链接依赖项。
  2. 加载命令用于映射内存命令中的段。
  3. LC_MAIN开始执行文件

好吧,这仅仅是信息,而该信息需要大脑进行处理。

这个大脑叫做Dyld!

让我告诉你一个秘密! 🤫
好吧,这不是一个秘密

当您通过点击应用程序图标启动应用程序时,而不是启动应用程序,内核会启动dyld

我知道,对吧! 这个家伙在这里很重要。

内核实际上将在某个随机地址空间加载dyld ,并且内核本身将具有自己的__TEXT段, __ DATA段……好了,您就知道了。

dyld的工作基本上是为我们加载和设置所有依赖的dylib。

加载dylib

dyld在这里读取Mach-O标头,以查找有关从属dylib的信息 。 然后,它在文件系统上找到该库文件并进行解析。

此过程是递归完成的,因为dylib A可以依赖dylib B,而dylib B可以依赖dylib C,因此它必须解析整个依赖关系图,最后将所有这些dylib的段映射到原始Mach-O标头。

而且,整个交易可能看起来像这样。

现在,请记住我们讨论过ASLR,以及如何不知道将哪个地址分配给应用程序中的所有变量。 dyld必须使用以下技术来解决此问题。

变基

__LINKEDIT部分包含所有需要移动的指针的位置。 Dyld将遍历所有这些指针,并根据您应用程序的起始地址对其进行移位。

请注意,要执行此操作,我们必须读取和写入数据页,从而导致这些页变脏,并且需要在写入时进行复制。 这就是为什么Rebasing在IO中非常昂贵的原因。

捆绑

对其他dylib函数的引用是通过绑定(例如NSLog,malloc等)进行固定的。

dyld加载依赖库后,需要搜索符号表并找到这些符号的实现。 因此,您的二进制文件中实际上有一个名为_NSLog的字符串_NSLog解析,而dyld将要做的就是查找符号表,并用从属库中这些函数的地址填充它。

这是计算复杂且昂贵的

对象运行时

所有Objc类定义都需要注册,为什么? 因为您可以从调用NSClassFromString(_:)方法的字符串构造Objc类。

因此,dyld必须先构建此表,然后才能启动应用程序。

在方法列表中添加类别-这意味着,如果您已经在UIView上创建了一个类别并添加了许多新函数,则这些新函数将被添加到UIView的方法列表中。

它还可以确保选择器是唯一的。

运行初始化程序

此时将调用Objc +load方法C ++静态初始化程序

这是以自下而上的方式发生的,因此基本上将首先初始化从属库。

哇! 完成所有这些操作之后,最后,您的main()将执行。

而且,这就是难以捉摸的Mach-O文件背后的故事。

摘要

  1. 二进制文件使用的Mach-O格式__TEXT,__DATA__LINKEDIT段。
  2. Dyld需要解析并加载所有动态库依赖项。
  3. Dyld需要修复内部和外部的所有指针(变基,绑定,设置运行时)。
  4. 运行静态初始化程序和+load方法。
  5. 然后,然后main()

然后去哪儿?

  1. 阴暗面-https: //lowlevelbits.org/parsing-mach-o-files/
  2. 如果您想更深入地学习并从dylib中提取一些代码,请查阅objc.io中的本文
  3. 甚至摩尔文档!!

先前发布在 https://medium.com/tokopedia-engineering/a-curious-case-of-mach-o-executable-26d5ecadd995

翻译自: https://hackernoon.com/a-curious-case-of-mach-o-executable-oc8l3v18

.o 文件mach-o

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值