测试代码如下:,此代码最终生成dlltest.exe文件,并调用test.dll,主要是打印出EXE所在的堆栈地址,DLL和EXE如何协作。
#include "stdafx.h"
#include "../TestDelayDll/TestDelayDll.h"char abc;
int cba = 0x0000a0a0;
int _tmain(int argc, _TCHAR* argv[])
{
int b = 0x1111;
char *ok = "wo shi shui";
char *xx = new char[4096];
printf("[exe]no-init=%X, init=%X, stack=%X, heap=%X, %X:%s\r\n", &abc, &cba, &b, xx, ok, ok);
//load
fnTestDelayDll();
delete []xx;
getchar();
return 0;
}
test.dll主要代码:
int fnTestDelayDll()
{
int sp = 0xabcd;
char *a = new char[1024];
printf("[dll]no-init=%X init=%X stack=%X heap=%X\r\n", &nTestDelayDll, &g_testValue, &sp, a);
delete []a;
return 42;
}
上面两段代码可能会被编译器优化掉,因此你必须关闭编译器优化才能看到真实的情况。
结果如下:
进程的内存结构如下(我自己整理的,可能不是很对,堆是各个线程公用的,栈是一个线程一个,用完还给系统):
我自己的理解是这样的,在执行一个EXE程序时,父进程负责为EXE创建进程地址空间,并将EXE文件映射到此空间,当然是以EXE文件所在的存储器为后备存储器,映射过程中是有一定流程的,首先将PE文件头(这个头包含了所有的小头)直接映射到400000开始的虚拟地址空间,然后根据头中的信息将4个段分别映射到地址空间,注意,这里的映射并不是和PE文件存储结构一致,而是根据PE头中的信息进行映射,例如.txt段在PE中偏移是400,那么按道理应该映射到400000+400=400400这是地址,可是实际上是映射到401000地址,就是因为PE头中已经指示了必须映射到此地址,其他各段以此类推。
映射完成后,系统开始找导入表,按照导入表中的信息,导入EXE依赖的DLL,例如需要导入Test.dll文件,这时候执行的映射和EXE一致,只不过地址为定位从10000000开始映射,实际上具体的系统可能不是从这边映射,因为一般我们编写的DLL都从10000000开始映射,这样2个以上的DLL会出现重叠,因此这时候系统会重定位该DLL,重定位DLL后,因为载入地址不在是10000000,所以DLL代码中所有可能引用地址的地方都需要更新,这时候系统会找到DLL的重定位段,根据重定位段记录的地址表,逐个更新地址。
对于DLL重定位我也做了一实验,让testdll.exe加载了两个DLL文件,这两个DLL文件镜像载入的虚地址都是10000000,测试发现,第一个DLL被正常映射到10000000地址,一切都是按照PE约定的虚拟地址进行映射,而第二个DLL镜像被映射到了3b0000地址,也就是被映射到了exe镜像的上面,哈哈,.txt代码段本应该是10001000的,却被映射到了3b1000地址,看来DLL镜像在映射的时候被重定位了,并且还发现,原来代码段中的内容被改掉了,果然是系统修复了地址,如图:
原始汇编:
看来DLL重定位确实会动态修改汇编代码段内容.
总体虚拟内存如下: