苹果系统的链接器/usr/lib/dyld 提供了一个叫dyld-interposing的功能(从 Mac OS X 10.4 开始),可以在程序启动时替换掉某个函数的实现。这个功能可以用来实现代码注入(详见:《Mac OS X Internals: A Systems Approach》- Amit Singh - 第二章 2.6.3.4 dyld interposing)

举个栗子

比如,我们可以在程序运行时,替换掉malloc函数的实现:

malloc_trace.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// malloc_trace.c
#include <stdio.h>
#include <stdlib.h>

#include <mach-o/dyld-interposing.h>
#include <memory.h> // memset
#include <malloc/malloc.h> // malloc_printf

void *trace_malloc(size_t size) {
char *p = malloc(size);
// fills with '#'
memset(p, '#', size);
malloc_printf("malloc(%u) = %p\n", size, p);
return (void *)p;
}

DYLD_INTERPOSE(trace_malloc, malloc);
test.c
1
2
3
4
5
6
7
8
9
// test.c
#include <stdio.h>
#include <stdlib.h>
int main() {
char *p = (char*)malloc(10);
printf("malloc return %p, %s\n", p, p);
free(p);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cc -dynamiclib -o libmalloctrace.dylib malloc_trace.c -install_name libmalloctrace.dylib
$ cc -o test test.c
$ DYLD_INSERT_LIBRARIES=libmalloctrace.dylib ./test

test(46555,0x11bdd3600) malloc: malloc(1536) = 0x7febbc808200
test(46555,0x11bdd3600) malloc: malloc(32) = 0x7febbc704130
test(46555,0x11bdd3600) malloc: malloc(32) = 0x7febbc704170
test(46555,0x11bdd3600) malloc: malloc(20) = 0x7febbc705550
test(46555,0x11bdd3600) malloc: malloc(422) = 0x7febbc7055d0
test(46555,0x11bdd3600) malloc: malloc(50) = 0x7febbc7057e0
test(46555,0x11bdd3600) malloc: malloc(16) = 0x7febbc705880
test(46555,0x11bdd3600) malloc: malloc(52) = 0x7febbc705900
test(46555,0x11bdd3600) malloc: malloc(12) = 0x7febbc7059b0
test(46555,0x11bdd3600) malloc: malloc(10) = 0x7febbc705b00
test(46555,0x11bdd3600) malloc: malloc(4096) = 0x7febbc808800
malloc return 0x7febbc705b00, ##########

ps. 不能在替换函数(replacement)中使用会调用被替换函数(replacee)的其它函数。比如printf()内部可能会使用malloc()trace_malloc中不能调用printf,不然死给你看~

头文件<mach-o/dyld-interposing.h>来自 dyld 源码:https://github.com/apple-oss-distributions/dyld/blob/dyld-1042.1/include/mach-o/dyld-interposing.h

其中宏 DYLD_INTERPOSE 的定义:

1
2
3
#define DYLD_INTERPOSE(_replacement,_replacee) \
__attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \
__attribute__ ((section ("__DATA,__interpose,interposing"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };

malloc_trace.c 展开后的代码如下:

1
2
3
4
5
6
7
static struct {
const void *replacement;
const void *replacee;
} _interpose_malloc __attribute__ ((section ("__DATA,__interpose,interposing"))) = {
(const void *)&trace_malloc,
(const void *)&malloc
};

这段代码声明了一个_interpose_malloc的结构体,replacement成员指向trace_malloc函数,replacee成员指向malloc函数。

并通过__attribute__编译配置将这个结构体放到了 Mach-O 产物的 __DATA,__interpose段中。

otool 查看:

1
2
3
4
5
6
7
$ otool -l libmalloctrace.dylib | grep -A 5 __interpose
sectname __interpose
segname __DATA
addr 0x0000000000008000
size 0x0000000000000010
offset 32768
align 2^3 (8)

__DATA,__interpose段的大小为0x10,也就是16字节,正好是代码中_interpose_malloc结构体的大小。

也可以使用 MachOView 查看:

在程序加载dylib时,dyld 会解析它的__DATA,__interpose段,找到所有的struct { uintptr_t replacement; uintptr_t replacee; };结构体,然后将replacee成员指向的函数替换成replacement成员指向的函数。

详见源码:RuntimeState::buildInterposingTables()

使用__DATA,__interpose段实现函数替换是静态替换,在程序启动时(dylib被加载时)就替换了。

曾经还有一种方式是动态替换,使用dyld的私有函数dyld_dynamic_interpose,在程序运行时替换函数,详见<mach-o/dyld_priv.h>不过经过测试,已经失效了~~

使用环境变量 DYLD_PRINT_INTERPOSING=1,可以打印出被替换函数的替换信息:

1
2
3
4
5
6
7
8
9
10
11
$ DYLD_PRINT_INTERPOSING=1 DYLD_INSERT_LIBRARIES=libmalloctrace.dylib ./test

dyld[92700]: libmalloctrace.dylib has interposed '_malloc' to replacing binds to 0x7FF805E19530 with 0x10151FF40
dyld[92700]: interpose replaced 0x7FF805E19530 with 0x7FF805E19530 in /path/to/libmalloctrace.dylib
dyld[92700]: interpose replaced 0x7FF805E19530 with 0x10151FF40 in /path/to/test
dyld[92700]: interpose: *0x7ff845cdd028 = 0x10151FF40 (dyld cache patch) to _malloc
dyld[92700]: interpose: *0x7ff845cddb38 = 0x10151FF40 (dyld cache patch) to _malloc
dyld[92700]: interpose: *0x7ff845ce35e0 = 0x10151FF40 (dyld cache patch) to _malloc
dyld[92700]: interpose: *0x7ff845ce47c0 = 0x10151FF40 (dyld cache patch) to _malloc
dyld[92700]: interpose: *0x7ff845ce6d78 = 0x10151FF40 (dyld cache patch) to _malloc
...

有什么用?

你可能疑惑通过dyld-interposing实现代码注入有什么用?

  • 方便调试(比如实现malloc的内存泄漏检测)
  • api trace(比如实现opengl api trace)
  • hook dylib api (比如实现 hot-reload)

Prevent dyld-interposing?

阻止dyld加载 DYLD_INSERT_LIBRARIES:添加链接参数-Wl,-add_empty_section,__RESTRICT,__restrict (详见:ProcessConfig::Security::getAMFIHeader::isRestricted()

Refs