我们经常在游戏目录下看见dll文件,这是windows下的动态链接库。在linux下我们可以使用-shared -fpic生成so文件。
动态链接的好处在于,当我们需要变更函数实现时,不需要重新编译整个可运行文件,只需要修改动态链接库即可,所以破解游戏的时候通常补丁就是一个dll而已。快忘光了,写个随笔复习一下,大一知识简单勿怪。
内存布局
- GOT(Global offset table) - 动态函数入口,elf中位于.got段,不可执行数据
- PLT(Procedure linkage table) - 桩函数,elf中位于.got.plt段,可执行代码
- 动态链接库 - 包含函数和变量,有不可执行数据和可执行代码
原理
下面这个是函数调用原理,如果是变量的话直接获得地址就好。
- PLT由调用GOT代码+桩代码组成。桩代码用于动态修改GOT表。
- GOT初始指向桩代码,后来指向动态函数。
- PLT索引 = 函数索引x+1
- GOT索引 = 函数索引x+3
初次调用,惰性加载
- 调用PLT[x+1]
- 跳转*GOT[x+3](桩代码地址)
- 传递动态链接函数索引x
- 跳转PLT[0]
- 传递动态链接表地址GOT[1]
- 跳转*GOT[2](动态链接器函数地址)
- 修改GOT[x+3]为动态函数地址
后续调用,直接跳转
- 调用PLT[x+1]
- 跳转*GOT[x+3](动态函数地址)
应用
#include <dlfcn.h>
void *dlopen(const char *filename, int flag) ;
returns: ptr to handle if OK, NULL on error
void *dlsym(void *handle, char *symbol) ;
returns: ptr to symbol if OK, NULL on error
int dlclose(void *handle) ;
returns: 0 if OK, -1 on error
const char dlerror(void) ;
returns: errormsg if previous call to
dlopen, dlysym, or dlclose failed,
NULL if previous call was OK
dlopen为mmap+解析符号表
- RTLD_LAZY/RTLD_NOW决定是否惰性加载
- RTLD_GLOBAL/RTLD_LOCAL决定符号是否为其他动态链接库可见
- RTLD_DEEPBIND优先查找动态链接库的符号而非全局符号
- RTLD_NOLOAD 不加载动态链接库,可用于修改之前的flag
- RTLD_NODELETE close时不会卸载,因此静态变量在reopen时不会重新初始化
dpsym从符号表中寻址函数、变量
dlclose减少mmap引用计数,为0后munmap
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int
main(int argc, char **argv)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen("libm.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror();
*(void **) (&cosine) = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("%f\n", (*cosine)(2.0));
dlclose(handle);
exit(EXIT_SUCCESS);
}
引用
- dlopen(3) - Linux man page
- http://csapp.cs.cmu.edu/3e/ics3/link/plt2.pdf
- sjtu,ipads,ics,2-16-dl