重载运行进程的符号(LD_PRELOAD附件)

时间:2014-11-25 21:58:57

标签: c linux gdb runtime dynamic-linking

我正在使用Linux的堆分析器,名为heaptrack。目前,我依靠LD_PRELOAD来重载各种(de-)分配函数,这非常有效。

现在我想扩展该工具以允许运行时附加到现有进程,该进程是在没有LD_PRELOAD我的工具的情况下启动的。我可以通过GDB dlopen我的库很好,但不会覆盖malloc等等。我认为,这是因为此时链接器已经解析了已经运行的位置相关代码过程 - 正确吗?

那么我该怎样做才能重载malloc和朋友?

我不熟悉汇编程序代码。从我到目前为止所阅读的内容来看,我想我不得不修补malloc和其他函数,以便他们首先回调我的跟踪功能,然后继续他们的实际实现?那是对的吗?我该怎么做?

我希望有现有的工具,或者我可以利用GDB / ptrace。

2 个答案:

答案 0 :(得分:16)

只是为了lulz,另一个解决方案,没有完成你自己的过程或触摸单行程序或使用/proc。您只需要在流程的上下文中加载库,让神奇的事情发生。

我建议的解决方案是使用构造函数功能(通过gcc从C ++引入C)在加载库时运行一些代码。然后这个库只修补malloc的GOT(全局偏移表)条目。 GOT存储库函数的实际地址,以便名称解析只发生一次。要修补GOT,你必须使用ELF结构(参见man 5 elf)。 Linux非常友好地为您提供了aux向量(请参阅man 3 getauxval),该向量告诉您在内存中找到当前程序的程序头的位置。但是,dl_iterate_phdr提供了更好的界面,在下面使用。

这是一个库的示例代码,它在调用init函数时完成此操作。虽然使用gdb脚本可能会达到同样的效果。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <sys/auxv.h>
#include <elf.h>
#include <link.h>
#include <sys/mman.h>


struct strtab {
    char *tab;
    ElfW(Xword) size;
};


struct jmpreltab {
    ElfW(Rela) *tab;
    ElfW(Xword) size;
};


struct symtab {
    ElfW(Sym) *tab;
    ElfW(Xword) entsz;
};



/* Backup of the real malloc function */
static void *(*realmalloc)(size_t) = NULL;


/* My local versions of the malloc functions */
static void *mymalloc(size_t size);


/*************/
/* ELF stuff */
/*************/
static const ElfW(Phdr) *get_phdr_dynamic(const ElfW(Phdr) *phdr,
        uint16_t phnum, uint16_t phentsize) {
    int i;

    for (i = 0; i < phnum; i++) {
        if (phdr->p_type == PT_DYNAMIC)
            return phdr;
        phdr = (ElfW(Phdr) *)((char *)phdr + phentsize);
    }

    return NULL;
}



static const ElfW(Dyn) *get_dynentry(ElfW(Addr) base, const ElfW(Phdr) *pdyn,
        uint32_t type) {
    ElfW(Dyn) *dyn;

    for (dyn = (ElfW(Dyn) *)(base + pdyn->p_vaddr); dyn->d_tag; dyn++) {
        if (dyn->d_tag == type)
            return dyn;
    }

    return NULL;
}



static struct jmpreltab get_jmprel(ElfW(Addr) base, const ElfW(Phdr) *pdyn) {
    struct jmpreltab table;
    const ElfW(Dyn) *dyn;

    dyn = get_dynentry(base, pdyn, DT_JMPREL);
    table.tab = (dyn == NULL) ? NULL : (ElfW(Rela) *)dyn->d_un.d_ptr;

    dyn = get_dynentry(base, pdyn, DT_PLTRELSZ);
    table.size = (dyn == NULL) ? 0 : dyn->d_un.d_val;
    return table;
}



static struct symtab get_symtab(ElfW(Addr) base, const ElfW(Phdr) *pdyn) {
    struct symtab table;
    const ElfW(Dyn) *dyn;

    dyn = get_dynentry(base, pdyn, DT_SYMTAB);
    table.tab = (dyn == NULL) ? NULL : (ElfW(Sym) *)dyn->d_un.d_ptr;
    dyn = get_dynentry(base, pdyn, DT_SYMENT);
    table.entsz = (dyn == NULL) ? 0 : dyn->d_un.d_val;
    return table;
}



static struct strtab get_strtab(ElfW(Addr) base, const ElfW(Phdr) *pdyn) {
    struct strtab table;
    const ElfW(Dyn) *dyn;

    dyn = get_dynentry(base, pdyn, DT_STRTAB);
    table.tab = (dyn == NULL) ? NULL : (char *)dyn->d_un.d_ptr;
    dyn = get_dynentry(base, pdyn, DT_STRSZ);
    table.size = (dyn == NULL) ? 0 : dyn->d_un.d_val;
    return table;
}



static void *get_got_entry(ElfW(Addr) base, struct jmpreltab jmprel,
        struct symtab symtab, struct strtab strtab, const char *symname) {

    ElfW(Rela) *rela;
    ElfW(Rela) *relaend;

    relaend = (ElfW(Rela) *)((char *)jmprel.tab + jmprel.size);
    for (rela = jmprel.tab; rela < relaend; rela++) {
        uint32_t relsymidx;
        char *relsymname;
        relsymidx = ELF64_R_SYM(rela->r_info);
        relsymname = strtab.tab + symtab.tab[relsymidx].st_name;

        if (strcmp(symname, relsymname) == 0)
            return (void *)(base + rela->r_offset);
    }

    return NULL;
}



static void patch_got(ElfW(Addr) base, const ElfW(Phdr) *phdr, int16_t phnum,
        int16_t phentsize) {

    const ElfW(Phdr) *dphdr;
    struct jmpreltab jmprel;
    struct symtab symtab;
    struct strtab strtab;
    void *(**mallocgot)(size_t);

    dphdr = get_phdr_dynamic(phdr, phnum, phentsize);
    jmprel = get_jmprel(base, dphdr);
    symtab = get_symtab(base, dphdr);
    strtab = get_strtab(base, dphdr);
    mallocgot = get_got_entry(base, jmprel, symtab, strtab, "malloc");

    /* Replace the pointer with our version. */
    if (mallocgot != NULL) {
        /* Quick & dirty hack for some programs that need it. */
        /* Should check the returned value. */
        void *page = (void *)((intptr_t)mallocgot & ~(0x1000 - 1));
        mprotect(page, 0x1000, PROT_READ | PROT_WRITE);
        *mallocgot = mymalloc;
    }
}



static int callback(struct dl_phdr_info *info, size_t size, void *data) {
    uint16_t phentsize;
    data = data;
    size = size;

    printf("Patching GOT entry of \"%s\"\n", info->dlpi_name);
    phentsize = getauxval(AT_PHENT);
    patch_got(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, phentsize);

    return 0;
}



/*****************/
/* Init function */
/*****************/
__attribute__((constructor)) static void init(void) {
    realmalloc = malloc;
    dl_iterate_phdr(callback, NULL);
}



/*********************************************/
/* Here come the malloc function and sisters */
/*********************************************/
static void *mymalloc(size_t size) {
    printf("hello from my malloc\n");
    return realmalloc(size);
}

一个只在两个malloc次调用之间加载库的示例程序。

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>



void loadmymalloc(void) {
    /* Should check return value. */
    dlopen("./mymalloc.so", RTLD_LAZY);
}



int main(void) {
    void *ptr;

    ptr = malloc(42);
    printf("malloc returned: %p\n", ptr);

    loadmymalloc();

    ptr = malloc(42);
    printf("malloc returned: %p\n", ptr);

    return EXIT_SUCCESS;
}

mprotect的调用通常没用。但是我发现gvim(编译为共享对象)需要它。如果您还希望捕获对malloc的引用作为指针(可能允许稍后调用实际函数并绕过您的指针),则可以将相同的进程应用于{{1}指向的符号表。动态进入。

如果您无法使用构造函数功能,您只需解析新加载的库中的DT_RELA符号并调用它。

请注意,您可能还想替换init,以便在您的库之后加载的库也会被修补。如果您很早加载库或者应用程序已动态加载插件,可能会发生这种情况。

答案 1 :(得分:4)

如果没有使用汇编程序进行调整,则无法完成此操作。基本上,你必须做gdb和ltrace做的事情:在过程映像中找到malloc和朋友虚拟地址,并在其入口处放置断点。此过程通常涉及临时重写可执行代码,因为您需要使用“陷阱”替换正常指令(例如x86上的int 3)。

如果你想避免自己这样做,可以在gdb(libgdb)周围存在可链接的包装器,或者你可以将ltrace构建为库(libltrace)。由于ltrace要小得多,并且它的库可以开箱即用,它可能会让你以更低的努力做你想做的事。

例如,这是ltrace包中“main.c”文件的最佳部分:

int
main(int argc, char *argv[]) {
    ltrace_init(argc, argv);

 /*
    ltrace_add_callback(callback_call, EVENT_SYSCALL);
    ltrace_add_callback(callback_ret, EVENT_SYSRET);
    ltrace_add_callback(endcallback, EVENT_EXIT);

    But you would probably need EVENT_LIBCALL and EVENT_LIBRET
 */

    ltrace_main();
    return 0;
}

http://anonscm.debian.org/cgit/collab-maint/ltrace.git/tree/?id=0.7.3