库打桩机制

库打桩机制

库打桩library interpositioning)允许你截获对共享库函数的调用,取而代之执行自己的代码。使用打桩机制,可以追踪对某个特殊函数的调用次数,验证和追踪它的输入和输出值,或者甚至把它替换成一个完全不同的实现。

基本思想:给定一个需要打桩的目标函数,创建一个包装函数,它的原型和目标函数完全一样。使用某种特殊的打桩机制,就可以欺骗系统调用包装函数而不是目标函数了。包装函数通常会执行自己的逻辑,然后调用目标函数,再将目标函数的返回值传递给调用者。

打桩可以发生在编译时、链接时或当程序被加载和执行时。

编译时打桩

用C预处理器进行编译时打桩,在当前目录下创建malloc.hmymalloc.cmain.c

本地malloc.h文件:指示预处理器调用对相应包装函数的调用替换掉对目标函数的调用。

#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)

void *mymalloc(size_t size);
void myfree(void *ptr);

mymalloc.c中的包装器函数

#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h> // 使用标准malloc.h头文件编译

/* malloc wrapper function */
void *mymalloc(size_t size) {
    static size_t _mymalloc_calltime = 0;
    ++_mymalloc_calltime;
    void *ptr = malloc(size);
    printf("malloc(%d) = %p, callTime = %d\n", (int)size, ptr, (int)_mymalloc_calltime);
    return ptr;
}

/* free wrapper function */
void myfree(void *ptr) {
    static size_t _myfree_calltime = 0;
    ++_myfree_calltime;
    free(ptr);
    printf("free(%p), callTime = %d\n", ptr, (int)_myfree_calltime);
}
#endif

示例程序main.c

#include <stdio.h>
#include <malloc.h>

int main() {
    int *p = malloc(32);
    free(p);
    return 0;
}

使用C预处理器在编译时打桩

# -D宏名
gcc -DCOMPILETIME -c mymalloc.c 
# 因为有-I.参数,所以会打桩,它告诉C预处理器在搜索通常的系统目录之前,先在当前目录中查找malloc.h,main函数中的malloc会变成mymalloc
gcc -I. -o main main.c mymalloc.o 

./main 
malloc(32) = 0x56192dfc02a0, callTime = 1
free(0x56192dfc02a0), callTime = 1

通过预编译查看main.i gcc -I. -E -o main.i main.c,可以看到malloc变成了mymalloc,free变成了myfree

# 2 "main.c" 2
# 1 "./malloc.h" 1
# 4 "./malloc.h"
void *mymalloc(size_t size);
void myfree(void *ptr);
# 3 "main.c" 2

int main() {
    int *p = mymalloc(32);
    myfree(p);
    return 0;
}

编译时打桩需要能够访问程序的源代码。本质上就是先定义自己的包装函数,在包装函数中调用标准的目标方法,并生成可重定位的文件。再定义一个通过宏定义方式转换的头文件(名称与需要打桩的函数所在的头文件相同),gcc编译时优先从本地搜索该同名文件而调用了包装函数。

  • 打桩函数内部不要打桩,即在mymalloc函数中要使用原始的malloc函数,在myfree函数中要使用原始的free函数,不然会造成循环调用。
  • 通过#define指令,将外部调用malloc的地方都替换为mymalloc,free替换为myfree。
  • 分开编译mymalloc.c和外部调用代码main.c,最终链接。

链接时打桩

Linux静态链接器支持用--warp f标志进行链接时打桩。这个标志告诉链接器,把对符号f的引用解析成__warp_f(前缀是两个下划线),还要把对符号__real_f(前缀是两个下划线)的引用解析为f。

在当前目录下创建main.cmymalloc.c

main.c

#include <stdio.h>
#include <malloc.h>

int main() {
    int *p = malloc(32);
    free(p);
    return 0;
}

mymalloc.c:用–wrap标志进行链接时打桩

#ifdef LINKTIME
#include<stdio.h>

void *__real_malloc(size_t size);
void __real_free(void *ptr);

/* malloc wrapper function */
void *__wrap_malloc(size_t size) {
    void *ptr = __real_malloc(size); // call libc malloc
    printf("malloc(%d) = %p\n", (int)size, ptr);
    return ptr;
}

/* free wrapper function */
void __wrap_free(void *ptr) {
    __real_free(ptr); // call libc free
    printf("free(%p)\n", ptr);
}

#endif

链接时打桩

# 把源文件编译成重定位目标文件
gcc -DLINKTIME -c mymalloc.c
gcc -c main.c
# 把目标文件链接成可执行文件,-Wl,option标志把option传递给链接器,option中的每个逗号都要替换为一个空格。所以-Wl,--wrap,malloc就把--wrap malloc传递给链接器,以类似的方式传递-Wl,--wrap,free
# -Wl,--wrap,malloc ==> --wrap malloc ==> __wrap_malloc
# __real_f ==> f
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o main main.o mymalloc.o

./main 
malloc(32)= 0x5ef0514752a0
free(0x5ef0514752a0)

链接时打桩需要能够访问程序的可重定位对象文件。

利用链接器的打桩机制,最后在main函数中调用malloc,将会去调用__wrap_malloc,而__real_malloc将会被解析成真正的malloc。

运行时打桩

运行时打桩只需要能够访问可执行目标文件。

机制基于动态链接器的LD_PRELOAD环境变量。如果LD_PRELOAD环境变量被设置为一个共享库路径名的列表(以空格或分号分隔),那么当你加载和执行一个程序,需要解析未定义的引用时,动态链接器(LD-LINUX.SO)会先搜索LD_PRELOAD库,然后才搜索任何其它库。通过此机制,可以加载和执行任意可执行文件时,可以对任何共享库中的任何函数打桩,包括libc.so。

在当前目录下创建main.cmymalloc.c

main.c

#include <stdio.h>
#include <malloc.h>

int main() {
    int *p = malloc(32);
    free(p);
    return 0;
}

mymalloc.c

#ifdef RUNTIME
#define __USE_GNU // 必须有此行,不再是_GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

/* malloc wrapper function */
void *malloc(size_t size) {
	void *(*mallocp)(size_t size);
	char *error;
	
	mallocp = dlsym(RTLD_NEXT, "malloc"); // get address of libc malloc
	if ((error = dlerror()) != NULL) {
		fputs(error, stderr);
		exit(1);
	}
	char *ptr = mallocp(size); // call libc malloc
    
	char buf[512];
	sprintf(buf, "malloc(%d) = %p\n", (int)size, ptr);
	fputs(buf, stderr);

	// printf("malloc(%d) = %p\n", (int)size, ptr); // segment fault
	return ptr;
}

/* free wrapper function */
void free(void *ptr) {
	void (*freep)(void *) = NULL;
	char *error;
	
	if (!ptr) {
		return;
	}

	freep = dlsym(RTLD_NEXT, "free"); // get address of libc free
	if ((error = dlerror()) != NULL) {
		fputs(error, stderr);
		exit(1);
	}
	freep(ptr); // get libc free
	printf("free(%p)\n", ptr);
}
#endif

运行时打桩

gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
gcc -o main main.c

LD_PRELOAD="./mymalloc.so" ./main 
malloc(32) = 0x56314f86b2a0
malloc(1024) = 0x56314f86b2d0
free(0x56314f86b2a0)

# 使用LD_PRELOAD可以对任何可执行程序的库函数调用打桩
LD_PRELOAD="./mymalloc.so" /usr/bin/uptime 
  • 要避免打桩函数内部调用打桩函数

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值