DLL简介
基地址和RVA
- PE里面有两个概念就是基地址(Base Address)和相对地址(RVA Relative Virtual Address)。当一个PE文件被加载时,其进程空间中起始地址就是基地址。对于任何一个PE文件来说,它都有一个优先装载的基地址,这个值就是PE文件头中的Image Base。
- 对于一个可执行EXE文件来说,Image Base一般值是0x400000,对于DLL文件来说,这个值一般是0x10000000。
DLL共享数据段
正常情况下,每个DLL的数据段在各个进程中都是独立的,每个进程都拥有自己的副本。但是Windows允许将DLL的数据段设置成共享的,即任何进程都可以共享该DLL的同一份数据段。比较常见的做法是将一些需要共享的变量分离出来,放到另外一个数据段中,然后设置成进程之间共享的。也就是说,一个DLL有两个数据段,一个是进程间共享,一个是私有的。
DLL简单例子
导入和导出
- 在C/C++中,可以使用
__declspec
属性关键字来修饰某个函数或者变量。当使用__declspec(dllexport)
时,表示该符号是从本DLL导出符号,__declspec(dllimport)
是表示该符号是从别的DLL导入符号。 - 在C++中,如果你希望导出或者导入的符号符合C语言的符号修饰规范,那么必须在这个符号的定义之前再加上external "C",以防止C++编译器进行符号修饰。
- 除了使用
__declspec
属性关键字来修饰,我们也可以使用.def
文件来声明导入导出符号。
创建DLL
__declspec(dllexport) double Add(double a, double b) {
return a + b;
}
__declspec(dllexport) double Sub(double a, double b) {
return a - b;
}
__declspec(dllexport) double Mul(double a, double b) {
return a * b;
}
执行
cl /LDd Math.c
使用DLL
程序使用DLL的过程其实是引用DLL中的导出函数和符号过程,即导入过程。
#include<stdio.h>
__declspec(dllimport) double Sub(double a,double b);
int main(int argc,char **argv)
{
double result=Sub(3.0,2.0);
printf("Result = %f/n",result);
return 0;
}
使用下面命令将TestMath.c编译成TestMath.obj。
cl /c TestMath.c
使用链接器将TestMath.obj和Math.lib链接起来产生一个可执行文件TestMath.exe。
link TestMath.obj Math.lib
这个过程如下图:
Math.lib中并不包含正在Math.c的代码和数据,它描述Math.dll的导出符号,它包含了TestMath.o链接到Math.dll导入符号以及一部分桩代码,又称作”胶水”代码。Math.lib文件被称为导入库。
使用模块定义文件
生成链接脚本
cl Math.c /LD /DEF Math.def
使用.def
文件来生产Math.dll
cl /LD /DEF Math.def Math.c
DLL显示运行时链接
使用Windows提供的三个API:
LoadLibrary,GetProcAddress,FreeLibrary
符号导出导入表
导出表
导出表结构中,最后3个成员指向的是3个数组:导出地址表,符号名表,名字序号对应表。
- 查看导出表
dumpbin /IMPORTS TestMath.exe
EXP文件
在创建DLL的同时也会得到一个EXP文件,这个文件实际上是链接器在创建DLL时的临时文件。
EXP文件实际上是一个标准的PE/COFF目标文件,只不过它的扩展名不是.obj而是.exp。
导出重定向
将某个导出符号重定向到另外一个DLL。
实现机制:导出表的地址数组中包含的是函数的RVA,但是如果这个RVA指向的位置位于导出表中,那么表示这个符号被重定向了。
导入表
DLL优化
- 重定基地址(Rebasing)
- 导入函数绑定
当一个程序运行时,所有被依赖的DLL都会被装载,并且一系列导入导出符号依赖关系都会被重新解析。这些DLL都会以同样的顺序被装载到同样的内存地址,所以它们的导出符号的地址都是不变的。
DLL绑定:使用editbin可以对EXE和DLL绑定。
editbin对绑定的程序的导入符号进行遍历查找,找到以后就把符号的运行时的目标地址写入到被绑定程序的导入表内。
INT这个数组就是用来保存绑定符号的地址的。 - 可以使用
editbin /BIND TestMath.exe
来实现DLL绑定。