之前写过一篇DLL 加载卸载的文章,不太好,重写一下,更深入一点。
两个组或者两个公司独立的开发可能用来组成同一个产品的组件,但是他们必须独立的构建、测试和提交他们的工作。组件粒度很难是正确的且与怎么对组件是最好的这样的问题无关。取而代之的是,一个组件是怎样才能对公司最好(团队不喜欢使用多个dll,且他们想自主的写一个单独的DLL,以便他们的测试人员可以对测试组件所意为的工作量感到满意)以及怎样才对一个高性能的团队最好的集合(一个功能一个DLL将使系统的性能下降不少)。
当一个加载器发现这种现象的时候,它会怎么做?
谁更深,谁被更早的添加到列表中??
装了火绒就是上面的效果,因为火绒会注入dtrampo.dll 到进程中。
不装火绒的时候就是这种状态。
为什么DLL 以错误的顺序卸载?
当一个程序开始运行,或者一个DLL 被加载,加载器为DLL 或者程序建立一个依赖树。之后加载器找到一个正确的加载顺序,使得,直到一个DLL 的所有的依赖的DLL 都被加载了之后该DLL才会被初始化。如果你有一个循环依赖,下面将思考这个问题??如果你在DLL_PROCESS_ATTACH 通知中调用LoadLibrary,将使得这个情形更加混乱,而且,会导致死锁。
首先:循环加载:
DemoProgram:
#include <stdio.h>
#pragma comment(lib,"helloworld.lib")
extern "C" __declspec(dllimport) void hello();
int main()
{
printf("DemoProgram world\r\n");
hello();
getchar();
return 0;
}
helloworld.dll
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
extern "C" __declspec(dllexport) void hello()
{
printf("Hello World\r\n");
return;
}
#pragma comment(lib,"ref_hello.lib")
extern "C" __declspec(dllimport) void ref_hello();
BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
printf("HelloWorld Module\r\n");
// hello();
ref_hello();
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
ref_hello.dll:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <stdio.h>
#pragma comment(lib,"helloworld.lib")
extern "C" __declspec(dllimport) void hello();
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
printf("ref_hello Module\r\n");
hello();
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) void ref_hello()
{
printf("ref_hello \r\n");
}
输出为:
ref_hello Module
Hello World
HelloWorld Module
ref_hello
DemoProgram world
Hello World
我们改变DemoProgram ,修改对helloworld 模块的引用为:对ref_hello 模块的引用:
#include <stdio.h>
// #pragma comment(lib,"helloworld.lib")
// extern "C" __declspec(dllimport) void hello();
#pragma comment(lib,"ref_hello.lib")
extern "C" __declspec(dllimport) void ref_hello();
int main()
{
printf("DemoProgram world\r\n");
ref_hello();
getchar();
return 0;
}
输出如下:
HelloWorld Module
ref_hello
ref_hello Module
Hello World
DemoProgram world
ref_hello
我们再次改变DemoProgram,使其同时引用两个模块:
#include <stdio.h>
#pragma comment(lib,"helloworld.lib")
extern "C" __declspec(dllimport) void hello();
#pragma comment(lib,"ref_hello.lib")
extern "C" __declspec(dllimport) void ref_hello();
int main()
{
printf("DemoProgram world\r\n");
ref_hello();
hello();
getchar();
return 0;
}
输出为:
ref_hello Module
Hello World
HelloWorld Module
ref_hello
DemoProgram world
ref_hello
Hello World
修改上面对于两个模块的引用的顺序:
#include <stdio.h>
#pragma comment(lib,"ref_hello.lib")
extern "C" __declspec(dllimport) void ref_hello();
#pragma comment(lib,"helloworld.lib")
extern "C" __declspec(dllimport) void hello();
int main()
{
printf("DemoProgram world\r\n");
ref_hello();
hello();
getchar();
return 0;
}
输出如下:
HelloWorld Module
ref_hello
ref_hello Module
Hello World
DemoProgram world
Hello World
ref_hello
似乎发现了什么。。。。。。。。。。。。。。。。。。
其PE 文件中的DLL 的加载顺序同上,
接下来在项目属性中修改两个模块的顺序如下:
ref_hello Module
Hello World
HelloWorld Module
ref_hello
DemoProgram world
Hello World
ref_hello
再次修改顺序如下:
输出结果为:
HelloWorld Module
ref_hello
ref_hello Module
Hello World
DemoProgram world
Hello World
ref_hello
我们似乎又发现了什么。。。。。。。。。。。。。。。。。。。。。。。。。。
使用CFF Explorer_CN.exe 查看两种EXE 其导入表如下:
再对比EXE 的输出我们可以发现导入顺序的玄机。
这个没有考虑到一个问题是:如果A,B 循环依赖,且在DLL_PROCESS_DETACH 消息中依然引用对方的代码,将可能导致程序崩溃,因为引用了已经卸载的DLL 的代码。
下一篇:
1. 如何在不修改PE 文件的情况下修改其导入的模块的加载顺序??????
2. dllmain 中调用LoadLibrary