前言

GCC的--wrap=symbol--wrap,symbol是一个链接器选项,可以重定向未定义的符号。要配合-Wl,<options>使用,表明是一个链接器选项。具体作用如下:

  • 如果符号symbol未在当前编译单元中(一般为当前源文件)定义,则链接到符号__wrap_symbol;
  • 如果符号__real_symbol未在当前编译单元中(一般为当前源文件)定义,则链接到符号symbol;

这方便我们在移植代码时,将已有的库函数(不兼容新平台)重定向到新的自定义函数,而又不需要对每处函数调用进行修改;或者在调试代码时,对目标函数包装一下,以监视其行为而又不需要修改函数本身。

重定向自定义函数

自定义的功能函数代码:

// foo.c
#include <stdio.h>
#include "foo.h"

void foo(void)
{
printf("call foo\n");
}

void call_foo(void)
{
foo();
}

// foo.h
void foo(void);
void call_foo(void);

重定向代码:

// wrap.c
#include <stdio.h>

void __real_foo(void);
void __wrap_foo(void)
{
printf("call __wrap_foo\n");
__real_foo();
}

调用代码:

// main.c
#include "foo.h"

int main()
{
printf("foo(): ");
foo();

printf("call_foo(): ");
call_foo();

return 0;
}

CMakeLists.txt:

# CMakeLists.txt 
cmake_minimum_required(VERSION 3.6)
project(wrap_foo)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wl,--wrap,foo")
add_executable(wrap_foo main.c foo.c wrap.c)

准备好上述文件后,编译执行:

mkdir build
cd build
cmake ..
make -j8
./wrap_foo

有如下打印:

foo(): call __wrap_foo
call foo
call_foo(): call foo

注意,call_foo函数中调用的 foo函数并没有被重定向,是因为其跟foo函数的定义在同一源文件,链接器能找到foo的定义就不会重定向。可以通过objdump命令查看汇编以确认链接情况:

objdump -d wrap_foo > wrap_foo.asm

摘取maincall_foo部分如下:

000000000000068a <main>:
68a: 55 push %rbp
68b: 48 89 e5 mov %rsp,%rbp
68e: 48 8d 3d ef 00 00 00 lea 0xef(%rip),%rdi # 784 <_IO_stdin_used+0x4>
695: b8 00 00 00 00 mov $0x0,%eax
69a: e8 c1 fe ff ff callq 560 <printf@plt>
69f: e8 3c 00 00 00 callq 6e0 <__wrap_foo>
6a4: 48 8d 3d e1 00 00 00 lea 0xe1(%rip),%rdi # 78c <_IO_stdin_used+0xc>
6ab: b8 00 00 00 00 mov $0x0,%eax
6b0: e8 ab fe ff ff callq 560 <printf@plt>
6b5: e8 1a 00 00 00 callq 6d4 <call_foo>
6ba: b8 00 00 00 00 mov $0x0,%eax
6bf: 5d pop %rbp
6c0: c3 retq

...

00000000000006d4 <call_foo>:
6d4: 55 push %rbp
6d5: 48 89 e5 mov %rsp,%rbp
6d8: e8 e4 ff ff ff callq 6c1 <foo>
6dd: 90 nop
6de: 5d pop %rbp
6df: c3 retq

重定向系统函数(malloc)

wrap的一个常见用途就是包装malloc函数以监控内存申请情况,或者在某些嵌入式平台上重定向到该平台所支持的内存管理模块。一个简单的测试用例如下:

// wrap.c
#include <stdio.h>

void* __real_malloc(size_t size); // 只声明不定义
void* __wrap_malloc(size_t size) // 定义__wrap_malloc
{
printf("__wrap_malloc called, size:%zd\n", size); // log输出
return __real_malloc(size); // 通过__real_malloc调用真正的malloc
}

void __real_free(void* ptr); // 只声明不定义
void __wrap_free(void* ptr) // 定义__wrap_free
{
printf("__wrap_free called\n"); // log输出
__real_free(ptr); // 通过__real_free调用真正的free
}
// main.cpp
#include <stdio.h>
#include <stdlib.h>

int main()
{
char* c = (char*)malloc(sizeof(char)); // 调用malloc
printf("c = %p\n", c);
free(c); // 调用free
return 0;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.6)

project(wrap_malloc)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wl,--wrap,malloc -Wl,--wrap,free")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wl,--wrap,malloc -Wl,--wrap,free")

add_executable(wrap_malloc main.cpp wrap.c)
mkdir build
cd build
cmake ..
make -j8
./wrap_malloc
objdump -d wrap_malloc > wrap_malloc.asm
000000000000071a <main>:
71a: 55 push %rbp
71b: 48 89 e5 mov %rsp,%rbp
71e: 48 83 ec 10 sub $0x10,%rsp
722: bf 01 00 00 00 mov $0x1,%edi
727: e8 2f 00 00 00 callq 75b <__wrap_malloc>
72c: 48 89 45 f8 mov %rax,-0x8(%rbp)
730: 48 8b 45 f8 mov -0x8(%rbp),%rax
734: 48 89 c6 mov %rax,%rsi
737: 48 8d 3d 06 01 00 00 lea 0x106(%rip),%rdi # 844 <_IO_stdin_used+0x4>
73e: b8 00 00 00 00 mov $0x0,%eax
743: e8 98 fe ff ff callq 5e0 <printf@plt>
748: 48 8b 45 f8 mov -0x8(%rbp),%rax
74c: 48 89 c7 mov %rax,%rdi
74f: e8 39 00 00 00 callq 78d <__wrap_free>
754: b8 00 00 00 00 mov $0x0,%eax
759: c9 leaveq
75a: c3 retq

执行有如下打印:

__wrap_malloc called, size:1

c = 0x563621af1670

__wrap_free called