我正在研究Linux的堆分析器,称为heaptrack.目前,我依赖于LD_PRELOAD
重载各种(解)分配函数,这非常有效.
现在我想扩展该工具以允许运行时附加到现有进程,该进程是在没有LD_PRELOAD
我的工具的情况下启动的.我可以dlopen
通过GDB很好地使用我的库,但是这不会覆盖malloc
等等.我认为,这是因为此时链接器已经解决了已经运行的进程的位置相关代码 - 正确吗?
那么我该怎么办才能超载malloc
和朋友呢?
我不熟悉汇编代码.从我到目前为止所读到的,我想我将以某种方式必须修补malloc
和其他功能,以便他们首先回调我的跟踪功能,然后继续他们的实际实现?那是对的吗?我怎么做?
我希望有现有的工具,或者我可以利用GDB/ptrace.
只是为了lulz,另一个解决方案,没有你自己的过程或触摸单一的装配线或玩弄/proc
.您只需要在流程的上下文中加载库,让魔法发生.
我建议的解决方案是使用构造函数功能(通过gcc从C++引入C)在加载库时运行一些代码.然后这个库只修补GOT(全局偏移表)条目malloc
.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
以便在您的库之后加载的库也得到修补.如果您很早加载库或者应用程序已动态加载插件,可能会发生这种情况.