将DLL固定在内存中(增加引用计数)

时间:2014-07-04 12:17:45

标签: debugging dll memory-leaks windbg access-violation

我正在尝试运行应用程序,但应用程序因访问冲突而退出。在调试器中运行应用程序我可以看到这是由卸载的库引起的。我等不及应用程序的下一个版本了,所以我正在尝试解决这个问题。

我想知道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中的字符串,我键入命令。这又意味着我可能意味着分配一些内存来存储字符串,所以这可能会变得更复杂。

3 个答案:

答案 0 :(得分:7)

在windbg中使用.call对我来说一直很挑剔。我相信你遇到了麻烦,因为kernel32只有公共符号,所以调试器不知道它的参数是什么样的。

让我们看一些替代方案......

简单方法

您可以使用像Process Hacker这样的工具,我认为这是任何调试器工具箱的一个很好的补充。它可以选择将DLL注入进程。

Process Hacker Inject 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将不会被释放。