findwindow函数 vb 获取不到句柄_使用Windbg分析C++句柄泄露问题

引言

句柄泄露是因为创建句柄之后,没有及时销毁句柄。因此,排查句柄泄露的原因,重点需要找到是哪些句柄发生了泄露,以及创建这些句柄的代码。本文将通过一个例子来演示使用Windbg分析句柄泄露的方法。

检测句柄泄露

检测进程使用句柄数量的工具有很多,如果看到句柄的数量在持续增加,那就是发生了句柄泄露。下面将对进程id为20298(0x51c0)的进程进行分析。

任务管理器

句柄列默认是隐藏的,需要通过配置把句柄列显示。

89901df80678900f02aec3cd31bddb81.png

Process Explorer

Process Explorer查看进程详细信息,在Performance的内容中,包括了进程的句柄数量统计。

3e227afe6e819f872e907142fad2104c.png

Windbg

需要先通过主菜单绑定进程,然后通过!handle命令查看当前进程的已使用的句柄数量

0:001> !handle
135 Handles
Type Count
None 6
Event 8
File 7
Directory 3
Key 4
Thread 92
IoCompletion 3
TpWorkerFactory 3
ALPC Port 1
WaitCompletionPacket 8

通过上面数据可以看出,当前已打开的句柄的主要为Thread。而通过~*查询发现,当前进程只有2个线程,打开这么多线程句柄是异常的。

0:001> ~*
0 Id: 51c0.85a4 Suspend: 1 Teb: 00626000 Unfrozen
Start: handle_leak!ILT+825(_mainCRTStartup) (00a1133e)
Priority: 0 Priority class: 32 Affinity: fff
. 1 Id: 51c0.3ad8 Suspend: 1 Teb: 0063b000 Unfrozen
Start: ntdll!DbgUiRemoteBreakin (7775abe0)
Priority: 0 Priority class: 32 Affinity: fff

使用Windbg分析Thread句柄泄露的原因

!htrace命令可以跟踪创建和销毁句柄的调用栈,通过调用栈可以判断句柄泄露的原因。首先,通过!htrace -enable打开功能,并且获取当前所有的句柄的快照。然后通过g命令继续运行程序。

0:001> !htrace -enable
Handle tracing enabled.
Handle tracing information snapshot successfully taken.
0:001> g

运行一段时间后,通过ATL+DEL快捷键暂停程序运行。通过!htrace -diff命令可查看创建快照之后的所有句柄操作。

0:001> !htrace -diff
Handle tracing information snapshot successfully taken.
0x17 new stack traces since the previous snapshot.
Ignoring handles that were already closed…
Outstanding handles opened since the previous snapshot:
-————————————-
************************
-————————————-
Handle = 0x00000244 - OPEN
Thread ID = 0x000085a4, Process ID = 0x000051c0
0x5e1c8322: +0x5e1c8322
0x5e1c7c83: +0x5e1c7c83
0x5ddd2d15: +0x5ddd2d15
0x8fcbe5d4: +0x8fcbe5d4
0x8f615ae4: +0x8f615ae4
0x8f617123: +0x8f617123
0x776a1783: +0x776a1783
0x776a1199: +0x776a1199
0x8f61c77a: +0x8f61c77a
0x8f61c637: +0x8f61c637
0x8fcf3fb3: +0x8fcf3fb3
0x8fce1db5: +0x8fce1db5
0x8fc91853: +0x8fc91853
0x8fc917fe: +0x8fc917fe
0x7772300c: ntdll!NtOpenThread+0x0000000c
0x7570f208: KERNELBASE!OpenThread+0x00000048
-————————————-
Displayed 0x17 stack traces for outstanding handles opened since the previous snapshot.

从上面的数据显示,在这段时间内,有23(0x17)个句柄发生了泄露。也可以通过!handle验证一下现有句柄的差值,Thread句柄数量已经从92增长到了115个,差值为23个。

0:001> !handle
158 Handles
Type Count
None 6
Event 8
File 7
Directory 3
Key 4
Thread 115
IoCompletion 3
TpWorkerFactory 3
ALPC Port 1
WaitCompletionPacket 8

!handle命令也可查看单个句柄的详情,里面有创建句柄的线程信息,在分析多线程程序时会用得着。

0:001> !handle 0x00000244 0xf
Handle 244
Type Thread
Attributes 0
GrantedAccess 0x1fffff:
Delete,ReadControl,WriteDac,WriteOwner,Synch
Terminate,Suspend,Alert,GetContext,SetContext,SetInfo,QueryInfo,SetToken,Impersonate,DirectImpersonate
HandleCount 131
PointerCount 130760
Name <none>
Object Specific Information
Thread Id 51c0.85a4
Priority 10
Base Priority 0
Start Address a1133e handle_leak!ILT+825(_mainCRTStartup)

现在的问题是,通过栈信息,只能看到KERNELBASE!OpenThread,看不到更上层的调用栈。由于本次句柄泄露是可以重现的,因此,可通过打断点的方式来查找句柄泄露的原因。在ntdll!NtOpenThread+0x0000000c中打上断点之后,继续运行程序,等待断点命中。断点命中之后,可通过k命令查看详细的调用栈。

0:001> bp ntdll!NtOpenThread+0x0000000c
0:001> g
Breakpoint 0 hit
eax=00000000 ebx=00623000 ecx=93e70000 edx=00000000 esi=008ff5c0 edi=008ff698
eip=7772300c esp=008ff574 ebp=008ff5ac iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
ntdll!NtOpenThread+0xc:
7772300c c21000 ret 10h
0:000> k
# ChildEBP RetAddr
00 008ff570 7570f208 ntdll!NtOpenThread+0xc
WARNING: Stack unwind information not available. Following frames may be wrong.
01 008ff5ac 00a11767 KERNELBASE!OpenThread+0x48
02 008ff698 00a118a6 handle_leak!OpenThreadFun+0x47 [D:githubsampleswindowshandle_leakmain.cpp @ 5]
03 008ff76c 00a12013 handle_leak!main+0x36 [D:githubsampleswindowshandle_leakmain.cpp @ 14]
04 008ff78c 00a11e67 handle_leak!invoke_main+0x33
05 008ff7e8 00a11cfd handle_leak!__scrt_common_main_seh+0x157
06 008ff7f0 00a12098 handle_leak!__scrt_common_main+0xd
07 008ff7f8 77176359 handle_leak!mainCRTStartup+0x8
08 008ff808 77717b74 KERNEL32!BaseThreadInitThunk+0x19
09 008ff864 77717b44 ntdll!__RtlUserThreadStart+0x2f
0a 008ff874 00000000 ntdll!_RtlUserThreadStart+0x1b

有了详细栈信息,就知道发生句柄泄露的上层调用函数为handle_leak!OpenThreadFun,下面就是通过代码来分析为什么会发生句柄泄露了。

代码分析

本文内存泄露的一个简化例子,看下代码,很容易就知道句柄泄露的原因,就是因为手误注释掉了CloseHandle,打开线程句柄之后未关闭句柄。如果是大型工程的话,可能需要更详细大的分析。本文使用的代码如下:

#include 

关于作者

微信公众号:程序员bingo
Blog: https://bingoli.github.io/
GitHub: https://github.com/bingoli

如果 `FindWindow` 函数没有成功到窗口句柄,可能是以下几个原因: 1. 窗口类名或窗口标题不正确:`FindWindow` 函数需要传入正确的窗口类名或窗口标题才能到对应的窗口句柄。请确保你传入的窗口类名或窗口标题是正确的。 2. 窗口还未创建或已关闭:如果窗口还未创建或已关闭,`FindWindow` 函数将无法到对应的窗口句柄。请确保你在调用 `FindWindow` 函数之前,已经启动了对应的窗口。 3. 窗口属于另一个进程:`FindWindow` 函数只能查属于当前进程的窗口句柄。如果你需要查属于另一个进程的窗口句柄,可以使用 `FindWindowEx` 函数。 以下是一个示例代码,可以使用 `FindWindowEx` 函数属于另一个进程的窗口句柄: ``` Imports System.Runtime.InteropServices Public Class Form1 ' Windows API <DllImport("user32.dll", SetLastError:=True)> Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, ByVal childAfter As IntPtr, ByVal lclassName As String, ByVal windowTitle As String) As IntPtr End Function Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' 查窗口句柄 Dim handleParent As IntPtr = IntPtr.Zero ' 父窗口句柄 Dim handleChild As IntPtr = IntPtr.Zero ' 子窗口句柄 Dim className As String = "your_window_class_name" Dim windowTitle As String = "your_window_title" handleParent = FindWindowEx(IntPtr.Zero, IntPtr.Zero, className, Nothing) While handleParent <> IntPtr.Zero handleChild = FindWindowEx(handleParent, IntPtr.Zero, className, windowTitle) If handleChild <> IntPtr.Zero Then Exit While End If handleParent = FindWindowEx(IntPtr.Zero, handleParent, className, Nothing) End While If handleChild = IntPtr.Zero Then MessageBox.Show("未到窗口") Else MessageBox.Show("窗口句柄:" & handleChild.ToString()) End If End Sub End Class ``` 在代码中,你需要将 "your_window_class_name" 和 "your_window_title" 替换为你实际使用的窗口类名和窗口标题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值