一、目的
在应用程序调用DLL中的函数之前、必须把该DLL映射到进程的地址空间中。分两种方式:隐式链接和显示运行时链接。
二、隐式链接
1、DLL模块显式声明需要导出的函数和变量(dllexport)
2、DLL模块根据导出的内容生成导出段
rodinal(函数编号:GetProcAddress可以用此得到函数地址)
hint(索引:便于查找提高性能)
RVA(相对地址:DLL基地址确定后,加上此地址就是函数or变量进程地址空间中的地址了)
name(函数名称:GetProcAddress可以用此得到函数地址)
3、DLL同时在链接阶段生成.lib文件,里面仅仅包含dll导出的符号,用于链接可执行文件时确定导入符号源自哪个DLL
(注:DLL在导出函数时会对函数进行重命名,如果不想改编函数名,有两个办法
1、创建.def文件,包含导出函数名即可;
2、利用如#pragma comment(linker,"/export:MyFunc=_MyFunc@8") 导出函数名
)
4、可执行文件或者其他DLL包含DLL头文件(此时头文件被声明为dllimport),引用导出函数或者变量(可显式声明extern 或者dllimport,略微提升性能)
5、可执行文件或者DLL根据引用的外部符号创建导入段;导入段包含dll名称,以及从此dll导入的符号的名称和地址。
6、启动可执行文件时 :
首先为进程创建虚拟地址空间,将可执行文件映射到地址空间;
然后扫描导入段,将其他DLL映射到地址空间;(可预生成基地址,减少DLL基地址重定位,在程序安装阶段)
最后根据DLL基地址和DLL导出段导出符号相对地址相加得到最终符号地址,修复可执行文件导入段导入符号地址。线程最终使用的就是导入符号地址;(可使用DLL绑定技术,预生成导入符号地址,在程序安装阶段)
三、显式链接
1、LoadLibrary 显式将DLL映射到地址空间中(GetModuleHandle只是获取已映射的DLL)。
2、FreeLibrary 显式卸载DLL(有时候某DLL创建了一个线程,如果该线程Free此DLL后,则FreeLibrary后面的代码就不存在了,因此有FreeLibraryAndExitThread函数)。
3、GetProcAddress 获取某DLL的一个导出函数(通过函数编号,或者是名字)。
4、DLL入口点DllMain(映射DLL时,卸载DLL时,创建线程时和销毁线程时都会进入,如果调用了DisableThreadLibraryCalls则在创建线程时和销毁线程时不会进入) 。
四、延迟载入DLL技术
1、使用/Lib:DelayImp.lib 将_delayLoadHelper2嵌入到代码中(此函数调用LoadLibrary 和GetProcAddress )。
2、使用/DelayLoad:MyDll.dll 将MyDll.dll从导入段中去掉,增加一个延迟段,对延迟载入DLL的函数的调用实际上调用的就是_delayLoadHelper2。
3、使用/Delay:unload 则可以使用_FUnloadDelayLoadedDLL2 来卸载延迟载入DLL,这样它会清空延迟段的函数地址。
4、_delayLoadHelper2 可以调用挂钩函数
五、其他
1、函数转发器:#pragma comment(linker,"/export:MyFunc=OtherDLL._MyFunc") 导出名为MyFunc的函数,但是此函数的入口点在OtherDLL中的_MyFunc
2、已知的DLL:LoadLibrary(L"SomeLib") 会通过正常规则搜索,LoadLibrary(L"SomeLib.dll") 会去掉后缀,然后在KnownDLLs 注册表项查找相同键值得到真正的DLL名称,然后在DllDirectory路径下搜索DLL,如果找不到则返回失败。
3、DLL重定位:为了强制系统优先从应用程序目录开始查找,可以在应用程序目录创建一个名为xx.exe.load的文件来告诉LoadLibrary 从此应用程序目录开始搜索。