DLL简介

DLL和EXE都是PE格式的二进制文件,不同的是PE头部有符号位表示该文件是EXE还是DLL。

DLL文件扩展名不一定是.dll,也可能是别的比如.ocx或.CPL

进程地址空间和内存管理

在32位Windows中开始支持进程拥有独立地址空间,一个DLL在不同的进程中拥有不同的私有数据副本。

ELF中代码是地址无关,所以他可以实现多个进程之间共享一份代码,但DLL的代码并不是地址无关,所以只能在某些情况下可以被多个进程间共享。

基地址和RVA

基地址就是进程的起始地址。对于PE来说,它都有优先正在基地址,这个值就是PE文件头中的Image Base。

RVA就是偏移地址。

DLL共享数据段

Win32下,系统提供给了一系列API可以实现进程间的通信。其中有一种方法是使用DLL来实现进程间通信。

正常情况下每个DLL的数据段在各个进程中都是独立的,每个进程都拥有自己的副本。但是windows允许将DLL的数据段设置成共享的。

DLL导入和导出

在ELF中共享库中所有的全局函数和变量默认可以被其他模块使用,就是说ELF默认导出所有的全局符号。但在DLL中不同,我们需要显示的告诉编译器我们需要导出某个符号,否则编译器默认所有符号都不导出。

当我们程序中使用DLL导出的符号时,这个过程被称为导入(Import)。

在DLL代码中声明:
__declspec(dllexport) 表示要导出该函数或变量。

在用户代码中声明:
__declspec(dllimport) 表示该符号是从DLL导入的。

DLL创建

创建一个我们自己的DLL:

testdll.c

1
2
3
4
__declspec(dllexport) double Add(double a,double b)
{
return a + b;
}

将其编译成.dll后缀文件:

1
cl /LDd testdll.c

DLL使用

调用dll中的函数:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

__declspec(dllimport) double Add(double a,double b);

int main(int argc,char **argv){

double result = Add(1.5,2.5);
printf("result : %f",result);
return 0;

}

编译和链接

测试:

DLL导入库

在静态链接时.lib文件是静态库,里面保存了数据和代码,用于和其他模块组合起来链接成EXE文件。

但在动态链接时.lib是导入库,它内部并不包含dll的代码和数据,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

DLL 显示运行时链接

windows 提供了3个API:

  • LoadLibrary 用于装载一个DLL到进程的地址空间。
  • GetProcAddress 用来查找某个符号的地址。
  • FreeLibrary 用来卸载某个已加载的模块。

导出表

当一个PE需要将一些函数或变量提供给其他PE文件使用时,我们把这种行为叫做符号导出(Symbol Exporting),最典型的情况就是一个DLL将符号导出给EXE文件使用。

在Windows PE中,所有导出的符号被集中存放在了被称为导出表的结构中。导出表从简单的结构上来看,它提供了一个符号名与符号地址的映射关系,可以通过某个符号查找相应的地址。