Символы перегрузки запущенного процесса (LD PRELOAD attachment)

Я работаю над профилировщиком кучи для Linux, называемым heaptrack. В настоящее время я полагаюсь на LD_PRELOAD перегрузить различные (de-)функции распределения, и это работает очень хорошо.

теперь я хотел бы расширить инструмент, чтобы среда выполнения подключалась к существующему процессу, который был запущен без LD_PRELOADing мой инструмент. Я могу!--2--> моя библиотека через GDB просто отлично, но это не перезапишет malloc etc. Я думаю, это потому, что в этот момент компоновщик уже решил зависящий от позиции код уже запущенного процесса-правильно?

так что же мне делать, чтобы перегрузить malloc и друзей?

Я не разбираюсь в ассемблерном коде. Из того, что я прочитал до сих пор, я думаю, что мне как-то придется патч malloc и другие функции, такие, что они сначала возвращаются к моей функции трассировки, а затем продолжают их фактическую реализацию? Это верно? Как мне это сделать?

Я надеюсь, что есть существующие инструменты там, или что я могу использовать GDB / ptrace для этого.

2 ответов


только для lulz, другое решение без отслеживания вашего собственного процесса или прикосновения к одной линии сборки или игры с /proc. Вам нужно только загрузить библиотеку в контексте процесса, и пусть волшебство произойдет.

решение, которое я предлагаю-это использовать конструктор функция (принесенная с C++ на C gcc) для запуска некоторого кода при загрузке библиотеки. Тогда эта библиотека просто исправит запись GOT (Global Offset Table) для malloc. The 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 как указатели (которые могут позволить позже вызвать реальную функцию и обойти вашу), Вы можно применить тот же процесс к таблице символов, на который указывает DT_RELA динамическая запись.

если конструктор функция не доступна для вас, все, что вам нужно сделать, это решить init символ из недавно загруженной библиотеки и вызовите его.

обратите внимание, что вы также можете заменить dlopen так что библиотеки, загруженные после вашего, также исправляются. Что может произойти, если вы довольно рано или если приложение динамически загружает библиотеку загруженные Плагины.


Это не может быть сделано без настройки с ассемблером немного. В принципе, вам придется делать то, что делают gdb и ltrace: найти malloc и виртуальные адреса друзей в образе процесса и поставить точки останова при их входе. Этот процесс обычно включает в себя временную перезапись исполняемого кода, так как вам нужно заменить обычные инструкции "ловушкой" (например,int 3 на x86).

Если вы хотите избежать этого самостоятельно, существует связываемая оболочка вокруг gdb (libgdb) или вы можете создать ltrace как библиотеку (libltrace). Поскольку ltrace намного меньше, а разнообразие библиотек доступно из коробки, это, вероятно, позволит вам делать то, что вы хотите при меньших усилиях.

например, вот лучшая часть " main.C " файл из пакета ltrace:

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