动态修改 C 语言函数的实现

463 查看

关注仓库,及时获得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

Objective-C 作为基于 Runtime 的语言,它有非常强大的动态特性,可以在运行期间自省、进行方法调剂、为类增加属性、修改消息转发链路,在代码运行期间通过 Runtime 几乎可以修改 Objecitve-C 层的一切类、方法以及属性。

真正绝对意义上的动态语言或者静态语言是不存在的。

C 语言往往会给我们留下不可修改的这一印象;在之前的几年时间里,笔者确实也是这么认为的,然而最近接触到的 fishhook 使我对 C 语言的不可修改有了更加深刻的理解。

在文章中涉及到一个比较重要的概念,就是镜像(image);在 Mach-O 文件系统中,所有的可执行文件、dylib 以及 Bundle 都是镜像。

fishhook 简介

到这里,我们该简单介绍一下今天分享的 fishhook;fishhook 是一个由 facebook 开源的第三方框架,其主要作用就是动态修改 C 语言函数实现

这个框架的代码其实非常的简单,只包含两个文件:fishhook.c 以及 fishhook.h;两个文件所有的代码加起来也不超过 300 行。

不过它的实现原理是非常有意思并且精妙的,我们可以从 fishhook 提供的接口中入手。

从接口开始

fishhook 提供非常简单的两个接口以及一个结构体:

其中 rebind_symbols 接收一个 rebindings 数组,也就是重新绑定信息,还有就是 rebindings_nel,也就是 rebindings 的个数。

使用 fishhook 修改 C 函数

使用 fishhook 修改 C 函数很容易,我们使用它提供的几个范例来介绍它的使用方法。

这里要修改的是底层的 open 函数的实现,首先在工程中引入 fishhook.h 头文件,然后声明一个与原函数签名相同的函数指针:

然后重新实现 new_open 函数:

这里调用的 original_open 其实相当于执行原 open;最后,在 main 函数中使用 rebind_symbols 对符号进行重绑定:

在对符号进行重绑定之后,所有调用 open 函数的地方实际上都会执行 new_open 的实现,也就完成了对 open 的修改。

111975281-4ffe232e9590086c
fishhook-result

程序运行之后打印了 Calling real open('/Users/apple/Library/Developer/Xcode/DerivedData/Demo-cdnoozusghmqtubdnbzedzdwaagp/Build/Products/Debug/Demo', 0) 说明我们的对 open 函数的修改达到了预期的效果。

整个 main.m 文件中的代码在文章的最后面 main.m

fishhook 的原理以及实现

在介绍 fishhook 具体实现原理之前,有几个非常重要的知识需要我们了解,那就是 dyld、动态链接以及 Mach-O 文件系统。

dyld 与动态链接

dyld 是 the dynamic link editor 的缩写(笔者并不知道为什么要这么缩写)。至于它的作用,简单一点说,就是负责将各种各样程序需要的镜像加载到程序运行的内存空间中,这个过程发生的时间非常早 — 在 objc 运行时初始化之前

在 dyld 加载镜像时,会执行注册过的回调函数;当然,我们也可以使用下面的方法注册自定义的回调函数,同时也会为所有已经加载的镜像执行回调