加载dll失败
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了加载dll失败相关的知识,希望对你有一定的参考价值。
我的电脑一开机就显示加载DLL失败程序推出,我不清楚DLL是什么.只是不能在网站下载东西了,每次下载都显示那句话.请高手帮帮我,怎么解决啊?谢谢.
按照下面的去做你试试.一、开始菜单-运行-输入msconfig回车
在里边的启动栏里把**.dll前面的勾去掉就行了
1、开始——运行——msconfig——启动——把加载项***.dll的那个勾勾去掉。 重启电脑,通常到这就可以了,如果还弹出来再进行第二步
2、开始——运行——regedit 在下面的位置删除相应键值: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
二、那应该是一个已被删除了的病毒或间谍软件。但也有可能是你卸载某个软件不正确留下的残留.
如果你想以后不要看到它:
开始-运行-键入regedit-在上方的编辑菜单打开选查找,将jtnhr.dll输入,搜索,发现的全删掉。就行了。
或: 开始-运行-msconfig(即系统配置程序)-启动,看看里面的东西,找到哪个你说的东西,把前面的勾去掉,Ok
重启看一下吧 (如果找不到它,那它就已经不存在了,就清理注册码) 参考技术A 电脑病毒、木马、流氓软件没杀干净,注册表残留了无用的DLL文件。
【处理】
(一)先手工清理
1、开始--运行--输入“msconfig”打开启动项,
在启动项里面找到包含加载时出错的“**.dll”文件,把它前面的对勾去掉。
【注意】如果启动项没有的话,请接着往下
2、开始-运行-输入“regedit”打开注册表。(此操作前先备份注册表)
在菜单-“编辑”--“查找”-输入-“**.dll”(输入加载时出错的),删除它,按F3,继续查找,直到删完,
确定-重起电脑。
【注意】注册表也没找到的话,请用下面的方法
(二)用软件查找
1、IceSword
1.22
中文版
下载地址
http://www.onlinedown.net/soft/53325.htm
打开冰刃
“IceSword.exe”,点软件左下角的“文件”,找到加载时出错的“**.dll”文件位置。文件“**.dll”上右键点“强制删除”。
2、AutoRuns
8.73
汉化版
下载地址
http://www.newhua.com/soft/21022.htm
运行“autoruns.exe”,在所有登陆项“全部”里找--加载时出错的“**.dll”文件和
所有“找不到.DLL文件”(这里要特别注意)项目,右键点删除 参考技术B DLL是MS系统的动态链接库,如果加载失败无非三种情况
1。文件被破坏
2。病毒破坏此文件
3。安装OS的时候出现问题
解决办法:
1。重新装操作系统
2。从别的,正常的机器上拷贝一个DLL,放到指定目录(必须是你丢失的那个DLL)
3。杀毒
补充下:一般出现这种情况都是在杀毒后
木马或者病毒有它的启动项
在杀毒后病毒木马不存在了但是启动项还在,就会出现加载错误的提示
你可以在运行中输入MSCONFIG把启动中的***.DLL禁用,哪个出错禁哪个不要乱禁 参考技术C 你下载一个超级兔子删除一下流氓软件,就可以了!这些问题可能都是流氓软件原因,或是病毒的原因。你下载一个超级兔子清理一下就应该差不多了! 参考技术D 装系统的时候是不是跳过了什么步骤?
看看是不是那个RPC服务关了?
不是系统的问题就卸载了超级旋风
用另存为
下载一个
迅雷
试试
调试实战 —— dll 加载失败之全局变量初始化篇
调试实战 —— dll 加载失败之全局变量初始化篇
前言
最近项目里总是遇到 dll
加载不上的问题,原因各种各样。今天先总结一个虽然不是项目中实际遇到的问题,但是却非常经典的问题。其它几种问题,后续慢慢总结。
示例代码包含一个 exe
工程,两个 dll
工程。 exe
会加载两个 dll
并调用它们的导出函数(GetCallCount
),结果只有一个 dll
的导出函数被成功调用。会是什么原因呢?
现象
运行效果如下图:
通过 dumpbin
已经确认两个 dll
都有名为 GetCallCount
的函数。但是只有一个调用成功了,另外一个却调用失败。
使用 process explorer
观察 dll
加载情况,发现只加载了一个 dll
,没发现另外一个 dll
。
对于这个问题,如果我们使用 process monitor
观察整个加载过程,看到的都是 Success
。如下图:
说明,加载正常,在本地找到了这个文件,并正确的映射到内存空间中了。但为什么在进程中观察不到这个 dll
呢?是时候上调试器了。
上调试器
直接在 vs
中按 F5
启动,果然中断到 vs
中了。
从上图右侧部分,我们可以看到完整的调用栈。
这里简单介绍下相关代码。在 GlobalVariableInitializeOrder.cpp
的第 15
行调用了 HMODULE hDll2 = LoadLibraryA("GlobalVariableInitializeOrderDll2.dll");
加载对应的模块。
CommonTest2.cpp
的第 10
行定义了全局变量 CTest2 g_t2;
(在 dll
中),问题就出在这个全局变量的初始化代码中。
从上图左侧部分,我们可以得知错误代码是 0xc0000005
,内存访问异常。访问的地址是 0x00000004
,对应的指令位置是 0x001EA6DB
。
从上图中的反汇编看,确实是挂在了 001EA6DB mov eax,dword ptr [eax]
。因为 eax
的值是 4
,我们需要查明 eax
为什么的值是 4
。相信很多小伙伴都知道,eax
用来保存函数调用的返回值。我们可以把注意力集中到 0x001EA6D6
处的 call
指令了,调用的是成员函数 _Root()
。
查看 vs
提供的源码,如下:
_Nodeptr& _Root() const
{ // return root of nonmutable tree
return (this->_Parent(this->_Myhead));
}
我们可以发现 _Root()
内部简单的调用了 _Parent()
函数,并把 this->_Myhead
当作参数传递过去了。再查看下 _Parent()
函数的源码,如下:
static _Nodepref _Parent(_Nodeptr _Pnode)
{ // return reference to parent pointer in node
return ((_Nodepref)_Pnode->_Parent);
}
务必注意: _Parent()
的返回值类型是 _Nodepref
,返回的是引用(最后三个字母 ref
已经说明了一切)!相当于返回的是 _Pnode->_Parent
的地址!我们可以查看 _Nodepref
的定义:typedef _Nodeptr& _Nodepref;
。
所以 _Root()
函数相当于 &(this->_Myhead->_Parent)
。我们来观察下 this
各个成员的值。
可以看到 _Myhead
的值是 0
,类型是 std::_Tree_node<...>
。
我们再看下 _Tree_node
的定义:
template<class _Value_type, class _Voidptr>
struct _Tree_node
{
_Voidptr _Left; // offset: 0x0
_Voidptr _Parent; // offset: 0x4
_Voidptr _Right; // offset: 0x8
char _Color; // offset: 0xC
char _Isnil; // offset: 0xD
_Value_type _Myval; // offset: 0x10
private:
_Tree_node& operator=(const _Tree_node&);
};
从 _Tree_node
的定义可知, _Parent
的偏移是 4
(因为是 32
位的程序,如果是 64
位,那么是 8
)。
综上,地址 001EA6D6
处的 call
指令反回了 4
。接下来的两条指令是把返回值赋给局部变量 _Nodeptr _Pnode
。但是在执行第一条汇编指令 mov eax,dword ptr [eax]
时就挂了,因为 eax
的值是 4
,正常情况下访问 0x00000004
处的值当然会挂掉了。
至此,我们知道了崩溃的直接原因——访问非法地址。但是根本原因是什么呢?为什么 _Myhead
是 0
呢? 我猜测是因为 map
还没有初始化。但是该如何证实这个猜测呢?
继续深入
CTest2
的构造函数里调用的是 CTest1::GetMap()
,GetMap()
内部会返回 CTest1
的静态变量 static std::map<std::string, std::string> s_manager;
的引用。
如果能证明在 CTest2::g_t2
初始化时,CTest1::s_manager
还没初始化,那么我们就证实了我们的猜测。
我想到两个办法:
- 在
map
的构造函数中输出一条日志。在调用g_t2
的构造函数时,查看是否有我们在map
中新加的日志。 - 明确每个全局变量的初始化顺序。
第一种方法比较简单,直接修改 vs
提供的源码即可,注意修改只读属性。本文以第 2
种方法为例展开。
全局变量初始化简介
本小节根据上面的调用栈简单的介绍全局变量的初始化过程(只介绍我们关心的部分)。
不知道各位小伙伴儿是否记得上面的调用栈。切换到 8
号栈帧,如下图:
可以发现,在 __DllMainCRTStartup()
函数中,当 dwReason == DLL_PROCESS_ATTACH
或者 dwReason == DLL_THREAD_ATTACH
的时候,会调用 _CRT_INIT()
函数。_CRT_INIT()
会执行运行时库的初始化相关功能,比如,初始化全局变量。然后才会调用用户提供的 DllMain()
函数。
继续切换到 7
号栈帧,如下图:
通过注释可知,_initterm()
是在调用 C++ constructors
。
我们继续切换到 6
号栈帧,如下图:
根据注释猜测,应该是在依次调用每个全局变量的初始化函数。pfbegin
指向了保存全局变量初始化函数的表格的起始位置,pfend
指向最后一个有效位置的下一个位置,跟标准库中的容器多么相似啊。如果 *pfbegin
的值不为 0
,说明表格对应的位置有有效的初始化函数,需要调用,否则就跳过。
在 vs
中,我们想遍历出这个表格的内容有些费劲。是时候请 windbg
出场了。
windbg 出场
在使用 windbg
之前一定要设置好符号路径,否则很多内容看不到。
使用 windbg
打开要运行的程序,在命令窗口输入 bm GlobalVariableInitializeOrderDll2!_CRT_INIT
,埋伏好断点后执行 g
命令继续运行。
很快,就中断到我们设置好的断点处了。在调用 _initterm()
的地方设置好断点,执行 g
命令(也可以和 vs
一样按 F5
),断下来后,单步进入 _initterm()
函数,执行 dv
查看局部变量。
从输出结果可知,pfbegin = 0x001f6000
,pfend = 0x001f6250
。然后我们就可以用强悍的 dps
来查看pfbegin
和 pfend
之间的内容了。在命令窗口执行,dps 0x001f6000 0x001f6250
。因为有很多空项,这里只截取中间部分。
我们可以很明显的看到,g_t2
的构造函数在前,s_manager
的构造函数在后。
至此,已经证实了我们之前的猜想。
对比强化
因为工程 GlobalVariableInitializeOrderDll1
和工程 GlobalVariableInitializeOrderDll2
代码一模一样,只有一点点的不同,就是这一点不同导致了一个 dll
可以正常使用,另外一个却不能正常使用。
我们可以用相同的手法观察 GlobalVariableInitializeOrderDll1.dll
的初始化过程。
在命令窗口输入 bm GlobalVariableInitializeOrderDll1!_CRT_INIT;g
,埋伏好断点后运行起来。再次中断后,使用相同的办法进入_initterm()
函数,通过 dv
命令得到 pfbegin = 0x10026000
和 pfend = 0x10026250
的值,然后执行 dps 0x10026000 0x10026250
,如下图(同样有很多空项,只截取了中间部分):
我们发现,s_manager
的构造函数在前,g_t2
的构造函数在后。
修复
我们应该从根本上消除对全局变量的依赖,只需要把 s_manager
放到 GetMap()
中就可以了。
static std::map<std::string, std::string>& GetMap()
{
static std::map<std::string, std::string> s_manager;
return s_manager;
}
但有时候,由于各种各样的原因,我们不能消除这种依赖。我们还可以调整全局变量的初始化顺序。只要有办法让 g_t2
在 s_manager
之后再初始化就可以了。对比两个 dll
工程文件,我们发现有一处关键的不同点。
在能正常加载的 dll
对应的工程中, Test1.cpp, Test2.cpp
出现的顺序是 Test1.cpp, Test2.cpp
,在不能正常加载的 dll
对应的工程中,出现的顺序是 Test2.cpp, Test1.cpp
。调整 dll2.vcxproj
中的文件顺序和 dll1.vcxproj
一样,再次编译运行,一切顺利。
动手实战
强烈建议你也动手实战一番,毕竟纸上来的终觉浅。如果你也想动手实战,可以下载完整的工程文件,使用 vs2013
编译运行即可。如果没装 vs2013
,也可以手动改成其它版本的 vs
。
完整的测试工程下载链接:
百度云 链接: https://pan.baidu.com/s/1gW1dZsNYZoo0s_rfaO2Jzg 提取码: 7irh
CSDN 链接:https://download.csdn.net/download/xiaoyanilw/12405380
总结
-
永远不要让一个全局变量依赖另外一个全局变量。
-
全局变量是在
DllMain
或者main
函数执行前进行初始化的。 -
在
32
位程序中,一般使用eax
保存函数的返回值。 -
dps
命令可以按地址遍历给定范围的内容。 -
dv
命令可以查看局部变量和参数。
参考资料
如果有小伙伴儿对全局变量初始化感兴趣,可以参考以下几篇文档:
以上是关于加载dll失败的主要内容,如果未能解决你的问题,请参考以下文章
VC++ 解决dll库动态库加载失败问题(调用LoadLibrary加载失败)(附源码)