Hook调试初体验

emmmmm,这篇文章主要是记录下最近在项目上调试GL资源的一些流程和原理。

首先呢,我们都知道调试GL图像效果是一件非常不方便的事情,特别是在各个项目频繁交叉使用可能产生一些意想不到的问题,所以一个良好的调试手段是必要的。

话说回来,OpenGL这种系统厂商实现的函数库我们要怎么调试呢?在iOS平台上还好说,至少还能用Xcode的GPU Frame Capture(那个小相机)调试GPU绘制的图像效果。而Android Studio的GPU调试工具实在是复杂的一匹。

那么Hook具体是如何实现的呢?这个可以扯到逆向工程的一些技术了,这里就不展开了。我们知道在iOS中由于Objective-C是一门动态语言可以直接用Method Swizzling这种方法来动态修改调用方法来实现Hook函数添加。但是我们要Hook的是GL的函数呀(C函数),这就需要我们往系统层来考虑这件事了。

基本原理

我们知道我们的开发语言大致有分为两种类型:编译型(C/C++,Obejctive-C,Swift)和解释型(Java,Python,Shell,Javascript)。

它们的最大差别在于编译型语言在运行前代码就在各个系统平台上通过编译链接生成一个个程序镜像(image)它们执行的是机器指令,在不同的系统平台上是无法通用的。

而解释型语言则是运行在一种系统无关的环境上,如Java虚拟机,Javascript引擎等等。它们执行的是一种标记指令,再交由软件环境去翻译器成机器指令去真正执行。

我们将运行在各个系统平台上的程序镜像(image)称为可执行二进制文件。而在这里要Hook的目标就是我们程序中调用的系统OpenGL函数库的方法调用。

可执行文件结构

学过计算机的同学都应该知道我们的所有高级语言代码最后都是被转化为二进制代码运行的,而这些二进制代码在不同的文件系统上有各自的组织方式。比如在OS X/iOS中使用Mach-O格式,而在Linux/Android中使用ELF格式。

Mach-O二进制文件:

ELF二进制文件:

从上面的截图,可以知道这些二进制文件存储着动态库函数跳转符号表、数据、文件格式等信息。

而我们Hook的目的就是通过截取动态库函数跳转符号并加以修改来实现函数功能的修改。那么我们又要如何实现这一步呢?

可执行文件执行过程

对于它们的实现细节并不是我关心的重点,这里从较为宏观的流程来阐述它们具体要如何加载到OS内核去运行的。

总体而言,运行一个可执行文件基本需要以下四个流程:

  1. 解析文件
  2. 依赖建立(动态链接依赖库)
  3. 初始化运行环境
  4. 执行进程

而我们要实现的Hook功能正是在进行动态链接依赖库(.so)这个流程的时候实现的。其中dyld(Dynamic Loader)和Android linker它们分别是OS X/iOS和Android上的动态依赖库加载器。

FishHook与Xhook

我们分别通过FishHookXhook这两个第三方库来实现对iOS/Android这两个平台的动态库函数Hook。

参考

Mach-O解析
Mach-O加载流程
ELF加载流程
FishHook解析
xHook分析
C语言函数修改