2013-02-26

用ld和LD_PRELOAD替换调用的函数

以前虽然也知道应该怎么把程序中用到的函数替换掉,但还从来没有实际做过,最近因工作需要才动手做了一遍。

写一个最简单的hello world程序

// main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    puts("Hello World!");
    return 0;
}
// end of main.c

# gcc main.c -o 1
# ./1
Hello World!

1. 静态编译
使用ld的命令行参数--wrap,a,表示对a的调用改为对__wrap_a的调用,而对__real_a的调用改为对原来的a的调用。

// stub.c
int __wrap_puts(const char *s)
{
    int result;
    __real_puts("========before real call========");
    result = __real_puts(s);
    __real_puts("======== after real call========");
    return result;
}
// end of stub.c

# gcc -Wl,--wrap,puts main.c stub.c -o 2
# ./2
========before real call========
Hello World!
======== after real call========

2. 动态编译
使用LD_PRELOAD,然后用dlsym取原来的符号地址,缺点是由于安全原因对有suid或sgid的程序没法用,据说有的系统调用也没法这么拦截,需要用ptrace。

// dynstub.c
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>

int puts(const char *s)
{
    int result;
    static typeof(puts) *orig_puts = NULL;
    if (orig_puts == NULL)
        orig_puts = dlsym(RTLD_NEXT, "puts");
    (*orig_puts)("========before real call========");
    result = (*orig_puts)(s);
    (*orig_puts)("======== after real call========");
    return result;
}
// end of dynstub.c

# gcc -fpic -shared -ldl -o dynstub.so dynstub.c
# LD_PRELOAD=/root/tmp/dynstub.so ./1
========before real call========
Hello World!
======== after real call========

在每个函数里用dlsym看起来也不太美观,可能__attribute__ ((constructor))更好点,但这就是大量使用的时候才需要考虑的问题了。

// dynstub2.c
#define _GNU_SOURCE
#include <assert.h>
#include <stdio.h>
#include <dlfcn.h>

static typeof(puts) *orig_puts = NULL;

__attribute__ ((constructor)) void loadsym()
{
    orig_puts = dlsym(RTLD_NEXT, "puts");
    assert(orig_puts != NULL);
}

int puts(const char *s)
{
    int result;
    (*orig_puts)("========before real call========");
    result = (*orig_puts)(s);
    (*orig_puts)("======== after real call========");
    return result;
}
// end of dynstub2.c

没有评论: