我正在尝试运行应用程序,但应用程序因访问冲突而退出。在调试器中运行应用程序我可以看到这是由卸载的库引起的。我等不及应用程序的下一个版本了,所以我正在尝试解决这个问题。
我想知道WinDbg是否提供了一种增加加载模块引用计数的方法,类似于C ++ LoadLibrary()调用。然后,我可以打破模块加载并增加受影响的DLL上的引用计数,看看我是否可以使用该应用程序。
我已经在WinDbg帮助中查找了以.load
,!load
,.lock
,!lock
,.mod
和!mod
开头的命令。 .load会将DLL作为扩展加载到调试器进程中,而不是加载到目标进程中。
更新
忘记提及我没有源代码,因此我不能简单地将LoadLibrary()调用实现为变通方法并重新编译。
Hans Passant的评论引导我.call
,我试图像
.call /v kernel32!LoadLibraryA("....dll")
但它提供了错误消息
符号不是'.call / v kernel32中的函数!LoadLibraryA(“.... dll”)'
更新2
.call
中文件名的字符串可能是指向目标进程中某些内存的指针,而不是驻留在WinDbg.exe中的字符串,我键入命令。这又意味着我可能意味着分配一些内存来存储字符串,所以这可能会变得更复杂。
答案 0 :(得分:7)
在windbg中使用.call
对我来说一直很挑剔。我相信你遇到了麻烦,因为kernel32
只有公共符号,所以调试器不知道它的参数是什么样的。
让我们看一些替代方案......
您可以使用像Process Hacker这样的工具,我认为这是任何调试器工具箱的一个很好的补充。它可以选择将DLL注入进程。
在幕后,它调用CreateRemoteThread
在目标进程中生成一个线程,该线程在所选DLL上调用LoadLibrary
。运气好的话,这会增加模块引用次数。您可以通过在dll注入之前和之后运行LoadCount
命令来验证windbg中!dlls
是否已增加。
您还可以深入了解Windows用于跟踪进程加载的模块并使用LoadCount
进行操作的内部数据结构。这在Windows版本之间发生了变化,这是一个严重的禁忌。但是,我们正在调试,所以,到底是什么? 让我们这样做。
首先获取带有!dlls
的已加载模块的列表。假设我们关心your.dll
;我们可能会看到类似的东西:
0x002772a8: C:\path\to\your.dll
Base 0x06b80000 EntryPoint 0x06b81000 Size 0x000cb000 DdagNode 0x002b3a10
Flags 0x800822cc TlsIndex 0x00000000 LoadCount 0x00000001 NodeRefCount 0x00000001
我们可以看到当前的加载计数为1.要修改它,我们可以使用模块路径之前打印的地址。它是该模块的进程ntdll!_LDR_DATA_TABLE_ENTRY
的地址。
r? @$t0 = (ntdll!_LDR_DATA_TABLE_ENTRY*) 0x002772a8
现在,您可以将LoadCount成员更改为更大的成员:
?? @$t0->LoadCount = 2
但是,正如我所说,这些东西随着Windows的新版本而变化。在Windows 8上,LoadCount
成员已移出_LDR_DATA_TABLE_ENTRY
并进入新的ntdll!_LDR_DDAG_NODE
结构。取代它,现在有一个ObsoleteNodeCount
,这不是我们想要的。
在Windows 8上,我们将改为执行以下命令:
?? @$t0->DdagNode->LoadCount = 2
还有时间检查我们的工作......
0x002772a8: C:\path\to\your.dll
Base 0x06b80000 EntryPoint 0x06b81000 Size 0x000cb000 DdagNode 0x002b3a10
Flags 0x800822cc TlsIndex 0x00000000 LoadCount 0x00000002 NodeRefCount 0x00000001
真棒。现在是2。这将教会FreeLibrary
关于在我们说它之前卸载DLL的教训。
首先尝试简单的方法。如果这不起作用,您可以开始查看Windows用于跟踪此类内容的内部数据结构。我没有提供希望你真正尝试它的艰难方法,但它可能会让你在!dlls
命令和未来的数据结构中更加舒适。
仍然,所有修改LoadCount
的人都会确认你看到DLL在它应该被卸载之前就已经卸载了。如果在人为地增加LoadCount
之后问题就消失了,这意味着你已经证实了你的理论,那么你将不得不采取不同的方法来调试它 - 找出卸载的时间和原因。
答案 1 :(得分:3)
编译时链接的dll通常具有-1(0xffff)的LoadCount,并且不能通过FreeLibrary卸载
因此您可以利用loadModule事件来破坏动态加载模块并在事件期间增加LoadCount
InLoadOrderModuleList的闪烁(在此过程中加载的最后一个dll)在ntdll初始中断时!Dbgbreak()xp-sp3用于使用dll的任意控制台应用程序
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY FullDllName LoadCount @@((( @$peb)->Ldr)->InLoadOrderModuleList.Blink)
+0x024 FullDllName : _UNICODE_STRING "C:\WINDOWS\system32\GDI32.dll"
+0x038 LoadCount : 0xffff <----------- not unloadable via FreeLibrary
设置特定模块加载中断
0:000> sxe ld skeleton
0:000> g
ModLoad: 10000000 10005000 C:\skeleton.dll
ntdll!KiFastSystemCallRet:
7c90e514 c3 ret
LoadModule在MapSection上中断,因此Ldr尚未更新
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY FullDllName LoadCount @@((( @$peb)->Ldr)->InLoadOrderModuleList.Blink)
+0x024 FullDllName : _UNICODE_STRING "C:\WINDOWS\system32\GDI32.dll"
+0x038 LoadCount : 0xffff
直到Ldr更新
0:000> gu;gu;gu
ntdll!LdrpLoadDll+0x1e9:
7c91626a 8985c4fdffff mov dword ptr [ebp-23Ch],eax ss:0023:0013fa3c=00000000
闪烁显示上次加载的模块通知loadCount 0尚未更新
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY FullDllName LoadCount @@((( @$peb)->Ldr)->InLoadOrderModuleList.Blink)
+0x024 FullDllName : _UNICODE_STRING "C:\skeleton.dll"
+0x038 LoadCount : 0
转储模块的LoadEntry
0:000> !dlls -c skeleton
Dump dll containing 0x10000000:
**0x00252840:** C:\skeleton.dll
Base 0x10000000 EntryPoint 0x10001000 Size 0x00005000
Flags 0x00000004 LoadCount 0x00000000 TlsIndex 0x00000000
LDRP_IMAGE_DLL
任意增加负载计数和redump(尚未调用进程附加)
0:000> ed 0x252840+0x38 4
0:000> !dlls -c skeleton
Dump dll containing 0x10000000:
0x00252840: C:\skeleton.dll
Base 0x10000000 EntryPoint 0x10001000 Size 0x00005000
Flags 0x00000004 LoadCount 0x00000004 TlsIndex 0x00000000
LDRP_IMAGE_DLL
运行二进制文件
0:000&GT;克
使用ctrl + break 将dll加载到进程中断中
发送闯入,等待30秒...... (aa0.77c):中断指令异常 - 代码80000003(第一次机会)
NTDLL DbgBreakPoint! 7c90120e cc int 3
转储并查看系统已将loadcount更新为我们的计数+ 1还有进程附加已被调用
0:001> !dlls -c skeleton
Dump dll containing 0x10000000:
0x00252840: C:\skeleton.dll
Base 0x10000000 EntryPoint 0x10001000 Size 0x00005000
Flags 0x80084004 LoadCount 0x00000005 TlsIndex 0x00000000
LDRP_IMAGE_DLL
LDRP_ENTRY_PROCESSED
LDRP_PROCESS_ATTACH_CALLED
btw use ken johnsons (skywing)
sdbgext !remotecall
而不是.call
它不需要私人符号
.load sdbgext !remotecall kernel32!LoadLibraryA 0“c:\ skeleton.dll”; g
应该在过程中加载dll
或使用
!loaddll "c:\\skeleton.dll" from the same extension
kernel32!LoadLibraryA() will be run when execution is resumed
0:002> g
kernel32!LoadLibraryA() [conv=0 argc=4 argv=00AC0488]
kernel32!LoadLibraryA() returned 10000000
答案 2 :(得分:1)
最简单的方法 - 获取.dll路径和LoadLibrary。 它将增加.dll引用计数,并且.dll将不会被释放。